Elevator pitch
What are you proposing to change?ACP v2 should provide a standard way for Agents to report display-only terminal output. A tool call can reference an Agent-owned terminal by
terminalId, while separate session updates describe that terminal, replace its stored output for replay, append live output bytes, and report its exit status.
This is a presentation surface, not a Client execution API. It does not let the Client write input, resize, interrupt, kill, wait on, or release the Agent’s process.
Status quo
How do things work today and what problems does this cause? Why would we change things?ACP v1 terminal content refers to a terminal created and owned by the Client through
terminal/create. That ownership model does not fit Agents that execute commands in their own sandbox and only need to report the resulting output.
Several implementations therefore use the v1 terminal content shape with implementation-specific _meta fields for terminal creation, output, and exit. This has demonstrated demand for a rich terminal display, but it leaves capability negotiation, ownership, replay, byte handling, and update ordering outside the protocol.
ACP v2 removes the Client-owned terminal execution surface. It still needs a distinct, standard model for byte-faithful command output that can be streamed live and reconstructed during session replay.
What we propose to do about it
What are you proposing to improve the situation?
Ownership and identity
The Agent owns every v2 display terminal and generates itsterminalId. The ID is unique within a session, remains stable for the terminal’s lifetime, and MUST NOT be reused for another terminal in that session.
terminalId and toolCallId are separate domains. An Agent may choose the same underlying string for both, but Clients MUST NOT rely on that.
Tool-call terminal content
Add a v2ToolCallContent variant that references the terminal:
terminalId so output is not lost while its tool-call content is being received or replayed.
Terminal state upserts
Add aterminal_update session update:
terminal_update is an upsert keyed by its required terminalId. The first update for an unseen ID creates stored terminal state. Its other fields are patches:
- An omitted field leaves its previous value unchanged.
nullclears its previous value.- A concrete value replaces its previous value.
- On a first-seen ID, omitted fields start unknown or empty.
command: the human-readable command being run. Agents SHOULD include it on the first update when the terminal represents a command. It is descriptive and does not ask the Client to execute anything.cwd: the command’s working directory. Every supplied path MUST be absolute.output: an authoritative replacement snapshot containing requireddata.datais RFC 4648 base64-encoded bytes and has JSON Schemaformat: "byte". A concrete snapshot is the complete byte sequence the Agent wants the Client to retain for the terminal at that point. It replaces all previously stored bytes, and Clients MUST NOT merge or splice bytes from the previous snapshot into it. Later chunks append to the replacement. Optional nullableoutput._metais scoped to that snapshot; omission andnullare equivalent and mean no snapshot metadata was provided.exitStatus: a concrete object marks the terminal as exited. ItsexitCode,signal, and_metafields are optional and nullable; omission andnullare equivalent and mean that value was not provided. Agents should use conventional platform signal names. POSIX examples includeSIGTERM,SIGKILL, andSIGINT; other platforms may use platform-specific names.exitStatus._metais scoped to that exit information.exitStatus: nullclears a previously reported exit status.- The top-level
_metais terminal-scoped and follows the same omitted,null, and concrete-object patch semantics as the other update fields.
Live output chunks
Add aterminal_output_chunk session update:
terminalId and data are required. data is one independently RFC 4648 base64-encoded byte chunk and has JSON Schema format: "byte". Clients decode each chunk separately and append the decoded bytes in received order for that terminalId; they MUST NOT concatenate the encoded strings and decode them as one value.
Chunk boundaries have no text or terminal meaning. A chunk may split a UTF-8 code point or ANSI escape sequence, so terminal emulators and transcript decoders retain parser state between chunks. _meta, when present, is optional chunk-scoped metadata; omission and null both mean no chunk metadata was provided.
JSON-RPC batch entries may be processed in any order. Agents therefore MUST NOT put more than one terminal_update or terminal_output_chunk for the same terminalId in one batch when their application order affects the result.
This initial design does not add byte offsets or sequence numbers. Replacement snapshots provide the replay, correction, and resynchronization operation, while chunks provide the efficient live append operation.
Command permission subjects
Addtype: "command" to the v2 permission subject union:
command and cwd are required, and cwd MUST be absolute. toolCallId, terminalId, and _meta are optional and nullable; omission and null are equivalent and mean the value was not provided. toolCallId and terminalId only associate the permission with displayed state, and either may be unavailable when permission is requested before execution begins.
The command subject is a self-contained description of the proposed execution, not a TerminalUpdate. Reusing the update type would incorrectly give permission context patch semantics. A selected allow outcome authorizes the Agent to execute the command; it never asks the Client to execute it.
Shiny future
How will things play out once this feature exists?Agents can stream command output once in a byte-faithful format, and Clients can choose the presentation that fits their environment. Rich IDEs can render a terminal emulator, while simpler Clients remain interoperable with a safe transcript. Session replay reconstructs the same terminal display without replaying every historical chunk, and command permissions can show the exact command and working directory without hiding them in generic tool-call input.
Implementation details and plan
Tell me more about your implementation. What is your detailed implementation plan?
- Add a v2
TerminalIdand theterminalToolCallContentvariant. - Add
terminal_updateandterminal_output_chunkto the v2SessionUpdateunion. - Model terminal update fields with distinct omitted,
null, and concrete states. - Add optional
_metato every new object payload; keepTerminalIdas a scalar string. - Mark snapshot and chunk
dataas base64 bytes with JSON Schemaformat: "byte". - Add the
commandpermission subject with its required command and absolute working directory plus optional associations. - Update v2 protocol documentation, migration guidance, generated schemas, and SDK tests.
- Keep v1 and all Client terminal execution methods unchanged.
Compatibility
The identical-looking v1 and v2 terminal content shapes have different ownership and lifetime semantics. Generic conversion MUST NOT map them by wire shape alone:- A v1 terminal ID refers to a Client-created resource that the Agent can control through
terminal/*methods. - A v2 terminal ID refers to an Agent-owned display stream with no Client control surface.
Frequently asked questions
What questions have arisen over the course of authoring this document or during subsequent discussions?
Why use base64 instead of strings?
PTY output is a byte stream. It may contain invalid UTF-8, and transport chunks may split a valid UTF-8 code point. Independently encoded byte chunks preserve the original stream without making JSON string boundaries semantically significant. The size overhead is acceptable for a correct baseline representation.Why have both snapshots and chunks?
Repeatedly replacing the full output buffer is inefficient for live output, while an append-only stream is insufficient for replay, correction, and resynchronization. The two operations have distinct, unambiguous semantics.Why put command and cwd on the terminal update?
They describe the terminal itself and are needed even when state arrives before its tool-call content or is replayed independently. Keeping terminal content as a small ID reference also avoids duplicating mutable state wherever the terminal is displayed.
Why is there no capability?
Display-only terminal output has a small safe fallback: decode the bytes, sanitize control sequences, and render a transcript. A capability would only be necessary if the protocol required richer behavior such as terminal emulation or interactive control.Revision history
- 2026-07-14: Initial draft.