Skip to content

Tools ​

A tool is one thing a session's model can call. Four places contribute them: this host's own set, what a plugin registered, what the clients in the session announced they can run, and the MCP servers the deployment configured. All four end up in one list the model is offered, and the ones that are not the model's own are what this page is about - what a tool is on the wire is AHP.md.

Terms, one line each:

Term
a host toolA ToolDefinition and a run, contributed to every session's model
serverToolsThe host's tools as a session reports them, on its own state
a client toolOne a connected client announced on SessionActiveClient.tools and runs itself
an MCP serverA program or endpoint whose tools an agent reaches over MCP, configured under mcpServers
the tools endpointOne MCP server per session, served by this host, for a backend that cannot call tools in process
advanced permissionA tool's own claim that it does more than a session's ordinary work
a tool's effectsThe claim, reads, writes, network and destructive, a backend's policy reads

The four sources ​

This host's own, from hostTools(): the reference host's eleven session tools, its three artifact tools, its six comment tools, its four Agent Merge tools, and this host's own three. They are the protocol's serverTools, reported on SessionState.serverTools, offered to every backend that can take tools, and replaced whole - which is what session/serverToolsChanged means.

A plugin's, through host.registerTool(tool), which appends one to the same set. The computer plugin contributes three this way - PLUGINS.md.

A client's, announced on SessionActiveClient.tools. The protocol makes the client that announced one responsible for executing it and dispatching the result, so these carry an owner and no implementation.

An MCP server's, from mcpServers in the daemon's configuration. These are not this host's tools at all: an entry names a server the agent itself starts or reaches, and the agent's own harness turns its tools into calls.

The host's own tools ​

The twelve session tools are the reference host's, by name and by schema, so a skill written against it works here unchanged. Each is a thing an agent inside one session cannot see for itself: the sessions running beside it, the chats inside them, and the terminals a person is watching. This host adds two readers beside them, list_agents and list_models, whose text is this host's own in the reference's style.

ToolWhat it does
list_sessionsThe catalogue, the same rows listSessions answers from
list_agentsThe agents a new session may be started on, each with the agent id create_session takes, the name a person reads, the settings its config accepts, and its first five models
list_modelsThe models the agents offer, in the order the host learned them: one agent's when agent is named, every agent's otherwise
get_current_sessionIts own session, as list_sessions would describe it
get_session_contextA chat's turns, its active turn, whether there is history before them, the caller's pending messages there, and the agent of a chat that runs another one
send_messageA message into any chat of any session this host runs: sent when the chat is free, queued when it is busy
create_sessionStart another session, owned by the owner of the one it was called from, or by the person calling with no session
create_chatA second conversation in a session
rename_chatTitle a chat. The session's title is its first chat's
archive_chatArchive a nested chat alone, or a session's main chat or a session link to archive the whole session
set_workspaceMove the session to another directory, acted on when the turn ends rather than mid-turn
isolate_sessionMove only the calling chat to a new Git worktree, cut from the folder it works in and acted on when the turn ends
delete_sessionDelete a session this host runs, refused for the one it was called from
move_sessionReorder a nested session in its session, move it into another session, or make it its own

A list_sessions row lists its chats when the session has a chat that is not its default: each with a title, an openLink that names it in send_message, get_session_context, rename_chat and archive_chat, its status in the words the row's own status uses, and harness when it runs on another agent than its session. The default chat comes first with main: true and the session's own openLink, since a link without a chat id is that chat. A session whose only chat is its default has no chats, and a tool's chat or a hidden one is left out, as VS Code's host leaves both out. get_current_session answers the same field.

get_session_context answers a chat's newest turns, its running one, and whether older ones exist behind the page, cut to the level asked for: summary, digest or full. A reply longer than its level's cap carries assistantTruncated and the responseTurnId that reads it whole, and at digest and full each turn lists its tool calls as {name, status}, with ok on a completed call, reason on a cancelled one such as denied or skipped, error cut to 200 characters on one that failed, and input at full alone. Passing responseTurnId and responseOffset answers one page of up to 8000 characters instead of a transcript, with responseLength, nextResponseOffset while more remains, and truncated; follow nextResponseOffset until truncated is false. messagesSentByMe lists the pending messages the calling chat left in the chat it reads - messageId, revision, delivery (steer or queue), status and message - which is how a chat recovers the id and revision to replace or cancel one; a call with no session gets none. harness names the agent of a chat that runs another than its session's, and is absent for the session's own chat.

create_session takes two settings of its own. agent names the backend the new session runs, by provider id; absent, the choice is the provider of the model asked for, then the calling chat's, then the first backend the host holds. The input is an enum of the agents the caller may start, each named in its description, so an agent a record holds for somebody else is neither offered nor accepted. A provider the host does not offer is refused, and the refusal names the providers it does. config is the new session's settings, as that backend's schema names them, checked the way createChat checks a chat's: a value the schema rules out is refused before anything starts, and the host's own isolation and model win over a key of the same name. With relationship: currentSession, both reach the new chat instead, so it runs on the named backend with those settings while the session keeps its own. What a call leaves out it takes from the chat the tool runs in, as VS Code's host does with its getCreationDefaults: where the two run one backend, the calling chat's model when the call names none (its running turn's, else its draft's, else its last turn's), and for a new session the settings that backend names as inherited from the calling chat's config (Agent.inheritedChatConfig), under any the call names. A call with no calling chat, as through the /mcp door, has no defaults to take, and names what it wants.

set_workspace and isolate_session are the ones that cannot act when they are called: the agent is restarted in the new directory, and a restart mid-turn is a turn that never finishes, so the request waits for the turn to end, and is carried out when the turn completes and dropped when it is cancelled or fails, as VS Code's host carries out a workspace change. A worktree the session worked in stays the session's, as SESSIONS.md says. delete_session acts as the person whose turn it was called in, and as the owner of the session it was called from where no turn is running: it is never the model, and never whichever connection started the session it names. move_session is the host's moveChat, so a tool's move and a client's make the same actions and are refused in the same words: it names what to move, into for the destination, after for the chat to sit behind (the end when left out), or relationship: "independent" for a session of its own. It is refused while chatMoves.enabled is off. isolate_session takes no input at all and moves the calling chat alone, into a worktree cut from the one local Git folder it works in; the tree carries the session's worktreeIncludeFiles, as a session's own tree does, and the session's other chats keep the folders and the processes they had. It is offered only to a chat of a session whose agent keeps a folder per chat, since a session of one folder moves as a whole with set_workspace, and only to the session's own chats, never to a worker chat a call spawned. archive_chat archives a nested chat alone, and a session's main chat (the one with main: true in its chats) or a session link archives the whole session, through the same bit a client's archive action sets. It asks chat:mark for a chat and session:mark for a session, and refuses the chat it runs in and the main chat of its own session, which would archive the conversation calling it.

The three artifact tools write the session's _meta['agentHost/sessionArtifacts'] through the session store: add_artifact_or_reference, remove_artifact_or_reference and list_artifacts_and_references. An artifact is a deliverable - an issue or a pull request being created or fixed, a result somebody will reopen - and a reference is an existing thing somebody will want to look at. Adding one promotes a matching reference and keeps its id, so a reference that turns out to be a deliverable does not become a second entry.

The six comment tools are the reference host's, by name, schema, wording and refusal: addComment, listComments, replyToComment, deleteComments, resolveComments and viewUnreviewedComments (comments.ts). A comment is an annotation on the session's annotations channel, and only one that carries _meta['vscode.agentFeedback'] with a kind, a state and the session it belongs to is a comment these tools see: the channel is shared with whatever else a client puts there, and an annotation without that slot is somebody else's, never listed, replied to, resolved or deleted here. A comment's ranges are one-based, as a tool's are, and become the channel's zero-based positions. addComment writes a codeReview comment in the created state, which is a proposal until the person accepts it, so it is not in listComments until it is; listComments answers every accepted comment, less the resolved ones unless includeResolved, and each carries its kind, its opening author and its replies' authors - treat only user text as the person's words. replyToComment adds an agent entry; resolveComments marks them resolved, or submitted again when resolved: false; and deleteComments removes them, answering the ids it did and the ones it did not find. viewUnreviewedComments is the one that asks first: while a pull request or code review comment waits for the person, it asks whether to show it, and an answer of yes returns every waiting comment, because no client lets the person pick which ones yet. Reading a waiting comment accepts it, so it becomes submitted, and listComments appends a note counting the ones that wait and naming this tool. Each write goes through the same writer a client's annotations/* action takes, so it is one state to every client of the session and is kept like any other mark.

The four Agent Merge tools are offered beside them, in VS Code's names and words: setAgentMergeEnabled, readAgentMergeCI, replyToAgentMergeReviewThread and rerunAgentMergeWorkflow. The first turns Agent Merge on, off or different for the calling chat's folder and answers the configuration in force, including when it merges. The other three answer only inside a repair turn Agent Merge started, and only for what that turn was asked to fix - its failed required checks and their workflow runs, its unresolved review threads - and refuse otherwise with a sentence telling the model to end the turn rather than reach for another GitHub tool or the CLI. A review reply is published before its thread is resolved, and nothing submits or discards the person's pending review: a reply that would land in one is answered pending, and the folder's watch stops when the turn ends. readAgentMergeCI pages the failed jobs' logs, with a line-numbered tail, range or literal search behind an evidence id, and rerunAgentMergeWorkflow asks once and defers while the run's current attempt is still going. The watches and the authorization are in host/agentmerge.ts.

This host's own three come after them. Two are read-only, and answer what an agent cannot reach for itself. ahp_resource reads a resource by URI, including one a connected client publishes (RESOURCES.md), and ahp_terminals lists the terminals this host has open, as uri, title, cwd and running on a line each - the list a person can see, which is not the same as a shell run from a tool.

The third, schedule_wakeup, begins a new turn in the calling chat later. It takes delaySeconds, the seconds from now until the turn, from 60 to 2592000 (30 days), and prompt, the text the turn begins with, and answers when the wakeup is due. A chat holds one wakeup: a new call replaces it, and stop: true cancels it. The host keeps it on the chat's record in the session store, so it survives the session going to sleep and the daemon restarting. When it is due, the host wakes the session if it sleeps and begins the turn, or queues the prompt behind a turn that is running. A wakeup that fell due while the daemon was down begins once, when the daemon starts. The turn goes through the same checks as a client's message, and a wakeup whose turn is refused or whose session cannot resume stays stored and fires again when the daemon next starts. A pending wakeup does not keep the session awake - SESSIONS.md.

Tools that need no session ​

A tool with sessionless: true runs with no calling session. Its run is given a ToolCall whose session and chat may be absent, and host.sessionlessTools(caller) answers the tools a caller may see, each bound to that caller: the session a call reaches, the person it acts for, and the grants it asks are the caller's, not a turn's. A tool with no sessionless is never in that list, whichever grant it names.

The caller is this host's root connection, { root: true }, or a person, { principal }. grant is the grant a caller needs to see a tool in that list: root sees every listed tool, a principal sees the ones whose grant it holds, and a host with no users directory sees all of them. A tool that names no grant is one every caller sees.

Eleven of the fourteen session tools are listed. list_sessions, list_agents, list_models and get_session_context ask session:read, and create_session, create_chat, rename_chat, send_message, archive_chat, delete_session and move_session ask session:write, the same grants the table under Grants names for a turn. get_current_session, set_workspace and isolate_session are not listed, because each acts on the session or chat the call came from and on nothing else.

list_agents and list_models answer only the agents the caller may start, the rule a start on an agent is refused by: an agent a record holds for somebody else is not named, as an unsigned caller is told the host's agents alone. list_agents names each agent's first five models, the order its probe lists them in, and list_models names every one.

A call with no session is the agent's own, with no delegation _meta, so no turn above it can be named. It refuses what needs one: create_session refuses relationship: currentSession with currentSession needs a calling session; use independent, and create_chat, rename_chat, send_message and get_session_context refuse a call that names no session. What it makes is the caller's: a session create_session opens with independent is owned by the caller and has no parent, so it is no link in any chain, and a message send_message sends is a turn recorded as the caller's. A call with no session also spends none of the tree budgets under Limits, since there is no tree to charge. A tool_call event for one names the person and no session.

A client's tools ​

A client announces them when it becomes active in a session, and this host offers them to the model beside its own. They are named <clientId>__<name>, because two clients in one session may both provide openFile and the model is offered one list. A call of one is drawn in its chat under the name its client announced, openFile, on every backend. A client with no id, or a name that is not letters, digits, _ or -, is dropped rather than offered as a tool the model will fail to call.

None of them runs here. The call is reported against the client that provides it, and only that client may complete it, which is what unblocks the agent. A client that never answers is one the call is failed on after clientToolTimeoutMs: ten minutes, or never when it is 0. This host hands the number to the backend when the session starts rather than timing a call itself, so what a call waits is one number decided once.

A client whose connections all close keeps its place in the session, and the calls it owes there, for clientToolCallDisconnectTimeoutMs, thirty seconds by default, as VS Code's host does; one that connects again inside it has never left. When it runs out, or the client unsubscribes from the session, this host fails each call the client still owes in VS Code's words: a call still streaming is readied first, then completed with success: false, pastTenseMessage "<name> failed" and the error "Client <id> disconnected before completing <name>", and where another client in the session provides a tool of the same name, the content tells the model it may try the call again. The backend holding the call is handed the same failure, so the agent goes on.

MCP servers ​

mcpServers is the host's own servers, by the name a person gave each, and every session is offered them whatever agent it runs. An entry is one of the two shapes VS Code uses.

FieldShapeWhat it is
typebothstdio or http, and it decides which of the two below an entry is
commandstdioThe program to run. Required
argsstdioIts arguments, as an argv
cwdstdioThe directory to start it in
envstdioVariables for the process, over the host's own
urlhttpThe endpoint to reach. Required
headershttpHeaders to send with every request
json
"mcpServers": {
  "files": { "type": "stdio", "command": "mcp-server-filesystem", "args": ["/srv"], "cwd": "/srv", "env": { "TOKEN": "..." } },
  "issues": { "type": "http", "url": "http://127.0.0.1:9310/mcp", "headers": { "Authorization": "Bearer ..." } }
}

An entry that is neither shape, or that is missing the one its own type needs, is one warning in the log and is left out: one server nobody can reach is a gap in one agent's reach, where a wrong port is a daemon that would not run at all. A value in env or in headers is a credential, so each answers <set> wherever root config is shown.

The map is read at each session's start rather than once when the host was built, so a change reaches the next session opened and a running one keeps the servers it started with. It is absent rather than empty when the host holds none. A server a client plugin contributes is merged over the host's, a client plugin winning a name clash. The Claude backend hands the map to its CLI as the SDK's own servers, beside the ones the CLI's own files name: the person's ~/.claude.json, and a trusted project's .mcp.json. A name both use is handed over as the host's, and the CLI's own precedence settles it against its files, as VS Code's agent host leaves it. A stdio server whose cwd is not the session's folder is left out there and logged, because the CLI starts a stdio server in the session's folder. An ACP agent is handed the map on its session/new. A backend that cannot take MCP servers ignores it, which is what pi and cofold do - they reach the same servers through their own configuration.

Advanced permission ​

advancedPermission is a tool's own statement that it does more than a session's ordinary work, and advancedTools is the operator's answer to it. A tool that declares one is absent from every session unless the host permits them: not reported in serverTools, not bound for a call, so a model is never offered it rather than being refused at call time.

Off by default. Turned on with advancedTools in the configuration, --advanced-tools on the command line, or a root config push, which takes hold while the daemon runs./HOST.md).

The three that declare it are the computer's: request_disposable_computer, which makes a machine, release_computer, which destroys one, and run_in_computer, which runs a command inside one. ahpd plugin list is unaffected either way: it says what would load, which is a different question from what a session is offered.

effects is a different claim and is not a permission. reads, writes, network and destructive say what running the tool does, and a backend's policy reads them to decide what to ask a person about. The computer's three declare both: effects for the policy, advancedPermission for whether the host offers them at all.

Which sessions are offered the session tools ​

An install can turn the eleven session tools off, as one answer or per provider, with two daemon keys. sessionTools is the switch: true, the default, offers them; false offers them to no session. sessionToolsProviders is a list of provider ids and narrows the switch to those providers' sessions; empty, the default, narrows nothing.

They are two keys rather than one because a key carries one type, and one key cannot be both a boolean and a list: sessionTools is a control a settings screen draws, and sessionToolsProviders is a list it draws. The list narrows rather than replaces, so false with a list still offers the tools to nobody.

What it covers is the eleven and nothing else. The three artifact tools, the six comment tools, ahp_resource, ahp_terminals and schedule_wakeup are this host's own rather than the reference host's session tools, so they are offered whatever the option says - an install can keep a person's artifacts, comments and wakeups without giving its agents the catalogue of the sessions beside them.

A tool left out is absent from SessionState.serverTools and is not bound for a call, the same shape as advancedPermission: a model is never offered it rather than being refused at call time. The answer is worked out per session, from the provider that session runs, so a list offers the tools to some sessions and not to others. A session whose provider this host is only browsing, with no agent of its own, is one a list does not name.

Both are daemon keys and apply while the daemon runs: a root config push to either rebuilds what every running session is offered and tells each of them. The command line takes both, each spelled as its key: --session-tools and --no-session-tools for the switch, and --session-tools-providers claude --session-tools-providers echo for the list, which is repeatable.

The wording a session is given ​

A tool says whether a session supports it, and three things shape it per session.

isEnabledForSession is the first, as VS Code's host asks each tool: a tool that answers false for a session is neither offered to it nor bound for it, and one that says nothing is offered to every session. It is asked beside the host's own switches, advancedPermission, optIn and the session tools' keys. rename_chat answers false under the utility title strategy, which names chats itself, isolate_session answers false for a session whose agent has no multipleDirectories, whose chats have no folder of their own, and a connector's action tool answers false for a session that binds no connector of its adapter.

Every tool may carry an instruction, which a backend that can add to its system prompt adds while the tool is offered. A description says what a tool does; an instruction says when a tool nothing asks for is worth calling. The artifact tools carry the reference host's.

forSession is the wording a session asks for, merged over the tool's own definition, as VS Code's getDefinitionForSession tailors a tool to a session. Every session runs under the deferred title strategy: this host names a chat after its first message, and rename_chat is offered without its automatic property, because the case that property covers is exactly the one this host handles. The type carries two more strategies a tool answers for, activeAgent, the agent naming its own chats, and utility.

deferLoading is a host-side hint and never on the wire: it says the harness may hide this tool behind tool search. The artifact tools set it the way the reference does - add_artifact_or_reference, which the instruction names, is never deferred, and removing and listing one are, because the model reaches them through the discovery that instruction points at. The six comment tools are all deferred, as the reference defers them. The four Agent Merge tools are deferred as the reference defers them, behind the sentence setAgentMergeEnabled opens with. set_workspace is deferred too, and forSession offers it up front to a session that started workspace-less, whose instructions point at it, as VS Code's host does; it stays so after the session takes a workspace. isolate_session is deferred as the reference defers it, and is withheld from a worker chat a call spawned, as VS Code's topLevelChatOnly withholds it. A tool that says nothing leaves the harness's own default in force.

Beside the tools' own wording, a session carries the rules of the clients in it. A client publishes a plugin on SessionActiveClient.customizations, and this host copies it; a rule in that copy is a .mdc or .instructions.md file, and only one whose frontmatter says alwaysApply: true applies. The rest are rules an agent reads where they apply, which is a decision this host has no turn to make. The body of each that applies joins Start.instructions after the host tools' own, with its frontmatter left behind. A body over 4096 characters is dropped, and the log names the rule.

Claude appends the instructions to its preset prompt, and pi and cofold add them to the prompt they start with. An ACP server keeps its own system prompt, so the chat's first prompt opens with the words as text instead. The rules are read when a chat starts, so a change reaches the next start, and a plugin set that moves restarts the chat between turns.

How a model reaches them ​

A backend that can call the host's tools in process is handed them as Start.tools, and the Claude harness's two in-process servers are what the model sees them through, by VS Code's Claude's names: this host's are mcp__host__* and a client's mcp__client__*.

A backend that cannot - an ACP agent, which asks its client for tools - is handed an endpoint instead. start.toolsServer() opens one MCP server per session on the host's own listener, under /ahp-mcp, with one path and one bearer token for that session, speaking streamable HTTP at revision 2025-06-18 and answering initialize, tools/list and tools/call. The same list as the in-process case, and a tools/call for a client's tool is handed to the backend's runner rather than run here. The endpoint is opened once per session and closed when the session goes. A daemon over stdio has nothing to serve on, so a session there is offered none. The shape that goes to an agent, and what a server that cannot take an http server is told, is PLUGINS.md.

Configuration keys ​

KeyValuesDefaultWhat changes
advancedToolstrue or falsefalseOffer the tools that declare advancedPermission to every session's model. Applies live
sessionToolstrue or falsetrueWhether the eleven session tools are offered at all. Applies live, and is worked out per session
sessionToolsProvidersa list of provider idsnoneOffer the session tools to the sessions of these providers alone. Empty offers them to every provider. Applies live, and is worked out per session
mcpServersan object of server entriesnoneThe MCP servers every session is offered. Applies live, and is read again for each session opened
clientToolTimeoutMsinteger, milliseconds600000 (ten minutes)How long a tool call a client runs may wait for that client. 0 waits for ever
clientToolCallDisconnectTimeoutMsinteger, milliseconds30000 (thirty seconds)How long a client whose connections all closed keeps its place in a session and the tool calls it owes there. 0 releases it at once
plugins.<name>.hostToolstrue or falsetrueWhether a plugin's sessions are offered this host's own tools as one MCP server

The first four are the daemon's keys and apply while it runs; a root config push of any needs config:write. clientToolTimeoutMs and clientToolCallDisconnectTimeoutMs are daemon keys and need ahpd restart. Every key with its values and its default is HOST.md and HOST.md.

Commands ​

There is no ahpd tools: the set is the host's own, and a deployment changes it by configuring what is loaded rather than by typing a verb. What does change it is this.

WhatWhat it does
setTools(tools)Replace the host's contributed set whole, for a host built from the SDK. Dispatches session/serverToolsChanged with the new definitions
registerTool(tool)A plugin appending one, before or after the host is running
plugins.<id> and plugins.<id>.hostToolsahpd plugin list says what would load; the option says whether that plugin's sessions take the host's own tools
a client becoming activeIts announced tools join the list, and one that stops being active takes its own away

Grants ​

A session tool is reached from a turn and not from a socket, and the turn says whose it is: under a users directory each one asks the grants of the person who sent that turn. A refusal is the host's own words, the sentence a client is told on the wire, naming that person: reader may not session:write here. Where nobody is behind the call there is no id to name, and the refusal is about the turn instead: this turn may not session:write here. send_message from inside a session is the same operation chat/turnStarted is, arrived at from the model instead of the network, so a person who may not send a turn may not send one this way either - and nothing is reachable from a tool that is not reachable from a client. A host with no users directory has nobody to ask, and its tools act as they always did.

ToolGrant
list_sessionsNone, because the listing is already cut to that person: session:read is what decides whether it answers the catalogue or nothing
get_current_sessionsession:read
get_session_contextsession:read
set_workspacesession:write
isolate_sessionsession:write
create_sessionsession:write
create_chatsession:write
rename_chatsession:write
send_messagesession:write
delete_sessionsession:write, and then the owner's own say: only the session's owner, or a person holding session:*, may delete one
move_sessionsession:write
schedule_wakeupNone, because it acts on the calling chat alone
add_artifact_or_reference, remove_artifact_or_reference, list_artifacts_and_referencesNone: they write the calling session's own record
addComment, replyToComment, resolveComments, deleteComments, viewUnreviewedCommentssession:review, the grant that says a person may write a review on this session
listCommentsNone: it answers the comments the calling session may already read
setAgentMergeEnabled, readAgentMergeCI, replyToAgentMergeReviewThread, rerunAgentMergeWorkflowNone: they act on the calling chat's folder with the token the host was lent, and the last three only in a repair turn Agent Merge started
ahp_resource, ahp_terminalsNone: they answer what the calling session may already reach

A call nobody is behind holds the read half alone: a turn this host began for itself, a turn a rule nobody owns started, a turn whose sender has not signed in since this process started, whose grants the host cannot name, and a call reached with no turn running at all, which is a window outliving the turn it sent. Each reads the sessions and is refused every write with this turn may not session:write here. A turn a session tool or a wake begins carries the person of the turn that began it, so a session opened by create_session acts as the person who asked for it and never as more.

A client's tool is reported and answered over the wire like any other call.

WhatGrant
Complete a call a client provideschat:tool
Confirm one, or answer a question a turn is waiting onchat:answer
Read a chat's turnschat:turns

member holds session:read and session:write, so the session tools answer it, and it may answer a tool call without a chat: grant of its own, since session:write covers every chat operation in the write group - SESSIONS.md. guest holds session:read and nothing else a session tool asks for, so it reads what is there and writes none of it; and because a chat's write group is its session's, a guest can never send a turn for a tool to run in.

Limits ​

The session tools are bounded the way the reference host bounds its own, with its numbers and its words, so a runaway agent stops where it stops there.

BoundedLimitThe refusal
Spawn depth3 links below the session a chain began inRefusing to create a session: recursion limit reached (max spawn depth 3). This session was itself created 3 level(s) deep.
Sessions50 per session tree in the last hour, made by create_session with independentRefusing to create more sessions: this session tree already used 50 of 50 allowed in the last 60 minutes. The limit applies to each session tree separately and frees up as older actions leave that window. Retry after <n> seconds, or turn off agent orchestration limits in settings.
Chats50 per session tree in the last hour, made by create_chat or by create_session with currentSessionthe same, with create more chats
Messages100 per session tree in the last hour, sent by send_messagethe same, with send more messages and 100 of 100

Depth is per session, and a chain rather than a tree: it is how many create links sit above the session making the call, so a session nothing started is at 0 and one the tool made inside another is one link deeper. A session three links down may not make one of its own, so a chain is four sessions long at most. The number in created 3 level(s) deep is the depth of the session that was refused, which is 3 or more.

The three counts are kept per session tree: a session nothing started, and everything the create tool grew from it, read through the parent links the store keeps. A tree spends its own budget, so one session's runaway agent leaves what another session may make untouched, and the refusal says the tree. The daemon's agentOrchestrationLimitsScope set to session keeps them per session, as the reference host keeps them, and the refusal says the session in its words: this session already used 50 of 50 ... The limit applies to each session separately. A count is taken against the session the tool runs in, and not against whatever the call makes or reaches. A message is counted in the tree of the session that sent it, however many sessions it is sent to. A chat or a session is counted in the tree whose tools made it. Counting the target instead would give one runaway a fresh hundred messages for every session it can name, and fifty more chats in each. Each is taken only once the thing it counts has happened, so a call refused for any other reason - a link naming a chat the session does not run, a turn whose person does not hold session:write - is not counted against the budget.

Each count is taken over a sliding hour, as the reference host's are, so a tree that reached a limit makes again as its oldest actions leave that hour, and the refusal says how many seconds that is away. The counts live in memory, as the reference host's do, so a restart begins them again.

A client turns the depth and the three counts off together with agentOrchestrationLimits: off in root config, VS Code's key for it, and on again with on, the default.

Only the tools are counted. A client's own createSession, and a message it sends with chat/turnStarted, are unbounded here: a count is a bound on what an agent may start on its own, and a person at a window is answerable for what they start.

See also ​

AHP.mdWhat a tool call is on the wire: chat/toolCall*, contributors and _meta.toolKind
RESOURCES.mdahp_resource, and the schemes a resource URI may name
TERMINALS.mdahp_terminals, and the shells it lists
PLUGINS.mdRegistering a tool, and the endpoint a backend without in-process tools is handed
BUILD-A-HOST.mdhostTools(), and building a host with a set of its own
COMPUTERS.mdThe three tools that declare advanced permission