Elevator pitch
What are you proposing to change?Allow an Agent to expose subagents that it creates when delegating work. Each subagent is represented by its own ACP session ID, so its messages, thoughts, plans, tool calls, and current work can be displayed independently from the parent session. The Agent associates a reusable child session with its parent through an upsert-style
subagent_update. Child events then flow automatically on the
same connection. Messages between sessions use session_message upserts,
which carry optional sender and recipient metadata, and
session_message_chunk notifications with normal message content, rather
than tool-call content.
The association is not a task or a one-shot lifetime. The parent can message
the same child again after earlier work finishes. One-shot children remain a
supported case: an Agent whose runtime creates a fresh worker for each
delegation announces a new child each time and never assigns it more work.
The protocol does not limit subagents to that pattern. Client-initiated session
mutations remain disabled unless explicitly advertised for that child; this
proposal initially defines only cancellation of current work.
Status quo
How do things work today and what problems does this cause? Why would we change things?ACP models a conversation as a single session update stream. Agents can run work concurrently internally, but there is no portable way to tell the Client that an update came from a subagent or to describe the relationship between the two sessions. Agents therefore have to flatten subagent activity into the parent session, hide it, or encode it in custom tool calls. Clients cannot reliably:
- render concurrent work as separate activity;
- associate plans, messages, and tool calls with the worker that produced them;
- distinguish a worker’s current title from the history of tasks sent to it;
- track nested subagents;
- cancel one subagent without cancelling the parent turn; or
- distinguish an idle child from one that is still processing work.
session/prompt, or future
queueing and steering methods, even when the underlying runtime has no way to
accept user input for that worker.
What we propose to do about it
What are you proposing to improve the situation?
Capability negotiation
In v1, add an optionalsubagents object to ClientCapabilities. A non-null
object means the Client understands the association updates, session-directed
messages and chunks, and restricted-session semantics in this RFD. The field
is optional and nullable, like adjacent capability objects. Omission or null
means the capability is not supported; {} means it is supported.
subagents. It may still use
subagents internally and present their results through the parent session.
There is no Agent-side capability. Subagent visibility flows only from Agent to
Client, so the Client capability alone is enough to prevent sending updates the
other side cannot understand; a Client learns that a particular Agent exposes
subagents by receiving the first update. In ACP v2, subagent updates are part
of the baseline session model and support is assumed rather than negotiated;
see Subagents in ACP v2.
Announcing and updating an association
subagent_update notifies the Client that the enclosing parent session has
created and owns another session. The first update for an unknown
update.sessionId announces that ownership association; later updates patch
its metadata without creating a new child or transferring ownership. This is
not a message to the child or merely a link to a related session.
When an Agent creates a subagent that it wants to expose, it MUST send the
announcing subagent_update before sending any request or notification bearing
the child’s session ID, including any live session message naming it as sender
or recipient. This includes permission, elicitation, filesystem, and terminal
requests where supported by the protocol version:
update.sessionId is required:
update.sessionIdis an opaqueSessionIdunique within the ACP connection. It identifies the same child across repeated delegations and MUST NOT be reused for an unrelated session.titleis an optional human-readable label for the child in its parent’s view. It need not be unique; the Client chooses a fallback if none is supplied.descriptionis an optional human-readable description of the child’s role or purpose. It is current display metadata, not a transcript of instructions.capabilitiesoptionally describes the Client-initiated session mutations permitted for this specific child session.cancelis an optional, nullable capability object: omitted ornullmeans unsupported, while{}advertises support. The object may include optional nullable_meta; omitted ornull_metameans no capability metadata. Ifcapabilitieswas never supplied, no session mutations are permitted.stateis an optional current-work snapshot, defined in Current work state. It is not a lifecycle outcome.cwdandadditionalDirectoriesoptionally report the child’s working directories, defined in Working directories._metaoptionally carries association metadata.
null clears the field, and a concrete value replaces the whole
previous field. The exception is a concrete _meta object in v2, which patches
the stored metadata by top-level key as defined by
v2 Metadata Patches. Clearing title or description removes the parent’s display
override. Clearing state leaves current activity unconfirmed, not idle.
Clearing cwd or additionalDirectories makes the child inherit that value
from its parent again.
Both capabilities: null and capabilities: {} disable Client-initiated
mutations.
The parent stream carries title and description so Clients can maintain a
subagent roster without collecting metadata from every child stream. These are
the parent’s labels for its child. The child’s ordinary session_info_update
can still describe its own conversation; it does not patch the parent
association’s display metadata. Updating either title does not rewrite the
content of earlier messages or operations.
The capabilities object is replaced as a whole, not patched recursively.
For example, capabilities: { "cancel": null } disables individual cancellation
in either version. Outer capabilities: null clears the entire capability set.
Capability advertisements inside that set are not patches: omitted or null
cancel means unsupported, not unchanged.
The outer sessionId establishes the immediate parent. This supports arbitrary
nesting without adding a second parent identifier. If a child spawns another
subagent, the Agent sends subagent_update with the child’s session ID as the
outer sessionId. A child has one immediate parent; the association cannot
reparent an existing session, refer to itself, or create a cycle.
This association describes ownership and management, not the set of sessions
that may interact with the child. Session-directed messages describe communication
and may go from a child back to its parent or to another known session
without changing the ownership tree.
Session identity follows the exposed conversation, not an individual provider
task, turn, or tool call. Completing a task does not create a new conversation.
Provider IDs can be used directly when their scope and lifetime meet the
identity requirements above; otherwise adapters can derive suitable ACP IDs
or maintain a mapping.
Reuse is allowed, not required. Many runtimes run a subagent once, often
synchronously inside a single tool call, and discard it. Such an Agent
announces a new child session for each run and never sends it another
assignment. This conforms to this RFD. The child keeps its last reported state
and history, and the Agent need not report anything further for it. See
Do subagents have to be reusable?
Announcement ordering also applies to short-lived work. Clients attribute
subsequent traffic to the announced child and apply its restricted-session
semantics; routing internals and presentation are implementation choices.
SDKs may tolerate non-conforming Agents by buffering early traffic, but that
tolerance does not replace the Agent’s announcement obligation.
Provider callbacks need not arrive in announcement order. If an interaction
arrives before the corresponding spawn metadata, the adapter can announce a
minimal association as soon as it knows the child’s identity and immediate
parent, or buffer the interaction until it can do so. It MUST NOT guess
parentage or temporarily announce a nested child under the root.
Exposure is optional. If the runtime cannot supply sufficient identity and
parentage without blocking the operation, the Agent may keep that operation
unexposed and represent it through the parent instead. It MUST NOT later
move an already-issued interaction to a different session. Once a child is
exposed, new interactions known to belong to that child’s work MUST use its
session ID rather than silently falling back to the root. A parent’s own
request for permission to create or delegate to a child remains a parent
interaction; it does not require announcing a child that does not yet exist.
The child’s execution context — its working directory, MCP servers, and
available tools — is chosen by the Agent and is not guaranteed to match the
parent’s. An Agent may, for example, run a child in a scratch directory or
without the parent’s MCP servers. Whatever the context, all child activity
flows through the same ACP connection: Agent-to-Client requests made on behalf
of a child are subject to the same Client capabilities and permission checks as
the parent’s, and the Agent MUST NOT use a child to circumvent restrictions
the Client imposed on the parent session. The Agent reports the parts of that
context a Client needs as described in
Child session context.
Automatic child event delivery
Announcement registers the child for routing on the existing ACP connection. The Agent then sends child events without waiting for another Client request. This RFD does not introducesession/attach, session/subscribe, or a
per-child event opt-in. Clients MUST NOT call session/load or
session/resume on a child to begin receiving its events.
Automatic delivery is an ACP contract, not an assumption about the provider
API. Depending on the provider, adapters may need to enable child output,
attach listeners, or restore subscriptions when reconnecting. A provider that
already delivers the necessary events requires no additional mechanism.
Existing session updates are addressed to the child in the normal way. For
example, the child can report its own conversation title separately from the
parent’s roster label:
Child session context
A child is not created throughsession/new, so the context a Client supplies
during ordinary session setup — working directory, additional directories, MCP
servers, and similar setup parameters — is not something the Client chose for
it. Directories default to the parent’s, but the Agent chooses the child’s
context and reports what the Client needs to
display the child and evaluate its requests. Where ACP already reports a piece
of session context for ordinary sessions, the child uses that same mechanism,
addressed to its own session ID:
In v1, an Agent that still uses legacy session modes may report a child’s
current mode with a child-addressed
current_mode_update. As with
configuration, reporting a mode does not authorize session/set_mode.
The Client cannot prompt a child, so a child’s slash commands are not
actionable. Agents need not send available_commands_update for a child, and
Clients MUST NOT offer commands from one as input to the child or its
parent.
This RFD does not report a child’s MCP servers, available tools, or system
prompt; see
Why not report MCP servers or tools for a child?
Working directories
subagent_update has two optional, nullable directory fields:
cwd: the child’s working directory, with the same meaning ascwdin ordinary session setup. It MUST be an absolute path.additionalDirectories: further directories the child works in, with the same meaning asadditionalDirectoriesin ordinary session setup. Each entry MUST be an absolute path.[]reports that the child has no additional directories.
null clears it, and a concrete value replaces it.
additionalDirectories is replaced as a whole array, not merged.
A field that has not been reported, or has been cleared, is inherited: the
child’s effective value is its immediate parent’s current effective value.
For a root session that is the value from ordinary session setup; for a nested
child it may itself be inherited. The two fields are inherited independently,
so a child moved to its own worktree that should not keep the parent’s
additional directories reports additionalDirectories: [] alongside its
cwd.
Because silence means “same as the parent”, an Agent MUST report a value
that differs from the parent’s before sending child traffic that depends on
it. An Agent that cannot determine a child’s directories SHOULD NOT expose
that child as sharing the parent’s workspace; it can keep the child’s
Client-mediated work unexposed, as described in
Announcing and updating an association.
A child running in its own worktree, for example, is announced as:
additionalDirectories session capability, which governs directories the
Client sends during session setup. In v1, the fields are covered by the
subagents Client capability like the rest of the association.
The Agent may change a reusable child’s directories, for example by giving
each new assignment a fresh worktree. It sends the updated values before
child traffic that depends on them. A change does not rewrite earlier
messages or tool calls, whose paths are already absolute. The reported
directories describe where the Agent placed the child; they do not ask the
Client to create, check out, or clean up anything. The Agent owns these
resources like any other child resource.
In v1, when a child’s terminal/create request omits cwd, the Client uses the
child’s effective cwd. Agents SHOULD still pass an explicit cwd on
child terminal requests.
A child’s root set
In v1, the root-set checks that filesystem and terminal requests apply to “the session’s root set” use, for a child, the child’s effective[cwd, ...additionalDirectories].
This is a report, not a grant the Client negotiates. Choosing where a child
runs is the Agent’s decision, just as Agent-local tools act without going
through the Client, and the root set confines the child’s work only where a
sandbox enforces it. Agents commonly place worktrees outside the project, for
example in a temporary directory or a per-user cache; reporting them keeps
the Client’s view of the child accurate and lets its Client-mediated requests
there pass the ordinary root-set check. A Client with a stricter policy for
directories outside the parent’s root set applies it to the requests it
receives, as it would for any session.
Agents SHOULD report the narrowest directories the child needs, such as
the worktree itself rather than its containing temporary directory or the
user’s home. A directory named only in model output or replayed history is
not part of a child’s root set. Agent-local tools remain subject to the
Agent’s own sandbox and permission policy, as for any session.
On reconnection, replayed directory values describe the recorded context.
Agents SHOULD send current values after reconnecting when they differ,
and MUST report a new value or null when a child’s directory no longer
exists.
Child configuration
Agents SHOULD report a child’s current configuration, when available, using the existingconfig_option_update notification with the child’s session ID.
Send an initial snapshot after announcing the child and a fresh snapshot when
its configuration changes, including changes between assignments to the same
child. Each notification contains the complete reported configuration, using
the existing v1 or
v2 config option
shapes. No configuration fields are added to the parent association.
Child configuration is not assumed to match the parent’s. If the Agent cannot
report it, the Client MUST NOT infer values from the parent or manufacture
configuration options.
When reporting configuration for a child that does not permit Client-initiated
configuration changes, Agents SHOULD restrict each select option’s
available values to its current value. This preserves the value’s
human-readable name without advertising choices the Client cannot select.
Clients SHOULD display singleton selects as labels or disabled selectors.
Boolean options have no available-values list and SHOULD be displayed
read-only under the same restriction.
Reporting config options does not authorize session/set_config_option, even
to set an option to its current value. This RFD defines only the cancel
mutation capability; enabling configuration changes requires a future,
explicit per-child capability. The Agent may still change the child’s
configuration itself and report a new snapshot.
On reconnection, historical configuration may remain visible but MUST NOT
be presented as confirmed current configuration. Agents SHOULD refresh
configuration for restored children when available. With history replay,
current configuration snapshots are sent after the successful load/resume
response; without replay, they may arrive before or after that response,
subject to announcement ordering.
Session-directed messages
Addsession_message and session_message_chunk variants to SessionUpdate
in both versions. These report a session’s outgoing or incoming messages to or
from another session, not responses addressed to the human user and not tool
calls. They use the existing session/update notification, not a new JSON-RPC
method or a new ContentBlock type.
The enclosing params.sessionId identifies the transcript being updated, not
necessarily the sender. Both variants have:
messageId: required, non-nullable, and unique within the enclosing session, just like other message IDs. It identifies the entry in that transcript, not a globally shared message or either participant.content: asession_messagemay replace the wholeContentBlock[];session_message_chunkrequires one non-nullableContentBlockto append. Normal text, images, audio, and resource content use their existing shapes._meta: optional and nullable. It updates message-scoped metadata onsession_message; it is chunk-scoped onsession_message_chunk.
session_message carries the message’s identity. A chunk carries only
content, as agent_message_chunk does:
senderSessionId: optional and nullable. Identifies the sending session when supplied.recipientSessionId: optional and nullable. Identifies the receiving session when supplied.senderMessageId: optional and nullable. On an incoming view, themessageIdof the same message’s outgoing view in the sender’s transcript.recipientMessageId: optional and nullable. On an outgoing view, themessageIdof the same message’s incoming view in the recipient’s transcript.
session_message for a
message when available, and when streaming a message whose participants are
known, send that upsert before its first chunk. Later upserts may omit them or
supply previously missing identities. These fields follow the same patch convention as content: omission leaves the stored value unchanged, null clears it, and a value replaces it.
senderMessageId and recipientMessageId link the two views of a message so
that a Client looking at either one can find the other, including when it has
not yet received or rendered the other transcript’s entry. They follow the same patch convention as the participant IDs. An Agent can therefore stream a view before it knows the other one’s
ID and supply the reference later with a metadata-only upsert. For
example, it can add recipientMessageId to the outgoing view once it has
reported the incoming view. Agents SHOULD supply each reference once they
know it. When both views carry references, they MUST name each other; a
Client that receives conflicting references treats the views as unlinked. A
reference does not indicate that the recipient finished processing the
message, and it does not establish ownership.
An Agent links two views only when it can establish that they are the same
message. A provider-supplied identifier shared by both sides is the strongest
evidence. Without one, the Agent chooses how to match them, for example by
recipient and content. Equal content alone can match the wrong entry when a
sender repeats a message, and delivery may transform the content. When the
Agent cannot establish the match, it leaves the views unlinked. Unlinked views
are valid, and Clients display each one in its own transcript.
Known participant information must remain consistent with the enclosing
transcript and earlier updates for that message. When both IDs are known,
they identify different sessions and the outer params.sessionId matches one
of them. Matching the sender represents the outgoing view; matching the
recipient represents the incoming view. Supplied live participant IDs refer
to sessions known through ordinary setup or a subagent_update announcement;
missing participants do not prevent displaying the message.
Whole-message updates use the same patch convention in both versions:
omitted content, _meta, participant IDs, and message references leave the stored value unchanged, null clears
the field, and concrete values replace the whole field. The exception is a
concrete _meta object in v2, which patches the stored metadata by top-level
key as defined by v2 Metadata Patches. content: []
also clears accumulated content. New messages start with empty content and no
metadata when those fields are omitted. Chunk _meta is not a message patch:
omitted or null means no metadata for that chunk.
Clients should render outgoing entries as “To …” with a recipient link and
incoming entries as “From …” with a sender link. Both use normal message
content while remaining distinct from user messages and answers to the user.
Neither needs a tool-call card, title, execution status, or a raw-input viewer.
When participant metadata is insufficient, Clients can use a generic
inter-session presentation and add links when identities arrive. Missing
metadata is not evidence of human authorship or confirmed receipt.
For example, the parent’s outgoing view of a message to its child is:
session/prompt because it received one. Neither
form acknowledges recipient processing or establishes new ownership. Actual
delivery is handled by the Agent.
The Agent reports each side only when it can observe that side: an outgoing
entry is not sufficient evidence to manufacture an incoming entry. The Agent
may report an outgoing view when it observes the send start or once the send
completes. It SHOULD NOT report an outgoing view for a send that it knows
failed before the message left the sender. Clients
MUST NOT infer receipt or synthesize an Agent-reported incoming transcript
entry solely from an outgoing entry. This does not prevent a clearly labeled
projection of outgoing messages in a cross-session view. The two views have
independently scoped message IDs, and Clients
MUST NOT assume equal IDs identify the same communication across sessions.
Only senderMessageId and recipientMessageId link the two views.
An incoming entry uses this message kind, not an additional ordinary user
message that would misattribute the communication. Rendering the same entry
in multiple views or quoting it in a summary is a presentation choice. Its
content reflects what was observed in that conversation; it need not match
the outgoing view byte for byte if the Agent transformed the content during
delivery. Neither view indicates that the recipient finished processing the
message.
Normal tool calls remain available for actual tools, including tools that
perform delegation or waiting. They report those operations’ inputs, outputs,
and outcomes. Agents do not need to invent a tool call to report a directed
message.
Referencing sessions from tool calls
Add asession variant to ToolCallContent in both versions. It is a
display-only reference to another session that the tool call operates on, in
the same way that terminal content references a terminal by ID:
sessionId is required. _meta is optional and nullable, and omitted or
null means no metadata. Clients use the reference to link the tool call to the
referenced session, for example to open the child’s conversation from the tool
call.
Delegation itself is a message. Agents SHOULD report the task a parent
sends a child as a session_message, as described in
Delegated tasks, and need not keep a tool call only to link
the parent to the child. Session references are for tool calls that remain
visible because they report a real operation, such as a spawn that needed
permission, a tool that waits on one or more children, or a batch spawn. A tool
call can reference several sessions, and a reusable child can be referenced by
many tool calls over its lifetime.
A referenced session follows the announcement rule above: it MUST be known
through ordinary session setup or a subagent_update announcement before a
tool call references it. A replayed reference to a session that is not
available follows the historical replay rules. A reference does not establish
ownership, grant Client-initiated controls, or report that the referenced
session received or finished any work. Its tool call’s status does not define
the referenced session’s lifetime or work state.
content on a tool call update replaces the whole array. An Agent that sends
new content for a tool call includes its session references again, or the
update removes them. An update that omits content leaves them in place.
A tool may address a session by a name or another provider handle instead of
its session ID. The Agent adds a reference only when it can resolve that
handle to a known session. It MUST NOT guess the session.
In v1, session content is covered by the subagents Client capability like
the rest of this RFD. In v2, receivers that do not recognize it preserve it as
unknown tool call content.
Delegated tasks
The task a parent gives a child, including the first one that starts the child’s work, is a session-directed message from the parent to the child. Agents SHOULD report it as the incoming view in the child’s transcript, asession_message with outer sessionId set to the child and
senderSessionId set to the parent, before the child’s response to it. If the
parent’s conversation also records the delegation, Agents SHOULD report the
outgoing view in the parent’s transcript and link the two views. Later
instructions to the same child use the same form.
Some providers record input in the child’s conversation without recording who
sent it, and a sibling or another session can also message the child. When the
Agent has no evidence of the sender, it reports the incoming view without
senderSessionId and MUST NOT assume the parent. It can supply the sender
in a later upsert. Clients display such an entry as a message from another
session, not as a user message.
Agents SHOULD NOT report this task as a user_message_chunk or a v2
user_message in the child’s transcript. A user message would attribute the
task to the human user. A session_message lets Clients render it as
communication between sessions, for example as “From Parent” at the top of the
child’s conversation. The description on subagent_update describes the
child’s role and is not a substitute for the task message.
A parent that delegates through a tool may also report the task in that tool
call’s input. The tool call reports the operation. The child’s
session_message reports what the child received.
Streaming
A directed message may start with a chunk on either side; a prior whole-message notification and complete participant metadata are not required:messageId. Participant metadata and references arrive in a metadata-only
upsert, before, between, or after the chunks, without repeating or replacing
the content:
session_message is an upsert: when it supplies a content array, that array
replaces all content accumulated for the message rather than appending. It can
also update metadata without resending content. Later chunks append to the
stored content. A chunk carries no participant IDs or references, and its
_meta does not patch message metadata.
Why session IDs appear in different places
These IDs answer different questions; they are not competing ways to route the same event:
For example, with parent
sess_parent and child sess_child_1:
- An association update has outer
sessionId: "sess_parent"andupdate.sessionId: "sess_child_1". This registers the parent-child relationship and the child’s capabilities. - An outgoing message has outer
sessionId: "sess_parent",senderSessionId: "sess_parent", andrecipientSessionId: "sess_child_1". This updates the parent’s history with a link to the child. - The received view has outer
sessionId: "sess_child_1"and the same sender and recipient. Its independent message ID identifies the entry in the child’s history, and itssenderMessageIdlinks back to the parent’s outgoing view, which can name it withrecipientMessageId. - The child’s own messages, plans, and tool calls have outer
sessionId: "sess_child_1". They update the child’s history, not the parent session. V2 work-state notifications use this child-addressed stream and are mirrored on the parent association; v1 reports work state through the association as described below. - A reply reverses the sender and recipient, not the ownership association. The Agent can report its outgoing view in the child’s transcript and its incoming view in the parent’s transcript.
Current work state
There is no separate subagent lifecycle enum. Work can start and stop many times within the same child session. Becoming idle, completing an operation, or cancelling current work does not permanently end the association. V2 uses the child’s ordinarystate_update notifications and their existing
foreground-work semantics. Agents SHOULD also mirror each reported child
state on the immediate parent’s subagent_update.state, so both the parent’s
roster and the child’s own stream carry the information.
These are two reports of one logical child state, not independent lifecycles.
Mirrored reports use the same StateUpdate snapshot and are idempotent: a
second copy does not represent additional work, completion, or token usage.
Copies of a transition precede later transitions on either path, so a delayed
mirror cannot overwrite newer state.
For example, a v2 child reports:
running: foreground work is in progress.requires_action: foreground work is blocked on user action.unknown: the Agent cannot currently determine foreground activity, for example after losing a remote child’s event feed while the ACP connection remains live. This is a nonterminal work state, not a work outcome.idle: foreground work is not in progress; the parent may assign more work. Optional nullablestopReasontakes v1’s existingStopReasonvalues pluserror, which carries an optional, nullable JSON-RPCerrorobject beside it, as in the v2 idlestate_update. Omitted ornullmeans no reason was reported. When the draft end-turn usage feature is supported, optional nullableusagefollows the same convention.- Each known state may contain optional nullable
_meta; omitted andnullboth mean no metadata for that snapshot.
running or idle state.
It SHOULD report requires_action while observed foreground work is
blocked on user action, and include stopReason when the reason for stopping
is known. This does not require reconstructing unobserved intermediate
transitions or guessing activity from provider callbacks. Cancellation uses
idle with stopReason: "cancelled", not a terminal child state. A failure
that ends the child’s foreground work uses idle with stopReason: "error"
and, when available, the failure in error. A v1 child has no prompt response
that could carry a JSON-RPC error, so this snapshot is the only place a v1
Client learns that the child’s work failed:
unknown using the existing state payload: v1 sends
subagent_update with "state": { "state": "unknown" } on the parent stream;
v2 sends state_update with "state": "unknown" on the child stream. Silence
alone is not evidence of lost observability. Observability is also lost when
the Agent itself stops tracking a child that may still be working, for
example when its runtime starts a new conversation and discards the
association while the child’s work continues. V2 mirrors this snapshot on the
parent association as well. The Client MUST NOT continue
presenting an earlier running or requires_action as confirmed live activity
after unknown, though it may retain that last-known value for context. A
subsequent running, requires_action, or idle replaces unknown for the
same child ID. unknown neither closes or cancels the child nor resolves
pending requests; it does not automatically revoke mutation capabilities.
If a control is no longer available, the Agent updates capabilities, in the
same subagent_update or a later one.
unknown also leaves the child’s unfinished tool calls, terminals, and v2
background list as last reported. The Agent MUST NOT invent a final
status for them, and it need not send further updates for work it can no
longer observe. The Client MUST NOT present them as confirmed live
activity or treat them as failed or cancelled. It may show them as
unconfirmed, as it does after connection loss. If the
Agent later learns that the work ended, it reports the final statuses and the
child’s idle state as usual.
The association’s state field is a replacement snapshot, not a nested patch:
omission leaves the previous snapshot unchanged, null clears it, and a
concrete object replaces it entirely. For example, { "state": "running" }
replaces an earlier idle snapshot and its stop reason. If no state has been
reported, or the snapshot is cleared, the Client has no confirmed current-work
state; neither announcement nor clearing implies running, idle, or
cancellation. A later state report on either supported path can supply the
current snapshot again. Unrelated association updates need not repeat it.
Unrecognized state objects preserve both their discriminator and payload. Values
beginning with _ are implementation-specific; other unknown values are
reserved for future ACP states. An unknown value does not imply completion or
closure. Unrecognized stop reasons on an idle snapshot are preserved the same
way in both versions. V2 keeps its open state and stop-reason handling, with
unknown added to ordinary StateUpdate.
Idle is not a successful task outcome or a declaration that the session has
been released. As in ordinary v2 sessions, background activity may still emit
updates while foreground work is idle. Operation results and failures are
reported through the relevant tool calls or other ordinary session output; the
error stop reason is only for a failure that ends the child’s foreground
work, and does not close the session or fail its parent.
Connection loss
Connection availability is separate from reported work state. After losing the ACP connection, the Client MUST NOT continue presenting a last-known running state as confirmed live activity, or infer that unfinished work succeeded, failed, or was cancelled. It may show the child as disconnected or its current activity as unknown while retaining recorded outcomes. This Client-local uncertainty differs from an Agent-reportedunknown state while
ACP is connected.
This is local presentation, not a synthesized wire state or a permanent change
to the session’s history. Fresh state reported after reconnecting is
authoritative and may be running again for the same child ID. Neither the
parent’s prompt response nor its transition to idle implies a lost connection
or a stopped child.
Reconnection and replay
The Client reconnects through the ordinary parent session. Direct childsession/load and session/resume are not supported by this proposal.
The Agent decides whether parent reconnection reattaches surviving child
runtimes, restores them, or only makes their recorded history available.
The protocol does not prescribe a runtime-recovery algorithm.
The replay entry point depends on the protocol version:
- ACP v1:
session/loadrequests history replay.session/resumerestores the parent without replaying history. - ACP v2:
session/resumewithreplayFrom: { "type": "start" }requests full history replay. Omitted ornullreplayFrommeans no history replay. v2 has nosession/loadmethod.
- Replayed child updates MUST preserve their original session identities, known parentage, and per-session ordering. Child announcements precede child traffic.
- If identity and parentage are recoverable but conversation content is not, the Agent may replay only the association. This is historical context, not evidence of a live runtime or a complete transcript.
- If an association cannot be reconstructed truthfully, the Agent MUST NOT guess its parent or issue traffic for an unannounced child. It may omit that child’s own replay.
- A recorded directed message in a known session’s history may still be replayed with its available participant metadata and content when the other participant’s association or history is unavailable. This applies to incoming as well as outgoing entries. A historical address is not an announcement. Clients MUST NOT infer ownership, live activity, or controls from it; they can show the other participant as unavailable until its session becomes known. Agents MUST NOT invent either participant ID or reinterpret the content as an ordinary message to or from the human user.
- The Agent SHOULD make known gaps apparent through a supported advisory mechanism rather than imply that child replay is complete. Missing history is not evidence that a child never existed, stopped, failed, or was cancelled.
- All history selected for this replay MUST be sent before the response. The response completes the best-effort child replay; it does not certify that every child record was available. Delayed historical child state must not be sent after that boundary as if it were fresh activity.
subagents receive ordinary parent history, not unsupported child
updates or session-directed message variants. Parent-level results and
summaries can still be replayed.
Reconnection separates routing context from current work state and controls.
When loading or resuming a parent, the Client MUST invalidate cached live
work state and mutation authorization for its children. Historical data may
remain visible, but effective child capabilities start empty and current work
state starts unconfirmed. Historical capability patches never populate that
effective set; omitting a current capability field cannot revive an earlier
cancel grant. The Client MUST NOT enable child mutations until the
load/resume succeeds and a current capability grants them. If the operation
fails, current state and authorization received during that attempt are
invalidated.
The Client need not retain an earlier session tree. Before using a child in
live traffic, the Agent establishes the child’s complete ancestor chain in
parent-before-child order during this load/resume. Replayed associations can
provide that routing context and need not be repeated merely because the
response has been sent. Missing intermediate ancestors still need announcing,
even if their runtimes were not restored; an association establishes identity,
not a live runtime.
When replay is requested, the successful response is the freshness boundary
for child work-state and mutation-capability snapshots:
- Before the response, these snapshots are historical. They MUST NOT authorize Client-initiated mutations or establish confirmed current activity.
- Current snapshots are sent after the response. Normal association and state updates suffice; no extra announcement phase is needed for already-known associations. An Agent can omit capabilities when none are enabled, because the effective set starts empty rather than inheriting historical grants.
root → coordinator → worker was previously announced
and only worker resumes, the Agent establishes coordinator under root,
then worker under coordinator, before sending fresh worker traffic.
Replay may already have supplied these associations. Without replay they can
be announced before the resume response. A routing-only ancestor can omit
capabilities and work state; it has no enabled controls or confirmed activity.
The worker is not reparented directly under root.
The Agent SHOULD report current child work state when it can determine it.
Historical running states are not proof of current activity. An Agent MUST
NOT manufacture a failure, cancellation, or permanent orphan outcome solely
because the persisted history lacks a final update or a runtime could not be
restored. A history-only child remains displayable, with unconfirmed current
activity and no enabled mutations.
Restricted session methods
Client-initiated methods that modify a child session or its runtime are disabled unless explicitly enabled by that child’s capabilities. This is an allowlist, not a list of individual prohibited methods: Clients MUST NOT invoke an unadvertised mutation, including a future method, merely because the Agent supports it for ordinary sessions. Mutations include prompting, queueing, steering, cancellation, closing, deletion, mode or configuration changes, and forking. Loading or resuming also changes runtime attachment and is not a read-only history query. The only mutation capability defined by this RFD iscancel; direct child load, resume,
and close are therefore not allowed. A child’s tool call or terminal that
advertises its own capabilities.cancel enables tool_call/cancel or
terminal/cancel for that item, sent with the child’s sessionId, whether or
not the child advertises cancel. It enables nothing else on the child. The parent Agent’s own communication with
its children is not a Client-initiated mutation and is not restricted by these
capabilities.
Read-only operations retain their normal protocol semantics and capability
requirements; this RFD does not grant access to additional sessions or define
a new history-query method. Responses to Agent-to-Client requests are not
Client-initiated session mutations and MUST still be delivered.
Restricted children are discovered through parent associations rather than
ordinary standalone session/list entries. This avoids offering load, resume,
or prompt controls that are not available for them.
If forking a parent copies child history, the Agent MUST consistently remap
the copied session IDs in both message participant fields and in session
tool call content, including references to the forked root. Each copied child still has one parent and a distinct
identity. Participants outside the copied tree do not become part of the copy;
unavailable participants follow the historical replay rules above. Copying
history does not itself create live child runtimes. Parent deletion keeps its
ordinary history-management semantics; it is not a substitute for closing
active sessions.
Cancellation and resource ownership
Ifcapabilities.cancel is a non-null object, the Client may send the existing
session/cancel notification with the child’s session ID. This cancels current
work in that child, not its parent or siblings. The per-child capability
controls individual Client cancellation; it does not prevent the Agent from
cancelling its own delegated work.
Cancelling a session’s current work, whether a parent’s or an individually
cancelled child’s, also cancels the descendant work that the cancelled work is
waiting on, such as a child whose result a pending tool call needs. Whether
other descendants keep running is up to the Agent: a background child that the
cancelled work did not wait on may continue, as it would have if that work had
ended normally. The Client learns which descendants stopped from their reported
work state, not from the cancellation itself.
An omitted or null per-child cancel capability does not waive this cascade
from an ancestor. Adapters should verify whether the provider already supplies
it or needs additional descendant cancellation. Advertise individual
cancellation only when the adapter can target the child’s current work
correctly in that runtime mode.
Cancellation retains ordinary session semantics: abort the affected work and
send its pending updates before confirming cancellation. A child confirms
cancellation with idle and stopReason: "cancelled" through its v1 state
snapshot or ordinary v2 notification, mirrored on the v2 parent association.
There is no Client-issued child
session/prompt response to wait for. A cancellation racing with already-ended
work does not rewrite that work’s outcome.
Acceptance of a provider stop/interrupt command is not, by itself, evidence that
the work has stopped. The Agent reports cancellation from actual work-state or
completion evidence, not merely from a successful command acknowledgement. If
activity becomes unobservable, it reports unknown rather than inventing a
cancelled outcome.
The Client SHOULD optimistically mark the cancelled session’s unfinished
tool calls as cancelled, as specified for v1 prompt cancellation
and v2 work cancellation, and a
descendant’s once that descendant reports idle with stopReason: "cancelled".
In v2, it SHOULD skip tool calls in that session’s latest
background list and tool calls that reference a
listed terminal, since that work keeps running.
It SHOULD still accept subsequent Agent updates for those calls. This local
presentation is not a new v1 wire-level tool status.
Cancellation does not close the child session or prevent the parent from
assigning later work to it. Completing or failing one operation also does not
close the session or automatically fail its parent.
The Agent owns child resources. Closing the ordinary parent session cancels
all of its active descendant work, background children included, and releases
the associated active resources under the existing close contract. It does not erase child history or require a new
terminal-state notification. Whether child runtimes can later be restored is
left to the Agent.
Pending permission and elicitation requests
Optimistically updating a tool’s display does not resolve its JSON-RPC requests. When the Client cancels a session’s current work, it MUST respond to that session’s pendingsession/request_permission requests with the
cancelled outcome, following the existing cancellation rule. It SHOULD
dismiss that session’s elicitation controls and respond with the cancel
action.
The cancellation does not by itself resolve a descendant’s pending requests.
Once that descendant reports idle with stopReason: "cancelled", the Client
cleans them up as if it had cancelled the descendant directly; until then the
user can still answer them. A descendant that keeps running therefore keeps its
requests, and the Client never denies a tool use of work that continues.
Closing the parent ends the work of all of its descendants, so the Client
cleans up their requests together with the parent’s.
Generic $/cancel_request remains optional.
Agents may use it to cancel an abandoned child request, including when the
Agent stops work on its own. Advertising subagent support does not add a
requirement to implement that generic mechanism.
If the Client learns that an interactive request’s associated work has ended
and the request no longer applies, it SHOULD dismiss the control and send
the corresponding cancellation response. Idle alone is not a cancellation of
unrelated background requests. Requests remain attributed to their original
child session and MUST NOT move to the parent or a later operation.
A late response still resolves its original request, but the Agent MUST NOT
use it to restart cancelled or otherwise abandoned work, or to authorize a new
assignment to the same child. A request MUST NOT receive more than one
response. There is no new child-termination barrier that requires every
outstanding request in a reusable session to resolve before reporting work
state.
Cost reporting without inferred aggregation
Exposing child sessions does not make existingusage_update.cost values
exclusive or additive. Cost reporting remains optional. An Agent may continue
reporting the provider’s cumulative cost for the parent session even when it
includes child work or internal helper calls. Exposing subagents does not
require changing that accounting scope, producing an exclusive breakdown, or
discarding a useful inclusive total.
Child costs remain optional and may overlap the parent’s reported cost.
Missing child cost is unknown, not zero. Agents MUST NOT fabricate an
exclusive parent or child amount from incomplete token counts or an aggregate
total.
Clients MUST NOT infer a combined tree total by adding parent and child
values, or infer exclusive costs by subtracting them, unless a separate
accounting contract establishes their coverage and how any overlap is handled.
Clients may display each session’s latest reported cumulative value without
claiming an accounting scope that has not been established. Successive
cumulative updates and repeated messages to a session are not additional costs.
For example, a parent’s reported USD 1.20 may already include a child’s USD
0.30. Both values can be shown, but the Client must not synthesize USD 1.50 as
the combined cost from the session tree alone. A missing cost for another child
does not invalidate the provider’s USD 1.20 report.
This RFD adds no cost-scope field or accounting negotiation. A future usage
extension can define explicit inclusive/exclusive breakdowns where available.
Amounts in different currencies must not be added without conversion under
such an accounting contract. Context-window usage remains local to each session
and is not summed across the tree.
Subagents in ACP v2
The two versions use the same ownership and directed-message model, with these differences:- Baseline support, no capability. v2 Clients MUST understand
subagent_update, apply child operation restrictions, understand directed message snapshots/chunks, and attribute requests and notifications to the correct sessions. Clients may choose not to render a dedicated subagent UI, but cannot merely ignore the announcement as an unknown update. ThesubagentsClient capability is v1-only. - Mirrored child work state. V2 keeps
state_updateon the child’s own stream and mirrors the same state in the parent association. V1 uses only the parent-carried snapshot. Both describe the child’s ordinary foreground work, not a separate subagent lifecycle. - Resume with replay. Available child history is replayed best-effort
through
session/resumewithreplayFrom: { "type": "start" }, notsession/load. Each child that the Agent announces during that request gets its ownreplay_completeupdate on its ownsessionId, after its last replayed update. Live updates and requests for the child follow it. See the v2 Replay Complete Signal RFD. - Directories are informational. v2 removes the Client filesystem and
terminal methods, so a child’s
cwdandadditionalDirectorieshave no Client-side root-set check or terminal default to drive. They use the same fields and inheritance rules and tell the Client where the child runs; the root set remains a boundary for the Agent’s own tools, as for any v2 session.
session/prompt at acceptance and reports foreground work through
state_update. Neither that response nor the parent’s transition to idle
implies that every child has finished.
Shiny future
How will things play out once this feature exists?A Client can render a parent session with a live tree of concurrent workers. Selecting a worker shows its own plan, messages, tool calls, and status without mixing them into the main transcript. Where supported, the user can stop a runaway worker without discarding useful work from its siblings or parent. Agents that do not implement subagents remain unchanged. Agents that use subagents internally but do not want to expose them can continue flattening or summarizing their output in the parent session.
Implementation details and plan
Tell me more about your implementation. What is your detailed implementation plan?This is a project rollout and validation plan, not a requirement for every implementation to use the named SDKs, providers, or integration techniques.
- Add the v1
SubagentCapabilitiesmarker and theSubagentUpdateassociation andSubagentSessionCapabilitiestypes in both versions, including the optionalcwdandadditionalDirectoriesfields onSubagentUpdate. - Add
SessionMessageandSessionMessageChunkupdate variants in both versions, using normal content, with optional sender and recipient session metadata and message references onSessionMessagefor incoming and outgoing views. Add asessionvariant toToolCallContentin both versions. - Mirror the v2
StateUpdatepayload in v1 and embed it in the v1 association update. In v2, mirror ordinary child-session state notifications in the parent association as well. - Keep the v1 additions behind
unstable_subagentsand regenerate schemas and reference documentation. The v2 additions are part of the v2 schema and need onlyunstable_protocol_v2. - Ship SDK releases that carry the draft
subagentscapability and update types — or at minimum preserve them when deserializing and re-serializing — before adapter rollout. SDKs that strip the draft fields force temporary out-of-band negotiation bridges such as_meta-scoped capability flags. Such bridges are compatibility shims, not an alternative protocol, and are retired once SDK support ships. SDK preservation of the draft fields is an explicit prerequisite for the validation step below. - Support the freshness boundary for replayed child state and capabilities. For example, an SDK could offer an explicit responder or after-response hook for publishing current snapshots after replay completes. Non-replaying resume does not need such a hook, and other live child traffic need not be globally buffered. Any ordering mechanism should provide the required ordering rather than rely on arbitrary delays.
- Update example Clients to route child events automatically, retain reusable session identities, and render session-directed messages distinctly from user-facing responses and tool calls. Enable or restore provider-side child delivery where that provider requires it.
- Establish stable provider-to-ACP session identity, truthful interaction attribution, and cancellation of current work. Retain or reconstruct child history where practical, but do not make complete child transcripts a prerequisite for live exposure.
- Exercise the validation scenarios with captured native events, including reordered callbacks and reconnects.
- Validate this revision against both Claude Code and Codex adapter mappings and a Client with concurrent-session UI before stabilization. Schema tests and the existence of provider APIs alone are not end-to-end validation.
Provider integration notes
The following inspected versions provide concrete inputs for adapters; they are not a claim that all versions or runtime modes have equivalent behavior, or that the existing adapters already implement this revision.- Claude Agent SDK 0.3.280: the published declarations
expose forwarded child messages through
forwardSubagentTextandparent_tool_use_id, task lifecycle events, permissionagentIDandtoolUseID,stopTask(taskId), and child transcript APIs such aslistSubagentsandgetSubagentMessages. Adapters may need to correlate these IDs where their scopes differ from ACP conversation identity. Permission callbacks can precede spawn metadata. Query cost totals may already include child and internal work. - Codex 0.156.1: thread/turn identifiers, thread status and approval/input
wait flags,
turn/interrupt(threadId, turnId), and thread history support the corresponding mapping. The server attaches listeners to newly created threads; adapters should also check delivery for existing threads on reconnect. Per-thread token usage is not a monetary cost report.
requires_action, and acknowledging an interrupt is not a completed
cancellation. Likewise, a decoder preserving a new field is not sufficient
implementation of child routing or the replay freshness boundary.
Validation scenarios
- Send two assignments to the same child conversation: keep its ACP session
ID, use distinct message IDs, and allow
running → idle → running. - Mirror v2 child work state to the parent association. Verify that both streams converge on the same state and duplicate snapshots do not count work completion or usage twice.
- Report child configuration after announcement, then change its model or
reasoning level between assignments. Verify that complete snapshots replace
the displayed configuration, missing values are not inherited from the parent,
singleton selects and boolean options remain read-only, and reporting options
never enables
session/set_config_option. On reconnection, distinguish historical configuration from fresh snapshots. - Announce one child in the parent’s directory and another in its own
worktree, then move the first to a new worktree between assignments. Verify
that the first child inherits the parent’s directories until it moves,
additionalDirectories: []stops inheriting the parent’s extra directories, in v1 a terminal request withoutcwduses the child’s effectivecwdand Client-mediated requests inside a worktree in a temporary directory pass the child’s root-set check without extending the parent’s or a sibling’s root set. - Send messages to a parent and a sibling from a child’s stream. Verify that each message has the correct sender, recipient, and content, and that the parent-child tree does not change.
- Report the received view in the recipient’s transcript, with its own message ID and a link to the sender. Verify that neither the Client nor the adapter invents an Agent-reported incoming entry from outgoing traffic alone. Clearly labeled UI projections remain possible, without duplicating the communication as an ordinary user message.
- Stream an incoming view before its outgoing view’s ID is known, then supply
senderMessageIdon the incoming view andrecipientMessageIdon the outgoing view with metadata-only upserts. Verify that a Client can go from either view to the other, including before it has rendered the other transcript, and that it does not link views by equal IDs alone. - Start a directed message with a chunk, interleave another message, and replace accumulated content with a whole snapshot. Start without participant metadata, add it later with a metadata-only upsert without replacing content, and keep streaming chunks. Verify that missing metadata remains an inter-session message rather than being attributed to a human or guessed sender.
- Finish reporting a message before the child finishes processing it, and keep receiving child events after the parent’s prompt response.
- Deliver a child permission callback before spawn metadata, including a nested child: verify early truthful announcement or buffering, no guessed parent, and the unexposed-operation fallback when necessary.
- Cancel a child and then an ancestor while interactions are pending: verify native work is targeted correctly, acknowledgement is not confused with completion, and late responses cannot authorize later assignments.
- Cancel a parent while a child it waits on and a background child it does not
wait on both have pending requests: verify that the waited-on child stops and
reports a cancelled
idlebefore the Client cleans up its requests, and that the background child may keep running with its request still answerable. - Replay after restarting the adapter with complete, partial, and unavailable child history. Preserve IDs and relationships for whatever is replayed, preserve historical senders and recipients without guessing ancestry, and do not infer an outcome from a gap. Verify that current work state and controls depend on post-response snapshots, not historical capabilities. An omitted current capability set must leave controls disabled.
- Issue a live child request during replay after announcing its ancestor chain. Verify ordinary request handling without enabling historical controls, and that replay cannot overwrite or duplicate newer live transcript content.
- Resume without history, including current announcements before the response. Verify that controls remain disabled until success, failed resume invalidates attempted live state, and routing-only ancestors are retained. No additional Client attach/subscribe request or universal after-response hook is required.
- Lose a child’s activity feed while ACP stays connected: report
unknownwithout inventing an outcome, and accept later state for the same child. - Report an inclusive parent cost with missing or overlapping child costs: retain the parent report and do not synthesize a tree total or exclusive shares from the message traffic.
Frequently asked questions
What questions have arisen over the course of authoring this document or during subsequent discussions?
Is a subagent session read-only?
Only from the Client’s conversational perspective. The Client cannot prompt, queue, or steer it. The subagent may still read and write files, run terminal commands, and request permissions using the capabilities already negotiated on the ACP connection. Making all subagents workspace-read-only would prevent many coding use cases and should instead be an agent policy or a future sandbox capability.Do Claude Code and Codex support stopping individual subagents?
Current Claude Code and Codex orchestration surfaces both have concepts for stopping or closing an individual worker. That does not mean every version, runtime mode, or ACP adapter can implement it, and other Agents may only support coarse parent-turn cancellation. Individual cancellation must therefore be a capability, not a protocol requirement implied by subagent support. The proposal uses a per-child capability because support may even vary within one Agent: a local worker might be interruptible while a delegated remote job is not.Why reuse session/cancel instead of adding subagent/stop?
Once a child has a real ACP session ID, the existing session-scoped lifecycle
methods already identify it unambiguously. Reusing them avoids two ways to
perform the same operation. The subagent capability narrows where those methods
are valid and defines the required cascade behavior.
Why doesn’t cancelling a parent stop every descendant?
Agents already differ here. In Claude Code, a background subagent keeps running after the user interrupts the turn that started it: it runs in the background so that it can outlive that turn. A required full cascade would make such an Agent stop background work on every cancellation, which its users do not expect. Descendant work that the cancelled work is waiting on has no purpose once that work is cancelled, so it must still stop. Because the Client cannot tell in advance which descendants will stop, it acts on each one only after that child reports a cancelledidle. A Client that
wants to stop a background child uses that child’s cancel capability when the
Agent advertises it.
Why one subagent_update type instead of separate spawn and state notifications?
The upsert announces an association and patches its capabilities without a
separate create method. It also carries the parent’s title and description
for the child. Ordinary session updates handle the child’s own conversation
and, in v2, foreground work. Both versions carry a state snapshot on the
association so the parent has roster information; v2 mirrors the child’s
ordinary notification. Neither version needs a second, permanently terminal
subagent lifecycle.
Why are there two sessionId fields in an association update?
The fields have different scopes: params.sessionId is the parent whose stream
receives the association update, while params.update.sessionId is the child
being associated with it. The tagged SessionUpdate payload is flattened
inside update, not into params, so these names do not collide. Directed
messages instead use senderSessionId and recipientSessionId for their
participants; the outer sessionId chooses the transcript being updated.
See Why session IDs appear in different places.
Why is cancel the only per-child capability?
Stopping current work is a useful minimal control and maps to
session/cancel. The Agent retains ownership of child resources; the Client
does not need to attach, subscribe, or close each child merely to display it.
The capability object leaves room for future controls without making those
operations available implicitly.
Why not subscribe to individual child streams?
Selective delivery could reduce bandwidth and processing costs for large session trees, so it is worth revisiting. Clients can already choose which sessions to render or expand without changing event delivery. A future subscription proposal would need to distinguish transcript delivery from ownership announcements, capabilities, current work state, cancellation, and permission or elicitation requests. Hiding a transcript must not strand an interaction or make an unobserved child appear stopped. It would also need defined catch-up or snapshot behavior when a Client attaches, including gaps in retained history and ordering relative to live events. This RFD keeps automatic delivery and does not add attach/subscribe methods.Why remove the terminal disconnected state?
The earlier draft correctly avoided inventing success, failure, or
cancellation when the outcome was unknown. It unnecessarily made that
uncertainty a permanent property of the child session. Connection availability
and recorded operation outcomes are now separate: Clients show unconfirmed
activity as unknown, and Agents may restore the same child without rewriting
history or pretending to know what happened while disconnected.
Why not model a subagent as a tool call?
The two represent different things. A session owns a conversation; a tool call reports execution of a tool. An outgoing or incoming message should be renderable as ordinary content with a recipient or sender link, without requiring a tool-call card or a raw-input viewer. The directed-message updates provide that representation and support streaming. Actual tool operations can still be reported normally and can link to the sessions they operate on withsession content, but their status does not define a child’s lifetime or
acknowledge that it processed a message.
Why can both views of a message reference each other?
A Client showing one transcript may need the other view’s entry without having received or rendered it, for example to open a child’s conversation at the message a parent sent. One reference would force that Client to search the other transcript first, so each view can name the other. Both are optional and can arrive in later updates, because an Agent may start streaming a view before it knows the other one’s ID. Message IDs stay scoped to their transcript; requiring both views to share an ID would instead force the Agent to know the link before sending the first update of either view.Why do chunks carry only content?
A message’s identity, its participants and references, lives in one place: thesession_message upsert, as message-level fields do for
agent_message, whose chunks also carry only content. Identity on chunks
would give the same fields two sources that can disagree, and would make a
chunk patch message state even though its _meta does not. An Agent that
learns the identity while streaming sends one metadata-only upsert, which
needs no content.
How does a Client get a subagent’s initial task?
From the incomingsession_message in the child’s transcript, as described in
Delegated tasks. The first task is not a special case: it
is the first session-directed message the child receives. Reporting it as a
user message would attribute it to the human user, and storing it on
subagent_update would conflate the child’s current display metadata with
its message history.
Why report directories on the association rather than session_info_update?
For ordinary sessions the Client chooses cwd and additionalDirectories
during setup, and session_info_update deliberately omits them. Keeping that
true for every session avoids a session_info_update field that is valid only
for children. Like title and description, the directories are also roster
information: a Client can show where each child runs from the parent’s stream
without collecting it from every child’s stream. Configuration differs because
config_option_update already reports it for every session.
Why not report MCP servers or tools for a child?
The Client supplies MCP server definitions for ordinary sessions, and those definitions can carry credentials in environment variables and headers. A child’s tools may also come from sources inside the Agent that the Client cannot name. ACP does not report the available tools of an ordinary session either. Tool calls already show which tools a child actually uses. Reporting tool or server availability would be a separate proposal covering all sessions, not only children. The same applies to the child’s system prompt: what the parent asks of a child appears in the session messages it sends.Why not allow prompting or steering a subagent?
The parent Agent can message or reuse a child according to its runtime’s semantics. This proposal does not expose the same control to the Client: direct user intervention may be unsupported or interfere with orchestration. A future per-child capability could opt into it without assuming every Agent can implement it. Some runtimes already let the user message a child directly. Claude Code, for example, accepts input on a subagent’s transcript, and OpenCode’s runtime can prompt a child session. The expected follow-up is a per-childprompt
capability in subagent_update.capabilities, enabling session/prompt with
the child’s session ID.
Do subagents have to be reusable?
No. A one-shot child, created for one delegation and never given more work, is a valid and common use of this RFD. The Agent announces it, reports the incoming task and the child’s traffic, and reportsidle when it finishes.
Later updates are optional. The RFD defines reusable sessions so that it does
not limit the protocol to one-shot children. Agents whose runtimes reuse
workers can keep a child’s identity across assignments, and Agents whose
runtimes do not can create a new child for each run.
What do the association’s title and description describe?
They describe the child in its parent’s view, so Clients can show multiple children directly from parent-session updates.title is a short display
label; description supplies its role or purpose. The child’s conversation may
also have an ordinary session title, which does not replace these parent-owned
labels.
These fields are not the contents of the latest message sent to the child.
Messages and operations retain their own content and history even when the
parent revises the child’s display metadata. There is no session-wide task
field that rewrites earlier instructions.
Is session/fork sufficient for subagents?
No. Forking is initiated by the Client and creates a normal session derived
from existing context. This RFD covers Agent-created sessions with a parent
relationship and restricted Client controls. An Agent may use a fork
internally to implement a subagent, but the wire semantics are different.
What alternative approaches did you consider, and why did you settle on this one?
- Put a
subagentIdon every existing update. This would duplicate routing information and require changing every update type. - Use only tool-call parent/child relationships. This cannot represent nested, independent session streams cleanly.
- Treat children as unrestricted ACP sessions. This advertises methods that many agent-created workers cannot accept.
- Add dedicated stop and close methods. Existing session methods already have the desired addressing and lifecycle semantics.
- Use separate
subagent_spawnedandsubagent_state_updatenotifications, as an earlier draft of this RFD did. Merged into one upsert-style update to match the v2 entity pattern. - Treat every completed task as the end of a child session. Rejected because Agents can reuse a child for later work.
- Require child subscriptions or independent load/resume. Deferred: association already gives the Client automatic event delivery on the parent connection.
Revision history
-
2026-11-13: The v2 schema no longer gates subagents behind
unstable_subagents. The v2 protocol guides describe them. v1 keeps the feature flag. - 2026-11-13: Moved to Preview.
-
2026-11-13: In v2, each child announced during a resume with replay gets
its own
replay_completeupdate, following the v2 Replay Complete Signal RFD. -
2026-11-12: Clarified, from adapter implementation
feedback, that an incoming view omits
senderSessionIdwhen the sender is not known, when an Agent links two views or leaves them unlinked, and when it reports an outgoing view. An Agent that stops tracking a child that may still be working reportsunknown, which leaves the child’s unfinished tool calls, terminals, andbackgroundlist as last reported. Session references are resent with replaced tool call content and are added only for sessions the Agent can resolve. -
2026-11-09: Stated that one-shot children, announced for a single
delegation and never reused, conform to this RFD. Named a per-child
promptcapability as the expected way to let Clients prompt a child in the future. Added asessionvariant toToolCallContentso a tool call that spawns, waits on, or otherwise operates on a session can link to it. -
2026-11-09: Recommended reporting the task a parent gives a child, including
the first one, as an incoming
session_messagein the child’s transcript rather than a user message, so Clients can render it as communication between sessions. -
2026-10-30: In v2, optimistic cancellation skips tool calls in the session’s
backgroundlist, following the v2 Background Work RFD. - 2026-10-29: Removed the JSON-RPC batch rules, since ACP v2 no longer uses batches.
-
2026-10-29: Added optional
cwdandadditionalDirectoriestosubagent_updateso an Agent can report a child that runs outside its parent’s workspace, such as in its own worktree. Unreported directories are inherited from the parent, and a child’s root set is its own effective directories. Summarized how the rest of a child’s session context is reported, and why MCP servers, tools, and system prompts are not. -
2026-10-27: Participant IDs and message references now follow the standard
patch convention:
nullclears a stored value instead of leaving it unchanged. -
2026-10-22: Added optional
senderMessageIdon the incoming view andrecipientMessageIdon the outgoing view of a session-directed message, so a Client can go from either view to the other without inferring a link from equal IDs. Either can be supplied after the view starts streaming. Moved the participant IDs offsession_message_chunk: chunks carry only content, and a message’s participants and references arrive onsession_messageupserts. -
2026-10-13: In v2, a concrete top-level
_metaobject onsubagent_updateandsession_messagepatches stored metadata by key, per v2 Metadata Patches. v1 still replaces the whole object. -
2026-10-05: Limited the cancellation cascade to the descendant work that the
cancelled work waits on, leaving other descendants, such as background
children, to the Agent. Clients clean up a descendant’s pending requests and
tool calls only once it reports a cancelled
idle. Closing a parent still ends all descendant work. -
2026-10-01: Added the
errorstop reason, with an optional JSON-RPCerror, to the v1 idle snapshot so it keeps the v2 shape and a v1 child can report that its work failed, and preserved unrecognized v1 stop reasons as v2 does. v1’s prompt response is unchanged. - 2026-09-24: Separated reusable session associations from tool-call operations, added whole and streamed session-directed messages with incoming and outgoing views, and distinguished parent-owned display metadata from the child’s ordinary conversation metadata. Replaced the terminal lifecycle enum with v1 work-state snapshots and normal v2 state notifications. Made event delivery automatic, left runtime restoration to the Agent, retained ordinary optimistic cancellation without requiring generic request cancellation, and added nonterminal unknown activity and routing-only ancestor recovery. Clarified the distinct session ID roles, incorporated provider integration and validation requirements, and preserved provider-reported costs without inferred cross-session aggregation. Aligned cancellation with object capabilities, allowed messages back to parents and other known sessions, and made child-history replay explicitly best-effort while retaining live identity and ordering guarantees.
-
2026-09-15: Merged
subagent_spawnedandsubagent_state_updateinto a single upsert-stylesubagent_updatefollowing the v2 entity pattern, madename,task, andcapabilitiesoptional, added an explicitrunningstate, removed the Agent-side capability and the per-childclosecapability, stopped implying that a child inherits the parent’s execution context, and allowed SDKs to buffer updates for unannounced children. Added the samesubagent_updateto the v2 schema: capability-free, with v2 patch semantics and an open state enum. -
2026-08-31: Defined the Client-local
disconnectedstate for unknown outcomes on a live connection, required pending permission and elicitation requests to resolve before the terminal lifecycle update with defined race handling, and made SDK preservation of the draft fields an explicit rollout prerequisite. - 2026-08-25: Defined child reconnection, replay, orphan, and disconnected lifecycle semantics.
- 2026-08-19: Initial draft.