The Agent Client Protocol (ACP) is the live protocol between a client (usually an editor) and an agent. ASR is the stored form of an ACP session. This page is the normative mapping in both directions:
  • Recording: a recorder observes an ACP v2 connection (as the client, or as a proxy between client and agent) and writes one ASR stream per ACP session.
  • Replay: a replayer reads an ASR stream and emits it as ACP v2 session/update notifications, as an agent does when it replays history on session/resume.
Replay is semantically equivalent to the source stream, with declared losses. Byte-level equivalence is promised only when the recorder retained the wire messages.

Pinned revision

ASR-700 This mapping is defined for ACP schema v2.0.0-alpha.9 (the schema-v2 artifact published 2026-10-16), wire protocol version 2. A recorder MUST set producer.method to recorder and producer.source to {"format": "acp", "formatVersion": "2.0.0-alpha.9"}, naming the schema revision it implements. A recorder for a later ACP schema revision follows the mapping published for that revision. ACP v2 is a draft; its stable and unstable schemas change. Features this page marks as ACP-unstable (subagents, inter-session messages) follow ACP’s own unstable schema at the same revision; the ASR records they map to are normal ASR records. The ACP v1 schema is not covered by this mapping; a converter for stored ACP v1 update logs follows the same principles and declares its losses.

Recording

Streams, sessions and identity

ASR-701 A recorder MUST write one stream per ACP session it observes, with sessionId set to the ACP sessionId verbatim, and MUST write records only for the sessions it observes. ASR-702 A recorder MUST keep ACP identifiers verbatim: messageId as messageId, toolCallId as callId (with callIdSource: "native"), planId, compactionId and terminalId under their own names, and an elicitation’s elicitationId as requestId. ASR-703 A recorder MUST set agent.name to the registered short name or reverse-DNS name that the registry gives for the agent’s info.name, or to a reverse-DNS name it is configured with. When neither exists, it MUST use org.agent-session.acp.<label>, where <label> is info.name lowercased with every run of characters outside a–z and 0–9 replaced by one - (and leading and trailing - removed). agent.version is info.version. The org.agent-session.acp. prefix is reserved by this specification (ASR-602) for exactly this fallback, so that a recorder never claims a domain it does not control.

Folding updates

ACP v2 reports state as upserts keyed by stable IDs, with one patch rule: an omitted field is unchanged, null clears the field, a value replaces it (arrays as a whole), a chunk appends, and a full-content update replaces content accumulated from chunks. _meta on upserts is patched key by key. ASR-704 A recorder MUST apply ACP’s patch rule to combine (fold) updates for each entity, joining chunks by concatenation (ASR-211), and MUST set assembly (chunks when the content came from chunks, folded otherwise). A replay that begins with an empty-content upsert (content: []) resets the accumulated content. ASR-705 A recorder MUST write an entity’s record when the entity becomes final:
  • a tool call, when its status becomes completed, failed or cancelled;
  • a compaction, when its status becomes completed, failed or cancelled;
  • a message, plan or terminal, at the next state_update to idle or requires_action, at session/close, or when the recorder stops;
  • an elicitation, when the client’s response arrives, at session/close, or when the recorder stops.
It MAY write a snapshot earlier (for crash safety), with status: "partial" where the type has a status, and supersede it when the entity becomes final. ASR-706 A change to an entity after its record was written as final MUST be recorded as a superseding record (ASR-508), rather than merged into the existing final record. ASR-707 For updates received live, a recorder MUST set ts to the time it received the first update for the entity, with tsSource: "recorder", and MAY set observedAt. For history the agent replays (on session/resume or after a fork), it MUST NOT set ts from receipt time; such records carry observedAt only, unless the update’s _meta carries a source time. ASR-708 A recorder MUST fold _meta key by key (a present key replaces, a null value removes the key) into the entity record’s _meta, and MUST preserve the reserved trace context keys. ASR-709 ACP enum values (tool kinds, statuses, stop reasons, plan entry statuses, permission option kinds), including _-prefixed vendor values, MUST be recorded unchanged where the ASR field has the same meaning, except where the table below gives a mapping. ASR-710 An ACP session/update the recorder cannot map (an unknown sessionUpdate kind, or one this page lists as not mapped) MUST be recorded as an audit-only record of type org.agent-session.acp/<sessionUpdate>, with the update in native.record.

Paths

ACP uses absolute paths. ASR uses root-relative paths where possible. ASR-711 A recorder MUST declare the session’s cwd as a root with ID root0, and each of additionalDirectories in order as root1, root2, …. It MUST convert every absolute ACP path to a PathRef: relative to the first root whose path is a segment-wise prefix of it, otherwise with root: null and the absolute path. Paths inside text (content, raw input and output, patch text) are kept as received.

Mapping table

Details

ASR-712 A recorder MUST NOT record MCP server configuration values (environment variables, headers, arguments), which can contain credentials. It MAY record server names and transport types under _meta. ASR-713 A recorder MUST record a prompt as a message with role: "user", origin: "prompt", the messageId from the session/prompt response, and the prompt’s content blocks as content. A user_message upsert with the same messageId is the same entity. A recorder MUST NOT set author: "human" unless it is configured to know that the client relays a person’s input. ASR-714 A recorder MUST write the tool_result for a call with ACP’s tool call content ({type: "content"} items) as displayContent, rawOutput as raw, and no content, with capture.content: {"state": "absent"}. ACP does not say what reached the model. ASR-715 A recorder MUST map a diff in a tool call’s final content to a file_change with the following values:
  • cause: {"callId": <toolCallId>};
  • changes[] from ACP’s changes (operation → op, path and oldPath converted per ASR-711, fileType, mimeType);
  • patch: {"format": "git_patch", "text": <patch text>} when present;
  • precision: "hunks" when a patch is present, otherwise unknown;
  • status from the call’s final status: completed → applied, failed → failed, cancelled → unknown, no final status → proposed.
ASR-716 A recorder MUST map a permission request to a permission with requestId set to <n>:<id>, where <n> is the recorder’s connection number for the session and <id> the JSON-RPC request ID as a string, and idSource: "derived"; title, description and options copied; subject from ACP’s subject (tool_call → {callId}, command → {command}). On the response, status becomes resolved; selected sets selectedOptionId and sets decision to the selected option’s kind; cancelled sets decision: "cancelled". decidedBy is not set unless the recorder knows who decided. ASR-717 When an agent replays history (on session/resume or after a fork), the recorder MUST NOT write a second record for an entity it already recorded (same messageId, toolCallId, planId, compactionId, terminalId or requestId). If the replayed final content differs from the recorded final content, the recorder MUST write a superseding record. Replayed entities it has not recorded are written per ASR-707. ASR-718 For session/fork, a recorder MUST start a new stream for the new session with lineage.forkedFrom: {"session": <parent>, "evidence": "native_field"} and lineage.inherits: {"mode": "copied", "from": <parent>, "evidence": "native_field"}. History the agent replays into the fork is copied: records with the same IDs as records in the parent carry copiedFrom with proof: "native_id", and the recorder SHOULD declare localThroughId once the replay ends. ACP forks whole sessions, so forkedFrom has no record. ASR-719 A recorder MUST map a compaction to a compaction with compactionId, kind: "summary", status from ACP’s status, and summary from ACP’s summary (with capture.summary: {"state": "absent"} when ACP gives none). ACP does not state the extent of a compaction; by convention of this mapping, replaces is {"throughId": <the last context-participating record on the active path when the compaction was first seen>}. ASR-720 A recorder MUST map usage_update to a usage_summary with scope: "session", cumulative: true, includesChildren: null, contextWindow: {used, size}, and cost: {amount, currency} without source when ACP gives a cost. used is the amount of the context window in use, not the tokens consumed so far (ASR-318). ASR-721 For an ACP subagent association, a recorder MUST write a subagent record in the parent stream (childSession from the child’s session ID, callId when ACP links a tool call) before any child traffic it records, and the child’s stream MUST carry lineage.spawnedBy with evidence: "native_field". A recorder MUST NOT guess a parent. ASR-734 A recorder MUST map an elicitation/create request for an observed session to an input_request with: requestId set to the elicitationId when ACP gives one, and otherwise to <n>:<id> as in ASR-716 with idSource: "derived"; mode from ACP’s mode (form, url, or a _-prefixed vendor mode unchanged); message, requestedSchema and url copied; callId from toolCallId when present. On the client’s response it MUST set status from the action (accept → answered, decline → declined, cancel → cancelled) and, for an accepted form, response from the submitted content, subject to ASR-283. In URL mode, answered means the user agreed to open the URL; the recorder records no other content. An elicitation sent before a session exists belongs to no observed session and is not recorded.
ASR-735 For an ACP session_message update (ACP-unstable), a recorder MUST write a message with role: "user", author: "agent" and origin: "inter_session" in the stream of the receiving session, when it observes that session, with from: {"session": <sender>}, recipients: [{"actor": {"session": <recipient>}}] and the message ID that ACP gives for the receiving side. In the sending session’s stream it records the update per ASR-710. It MUST NOT invent a session or a message ID.

Declared losses (recording)

ASR-722 A recorder MUST include in producer.lossy every category below that applies to the stream: Elicitation requests and their answers are recorded as input_request records (ASR-734), so they are not a loss. ASR-723 A recorder SHOULD retain the wire messages it observed (the JSON-RPC frames, in order) in native files, with native.ref on each record pointing to the frames it came from. Byte-level replay is possible only from this capture.

Replay

A replayer turns a stream into ACP v2 updates, for a client that asked to resume a session with replayFrom: {type: "start"}. ASR-724 A replayer MUST replay the active path of the stream’s primary head (ASR-363), in path order, using the latest revision of each entity, and MUST preserve every ASR identifier that maps to an ACP identifier. ASR-725 A replayer MUST send whole-entity upserts (user_message, agent_message, agent_thought, tool_call_update, plan_update, compaction_update, terminal_update), not chunks. ASR-726 Replay MUST NOT re-execute tools, re-issue permission requests or start processes. Terminals are replayed as display-only snapshots.

Replay mapping

ASR-727 A reasoning part inside an assistant message that also has other parts MUST be replayed as a separate agent_thought, sent before the agent_message, with the derived message ID <messageId>#thought-<n> (<n> is the part’s 0-based index among the message’s reasoning parts). Refusal parts are sent as text content blocks. ASR-728 A replayer MUST map tool_result.status to ACP tool call status as follows: completed → completed; failed and denied → failed; cancelled and interrupted → cancelled; unknown → other; a vendor value unchanged. ASR-729 A replayer MUST rebuild a file_change’s diff with absolute paths from its root’s path. When the root’s path is null (redacted), it MUST omit that change’s diff and declare the loss. ASR-730 Notices (origin: "notice") MUST NOT be replayed.

Declared losses (replay)

Replay does not carry: model calls and per-call usage (inference), commits and workspace state (vcs, workspace_state, checkpoint), gap and tombstone records, declarations, instructions, the history of config_change and state, turn triggers, permission and input_request records, hook records, memory_change and delivery records, senders and conversations, off-path records and branch structure, lineage and copy markers, capture states, redaction records, and timestamps. ASR-731 A replayer MUST report to its caller which of these categories the replayed stream contained. It MUST NOT present a replay as a complete representation of the stream (ASR-617).

Equivalence

ASR-732 Replay MUST be semantically equivalent with declared losses: recording a replay of stream S (per this page) yields a stream S′ whose context view at its primary head has the same entities as S’s, in the same order, with the same IDs, roles, content blocks, tool names, inputs, mapped statuses, display content and file change operations, except for the categories ASR-731 reports. Conformance fixtures test this round trip. ASR-733 Byte-level equivalence is promised only for a stream whose recorder retained the wire messages (ASR-723). A replayer that has the capture MAY resend the captured frames instead of rebuilding them.

Support matrix