A test in the repository (
src/server/docs-site.test.ts) imports the tool
lists from the code and fails if any tool name is missing from this page.
The prose around the names is checked by people, not by the test.How a session’s tools are decided
Three filters run in order, and a tool must survive all three.- Registration. A tool exists only if its server is built for the session. A connector’s server is built only when that brand’s credentials for it resolve. An operator switch that is off means the tool is never registered.
- The ceiling. A coworker run carries a list of permitted tool names. Every
server is intersected with it, and a server with nothing left is dropped.
A call to a name outside the ceiling is denied and logged as a
permission_deniedevent. - The per-call gate. Some registered tools ask a person before each call, or refuse above a spending limit. See Approvals and budgets.
tools), with a permission callback that is
always present. The code deliberately does not pass allowedTools: an
entry there is approved automatically and would skip the callback.
Source: src/engine/session.ts (createSession), src/engine/harness/claude-code.ts (claudeCodeOptions), src/engine/tool-surface.ts
Roles at a glance
Built-in Claude Code tools
The deployment’s built-in list istools in agent.config.json. As shipped it
is:
Bash, Read, Write, Edit, Glob, Grep, WebSearch, WebFetch
Because the list is an exclusive allowlist, every other built-in the CLI has
(for example Agent, NotebookEdit, TodoWrite) is not offered to any role.
Removing a name from agent.config.json removes it from every role.
Under E2B (selected when the operator sets an E2B key), a coworker’s ceiling
does not include the sandbox tools, so a coworker there keeps only
WebSearch
and WebFetch of the built-ins. This is reasoned from the code, not observed on
a run.
Source: agent.config.json, src/engine/config.ts (loadConfig), src/engine/environment.ts (HOST_FS_TOOLS), src/engine/guard.ts (evaluateAccess), src/server/registry.ts (coworkerCeiling)
Person chat
A person’s own conversation in the browser. It gets the widest surface, and it is the only role with someone present to approve gated calls.Platform tools
Environment tools (E2B only)
The browser reads pages only. Clicking, typing, filling forms, running
JavaScript, downloads, PDFs, cookies and long-lived sessions are not built,
because each turns a read into a write on someone else’s site.
What the sandbox can reach (since 2026-10-11). The E2B sandbox, shell and
browser alike, connects only to an allowlist: a short default list of package
registries, code hosts, CDNs and reference sites, plus the sites a brand adds
under Settings → Account and security → “Sites the sandbox can reach”. Private, link-local and
metadata addresses (
169.254.0.0/16 among them) can never be added to it.
That does not make them unreachable: E2B’s filter was measured on 2026-10-11
not to block 169.254.169.254 under this policy either (it answered HTTP 401).
Since 2026-10-12 each sandbox gets a root firewall rule at start that refuses
169.254.0.0/16. A sandbox where the rule does not take is killed, not used.
The rule was measured to work. The first run also measured the agent’s own
user removing it with passwordless sudo, and this page then said the rule
did not stop a command that tries to remove it. Since a second run that day,
the sandbox’s user loses its sudo at start, before the agent gets the sandbox.
After that, sudo, su and E2B’s own in-sandbox service all refused, and the
rule stayed. A sandbox where the removal fails is killed, not used. Three gaps
remain: the browser sandbox runs the agent as root, so there the rule is still
removable; the operator can turn the removal off (E2B_SANDBOX_SUDO=keep);
and the base sandbox left E2B’s own root-run program writable by the agent.
Since later that day, the files root runs under /etc and /usr
(/usr/local excepted) are made read-only to the agent at start, and a
sandbox where that does not hold is killed, not used. On one test sandbox
that took the two writable files to none, and npm and pip installs still
worked. It does not cover /usr/local, /opt, setuid programs or the
browser sandbox.
When a
command fails against a site that is not listed, the agent can ask with
mcp__sandbox_egress__sandbox_allow_host, which parks a card offering
Allow once (this conversation’s sandbox only), Allow for this store
(saved to the brand’s list) and Don’t allow. Either yes widens the running
sandbox in place. In a run with nobody to ask the tool is absent, and an
unlisted site stays blocked. SANDBOX_EGRESS=open restores the earlier
posture: the open internet minus the private ranges, with no card.
Connector tools
A person’s session gets every tool of every connector the brand has connected, subject to the operator switches below. The full list, and what each connector’s writes require, is on Connections. The names in brief:
The Shopify-side content tools for the Content writer preset are listed under
Specialist coworker run: a person’s session does not
register them.
Whose credentials a session uses: a brand’s own stored connections first; then
a connection the account owner has shared with that brand; then, for the
deployment owner’s own brand only, the server’s host environment. No other
brand ever inherits the host environment.
Source:
src/engine/session.ts (createSession), src/engine/connector-tools.ts (TOOLS_BY_CONNECTOR), src/engine/credentials.ts (resolveConnectors), src/server/registry.ts (create)
Inbound email turn
WhenAGENTMAIL_WEBHOOK_URL_SECRET is set, an email that arrives in the
brand’s AgentMail inbox starts one turn on the default model. A stranger chose
its input and nobody is watching it, so its session is built narrow:
Tools: mcp__agentmail__agentmail_list_threads,
mcp__agentmail__agentmail_search_threads, mcp__agentmail__agentmail_get_thread,
mcp__agentmail__agentmail_list_messages, mcp__agentmail__agentmail_search_messages,
mcp__agentmail__agentmail_get_message, mcp__agentmail__agentmail_get_attachment,
mcp__agentmail__agentmail_whoami, mcp__agentmail__agentmail_create_draft,
mcp__agentmail__agentmail_list_drafts, mcp__agentmail__agentmail_get_draft.
Not given: any built-in tool, the sandbox or browser, memory, the project
folder, transcripts, skills, goals, team, every other connector, and the four
AgentMail send tools whatever AGENTMAIL_ALLOW_SENDS says. The session is
handed AgentMail’s credentials and no others, so no other connector’s server is
even built.
No one to ask. Like a coworker run, an inbound turn has no approval channel,
so nothing it does can park an approval card.
Limits: $0.50 and eight turns per message, or the deployment’s own
per-session limits if they are lower.
Until 2026-10-11 an inbound turn had the same tool surface as person chat, on
the deployment’s per-session limits, and “read-only” was only an instruction
in its prompt. That was the gap this section used to warn about. The new
surface is proved against a stand-in for the CLI, not on a live delivery.
src/server/http.ts (inbound webhook route), src/server/registry.ts (createInbound), src/engine/agentmail-inbound.ts (inboundSessionShape, INBOUND_TOOLS, composeInboundTurn)
Coworker run
A coworker is a saved profile that runs a task unattended. Its permitted tools are the intersection of what its profile asks for and the coworker ceiling. The coworker ceiling on a standard route is the deployment’s built-ins plus memory and the project folder:Bash, Read, Write, Edit, Glob, Grep, WebSearch, WebFetch,
the nine memory tools, and four of the five project tools. Since 2026-10-11
mcp__project__project_delete is not in it: a delete is recursive, unapproved
and cannot be undone, so only a person’s own chat holds it. A saved profile
that lists it keeps the entry and loses the tool.
On a read-only route it is only the read-only built-ins: Read, Glob,
Grep, WebSearch, WebFetch.
What a coworker run actually holds, beyond the ceiling:
Limits. Each profile carries its own
runBudgetUsd (must be above zero),
maxTurns (at least 1), maxConcurrentRuns (at least 1),
maxDelegationDepth (0 or more), and optionally maxAttempts (default 1). The
run’s dollar and turn caps are the profile’s, not the deployment’s. The
monthly model cap is the brand’s and is shared with person chat.
A retry is narrowed to the tools the previous attempt actually ran with. A
profile whose requested tools all fall outside the ceiling is refused rather
than started with none.
Source: src/server/registry.ts (coworkerCeiling, create), src/engine/coworkers.ts (CoworkerLimits, permittedTools), src/engine/tasks.ts (ceilingFor), src/engine/memory.ts (memoryToolNames)
Specialist coworker run
A specialist is a coworker with a grant: a list of connectors, a spending allowance for the connectors that bill, and optionally bundled skills. Only a person can write a grant, through the coworker settings (PUT /coworkers/:id). An agent cannot widen its own or another coworker’s
grant.
The grant adds to the coworker ceiling, in this order:
- The tools of each granted connector that are in the read table
(
CONNECTOR_READS) and whose server will actually register with the credentials the run is handed. - A joint tool, such as
mcp__dataforseo__seo_page_opportunities, only when every connector it reads (DataForSEO, Search Console and Shopify) is granted and live. - Opt-in tools of a granted connector: today the content writer’s tools beside Shopify, given only to a profile that asks for them by name.
- The skill tools, when the grant names at least one bundled skill that resolves.
- Tools that ride on one skill: today the SEO brief tools on
seo-method.
The read table
These are the only connector tools a specialist or the goal agent can be granted.
The content writer tools write only into the brand’s project folder: a voice
guide saved as a draft, and page copy saved as a draft. Nothing is written to
the store. A voice guide is used only once its status line says
APPROVED,
which the tool itself can never set.
SEO brief tools
Rules a grant must pass
A profile is refused when it is saved, not at its first call, if:- it asks for a connector tool its grant does not cover;
- it asks for any tool outside the read table (“a specialist reads and proposes; it never commits”);
- it grants a connector that bills (DataForSEO or Firecrawl) without an allowance, or with a per-run figure above the per-day figure;
- it asks for a skill tool without granting a skill, or names a skill that is not bundled;
- it asks for
mcp__seo__seo_hand_off_briefat delegation depth 0.
Allowance
The per-run figure is enforced inside the connector’s spending gate on every
call. The per-day figure is checked before a run starts: runs already ended
today count at what they recorded (or their whole per-run figure if they
recorded nothing), and live runs count at their whole per-run figure.
Google products in a grant also narrow the Google credential the run is handed
to the scopes of the granted products.
Presets
The coworker setup screen offers these starting points. Each is a profile a person can still edit before saving.
Presets default to $0.50 per run, one run at a time, no delegation and ten
turns. The SEO analyst may delegate one level so it can hand a brief to the
Content writer. The Content writer is not given
mcp__project__project_write,
so it cannot mark its own voice guide approved by editing the file directly.
Source: src/server/specialist.ts (specialistCeilingFor, specialistToolsFor, workerConnectorEnv), src/engine/connector-tools.ts (CONNECTOR_READS, OPT_IN_TOOLS_BY_CONNECTOR), src/engine/coworkers.ts (specialistProblem), src/engine/allowance.ts (specialistDayProblem), src/engine/seo/handoff.ts (TOOLS_BY_SKILL), public/coworker-presets.js
Goal agent step
The built-in goal agent (goal-agent) runs one step of a goal plan when a
person starts it from the Goals page. It runs as an unattended coworker.
Tools: Read, Glob, Grep, WebSearch, WebFetch,
mcp__project__project_list, mcp__project__project_read,
mcp__project__project_write.
Plus, if a person has given it a connector grant under Goals, Advanced
(PUT /goals/agent), the granted connectors’ tools from the read table, with
an allowance for any that bill.
Not given: Bash, Write, Edit, memory, transcripts, skills, team,
goals, and any connector tool outside the read table.
Limits: $0.50 per step, ten turns, one at a time, no delegation, at most
two attempts. There is no separate daily cap for the goal agent; a granted
billing connector’s per-day allowance applies.
An agent step has never been run against the real service, with or without a
grant (
GOALS_UNVERIFIED.agent_step_run, GOALS_UNVERIFIED.agent_step_with_grant).src/engine/goal-agent.ts (GOAL_AGENT_TOOLS, goalAgentToolsFor, goalAgentInvariantProblem)
Goal reviewer
The reviewer reads goal progress on each goals tick and writes a short review. Tools: none. Its profile asks for an empty tool list, and the code refuses to start a reviewer that has any tool, any delegation, or any grant. Limits: 0.25 per day,GOALS_REVIEW_DAILY_USD). If today’s spend cannot be
read, no review runs.
Model: a reviewer created after 2026-09-23 defaults to
google/gemini-3.8-flash through OpenRouter.
What an empty tool list does on the wire was measured locally on 2026-09-05
against CLI 2.1.261: zero tool schemas sent, against 24 with the option
omitted (GOALS_CAPTURED.empty_tools_on_the_wire). A review on the deployment
has never been run (GOALS_UNVERIFIED.zero_tool_worker).
Source: src/engine/goal-reviewer.ts (reviewerProfile, reviewerInvariantProblem, REVIEWER_MODEL), src/engine/goal-store.ts
Read-only route
A route is read-only when its model is not certified for the standard tool set, or when it runs on an uncertified harness. The session is assembled narrow, not fenced wide.
Which routes are read-only today:
- Models. Every gateway model in the registry is certified. The one held
back is
claude-haiku-5-5, which is read-only for every brand except the owner’s: the owner’s own sessions run uncertified models on the standard set. - The loop harness. On only when
HARNESS_LOOP_ENABLEDis1ortrue. Owner only, OpenRouter only, $0.25 and eight model calls per session. It is given MCP tools only, never the CLI built-ins. - The Hermes harness. On only when
HARNESS_HERMES_ENABLEDis1ortrueand the MCP bridge is on (HARNESS_MCP_BRIDGE_ENABLED). Owner only, OpenRouter only, $0.25 and eight turns. Of Hermes’s own tools, onlytodo_list(also calledtodo),skills_list,skill_view,skill_manageandsession_searchare enabled, all scoped to a throwaway folder deleted when the session ends. Every permission request Hermes raises is rejected. The bridge listens on loopback only and serves only the owner on a read-only route.
The loop harness has one observed owner turn (2026-10-10,
LOOP_CAPTURED). Hermes has never run against this adapter:
HERMES_CAPTURED and BRIDGE_CAPTURED are empty.src/engine/session.ts (read-only ceiling), src/engine/backends.ts (READ_ONLY_BUILTINS, CLAUDE_HELD_BACK), src/engine/harness/registry.ts (switchOn, harnessSessionLimits), src/engine/harness/hermes.ts (HERMES_OWN_TOOLS), src/engine/harness/mcp-bridge.ts
What is never given, to any role
Each connector keeps a written list of what it deliberately does not build, and why. A summary:
Source:
GOOGLEADS_WITHHELD, MICROSOFT_ADS_WITHHELD, FIGMA_WITHHELD, SEARCH_CONSOLE_WITHHELD, ANALYTICS_WITHHELD, SHEETS_WITHHELD, GITHUB_WITHHELD, RAILWAY_WITHHELD, TAILSCALE_WITHHELD, BROWSER_WITHHELD, and WITHHELD_TOOLS in src/engine/agentmail.ts and src/engine/firecrawl.ts
Where “absent beats refusing” has exceptions
Three groups of tools stay registered and refuse, on purpose:- AgentMail sends register whenever
AGENTMAIL_ALLOW_SENDSistrue, and refuse when the session has no approval channel. The refusal is the control: a session that cannot ask anybody cannot send mail. - GitHub writes (
mcp__github__github_create_branch,mcp__github__github_commit_files,mcp__github__github_open_pull_request,mcp__github__github_comment) register with the connector and refuse, before any request to GitHub, whenGITHUB_ALLOW_WRITESis off or there is no approval channel. - Google Sheets
mcp__sheets__sheets_exportandmcp__sheets__sheets_appendrefuse without an approval channel. - Shopify mutations through
mcp__shopify__shopify_admin_graphqlrefuse, before any request to Shopify, when there is no approval channel. The tool stays registered because it is also the read path, and reads still run.
SHOPIFY_ALLOW_WRITES alone when approvals
were switched off. That was a hole, and it is closed.
Source: src/engine/session.ts (comments at the AgentMail and Shopify servers), src/engine/agentmail.ts, src/engine/github.ts (preflight), src/engine/sheets.ts, src/engine/shopify.ts
Operator switches
Set on the server’s host environment. None can be changed from inside a session, and none can be set per brand.
Spending limits for each connector are on
Approvals and budgets.
Source:
src/engine/approval.ts (approvalRequired, approvalTimeoutFromEnv), src/engine/shopify.ts, src/engine/github.ts, src/engine/agentmail.ts, src/engine/railway.ts, src/engine/browser.ts, src/engine/browser-config.ts, src/engine/sandbox-egress.ts, src/engine/harness/registry.ts
What has been measured and what is reasoned
The code keeps two kinds of record side by side:*_CAPTURED maps for
behaviour observed against the real service, with a date, and *_UNVERIFIED
maps for behaviour reasoned from documentation. Only the first kind is a
measurement. A passing test suite is evidence about the test fakes, not about
any vendor.
Measured
Reasoned, never exercised
Source: the
*_CAPTURED and *_UNVERIFIED exports across src/engine/