Repository navigation
Conversation
`Query.resumeSessionAt` keys on an `SDKAssistantMessage.uuid`, so a client that keeps the uuid a prompt ended on can later re-enter that conversation at exactly that point. The adapter sees every one of those uuids go past and already records a `messageId` -> uuid table for rewind, but a prompt's own response carried no way to say which message it finished at. A settled prompt now carries it on `_meta.claudeCode.assistantMessageUuid`. The uuid is turn-owned and assigned where the consumer attributes the message, not read back at settle time. A session-global "last assistant message" would hand a turn whatever the session said most recently, which after a hand-off, a held-open turn, or a steered second cycle is another turn's answer. Subagent messages are excluded for the same reason they are excluded from the usage snapshot beside them: they are not this turn's own answer, and resuming from one would re-enter the conversation inside a tool call. It is the SDK uuid rather than the Anthropic API message id — those are two different identities on one message, and the `msg_...` id is what the ACP `messageId` already carries — and rather than the prompt's own uuid, which names the question and not the answer. Merged into whatever the response already carries, so an existing `claudeCode` namespace and any failure metadata survive. A cancelled turn carries none: it was interrupted rather than answered, and a turn that produced no top-level assistant message names nothing.
This was referenced Aug 27, 2026
A client that defers creating durable session state until a conversation really
exists has nothing to wait for. A session exists SDK-side before any prompt has
been taken, and everything a turn then produces — output, an idle, a result, a
settled prompt response — says the turn is under way or over, not that the SDK
accepted it. A client that read any of those as acceptance would retain a
conversation that may never have started.
The adapter already sees the dispatch. Two facts report it: the
`command_lifecycle` "started" frame on CLIs that emit one, and the replayed echo
of the prompt's own uuid everywhere else. Either may arrive first, or both.
A queued prompt the SDK took now publishes one `session_info_update` carrying
`_meta["executablemd.session-materialization/v1"] = {state: "accepted"}`. It
carries no session information of its own — no title, no updatedAt — so a client
reading it as session info reads nothing, and one waiting for acceptance reads
exactly that.
Exactly once per prompt, latched on the turn rather than the session: the two
dispatch facts describe the same dispatch, and a session-scoped latch would
publish for the first prompt of a session and never again. A local-only slash
command is never dispatched into a turn and publishes nothing. Nothing is
published from session initialization, output, idle, a result or settlement.
Three suites read every `session_info_update` a prompt produces and now see one
that carries no title; each is narrowed to the updates it is actually about
rather than to the update kind.
6 tasks done
Member
|
covered in #1046 |
Author
|
Thank you 🙏 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
Query.resumeSessionAtkeys on anSDKAssistantMessage.uuid. A client that kept the uuid a prompt ended on could re-enter that conversation at exactly that point — but a prompt response has no way to say which message it finished at, so the only thing a client can ask for is wherever the session has since got to.The adapter sees every one of those uuids go past, and already records a
messageId→ uuid table for rewind. The turn-boundary uuid just never reaches the caller.The change
A settled prompt now carries it on
_meta.claudeCode.assistantMessageUuid:{ "stopReason": "end_turn", "usage": { /* unchanged */ }, "_meta": { "claudeCode": { "assistantMessageUuid": "0f9c…" } } }Why it is turn-owned
latestAssistantUuidlives on theTurnand is assigned where the consumer attributes the message — beside the usage snapshot that is already turn-scoped by the same two conditions — rather than being read back at settle time.A session-global "last assistant message" would hand a turn whatever the session said most recently, which after an echo hand-off, a held-open turn waiting on subagents, or a steered second cycle is another turn's answer.
Subagent messages (
parent_tool_use_id !== null) are excluded for the same reason they are excluded from the usage snapshot: they are not this turn's own answer, and resuming from one would re-enter the conversation inside a tool call.Which identity
The SDK uuid, not the Anthropic API message id. Those are two different identities on one message, and the
msg_…id is already what the ACPmessageIdcarries — reporting it here would hand a client somethingresumeSessionAtcannot use. And not the prompt's own uuid, which names the question rather than the answer.Compatibility
Merged rather than assigned:
claudeCodeis a namespace this adapter already writes into, and a failure lane may have put its own metadata on the response before it reaches the settle path. Everything already there survives, and a client that ignores the key sees no difference.A cancelled turn carries nothing — it was interrupted rather than answered — and a turn that produced no top-level assistant message names nothing rather than reporting an empty value.
Tests
src/tests/checkpoint-metadata.test.tscompares the uuid the SDK emitted with the uuid the response returned:msg_…idThe new file passes and the build is clean. Reverting only
src/acp-agent.tsfails 5 of the 6 new tests, so they are not vacuous. Three unrelated pre-existing failures inproviders.test.tsandsettings.test.tsare present with and without this change (10 failures unpatched, 3 patched — this touches neither).Context
This came out of building durable Agent-session continuity in executable.md, where a workflow run retains which provider turn each prompt landed on so a later run can resume from an exact point. That consumer treats the value as opaque and never infers one — a prompt it cannot name stays unnamed — so the identity has to come from the adapter or not at all.
A matching change for
@agentclientprotocol/codex-acpis proposed alongside this one.