Repository navigation
Description
Design doc: files/designs/code-acp-adapter-prompt-lifecycle.md in the keep-the-four-repos-healthy goal workspace. Audit at origin/main cf13a269456d3c4fb733af312d89dc741140836c — every defect below re-verified present at the cited sites this pass (2026-11-02, adapter and dispatch client read at that commit).
Summary
bin/opencode-acp-adapter.mjs (558 lines) lets OpenClaw's acpx runtime drive the fleet's one standing opencode serve over ACP (#1243, landed by PR #3997). PR #3997 merged as-is with four Codex review findings unresolved; they were filed on 2026-11-02 as #4614, #4616, #4617 and #4618 (source/pr-comment-review). They are one unstated contract in two halves:
- The adapter does not speak its server's contract. The file header (:11–20) documents a v1 mapping and the code follows it —
session/newposts/session?directory=…and expects a bareid(:449); prompts go to/session/:id/prompt_async(:470); the permission reply (:340) and abort (:487) are likewise v1-shaped. The target is the pinned OpenCode 2.x standing server, and the fleet's own client for that same server,pm-stack/dispatch/src/opencode.ts, uses the v2 contract throughout:POST /api/sessionwithlocation.directoryin the body and a{data: …}envelope (:58),POST /api/session/:id/prompt(:66),DELETE /api/session/:id(:70),GET /api/session(:74). Per openclaw ACP adapter: use the standing server's v2 session API — v1 /session + prompt_async contract cannot create working sessions (#3997) #4614 the v1 flow does not create working sessions against the intended server at all. - Prompt state has no lifecycle. Three module-level containers (:187–191) —
pendingPrompts,cancelledSessions(a bare session-id set),partTextSent(keyed by part id alone, no session) — are written at prompt start and cleared, if at all, on exactly one exit path each.finishPrompt(:349–352) clears all sessions' text offsets (openclaw ACP adapter: finishPrompt clears streamed-text offsets for all sessions, duplicating the still-running session's text (#3997) #4616: a still-running session's text is re-emitted from character zero);session/cancel(:484) records a marker unconditionally and only the pending-gatedsession.idlebranch (:371–372) ever deletes it, while thesession.errorpath finishes without clearing it (openclaw ACP adapter: cancellation marker survives a finished prompt and mislabels the next prompt as cancelled (#3997) #4618: the next prompt is mislabelledcancelled); and the event-stream loop (:247–256) reconnects after a drop with no reconciliation ofpendingPrompts(openclaw ACP adapter: pending prompts are never reconciled when the SSE event stream reconnects (#3997) #4617: a prompt whose idle event was lost hangs forever and blocks its session permanently). Even the submission-failure path (:475) deletes only the pending entry.
Both halves are the same omission: the adapter was written against an assumed server and an assumed happy path, and neither assumption was ever stated as a contract the code is checked against.
The contract
The pinned server's v2 API is the only contract, and a prompt's state lives and dies with the prompt: scoped to its session, recorded only while the prompt is in flight, cleared on every terminal path through the one terminal function — and lost event continuity is itself a terminal path, settled by reconciling against the server's state or failing explicitly, never by waiting.
Recorded decisions (full reasoning, alternatives and risks in the design doc):
- Every route is v2-shaped or explicitly dispositioned. Session create and prompt submission per openclaw ACP adapter: use the standing server's v2 session API — v1 /session + prompt_async contract cannot create working sessions (#3997) #4614's acceptance, tested against a v2-shaped stub (not the v1 stub). Phase 1 also dispositions the routes openclaw ACP adapter: use the standing server's v2 session API — v1 /session + prompt_async contract cannot create working sessions (#3997) #4614 does not name — abort, permission reply, event stream — against the pinned server, and pins the v2 prompt submission semantics (blocking vs fire-and-event) that Phase 3 depends on.
- Offsets are scoped to (session, part) and a prompt's cleanup removes exactly its own session's entries (openclaw ACP adapter: finishPrompt clears streamed-text offsets for all sessions, duplicating the still-running session's text (#3997) #4616's acceptance, stated as the general rule).
- Cancellation is recorded only for an in-flight prompt and cleared on every terminal path — idle, error, submission failure, reconciliation failure — because all of them exit through the one terminal function, which clears pending entry, cancellation marker and offsets for that session together (openclaw ACP adapter: cancellation marker survives a finished prompt and mislabels the next prompt as cancelled (#3997) #4618's acceptance, stated as the general rule).
- Reconnect reconciles, then fails only the unprovable. On event-stream reconnect, each pending prompt is reconciled against the server's v2 session read: proven finished/idle → finished normally; state not provable → the ACP request is failed explicitly. This pins openclaw ACP adapter: pending prompts are never reconciled when the SSE event stream reconnects (#3997) #4617's either/or (reconcile or fail) as reconcile-first: stream drops are routine and the server's state is readable, so failing every pending prompt would discard completed work; waiting — the current behaviour — is the defect being fixed.
Phased breakdown
Phases adopt the four origins as sub-issues (no parallels). Phase 4 is a new closing-audit issue, gated on Phases 1–3.
- Phase 1 — speak the server's contract (adopted: openclaw ACP adapter: use the standing server's v2 session API — v1 /session + prompt_async contract cannot create working sessions (#3997) #4614). v2 session create + prompt submission per the origin's acceptance; required dispositions: abort / permission-reply / event-stream routes against the pinned server, and the v2 prompt submission semantics. (openclaw ACP adapter: use the standing server's v2 session API — v1 /session + prompt_async contract cannot create working sessions (#3997) #4614 is currently
status/parkedwith a holder — its agent note under this design records the phase structure; the design does not reassign it.) - Phase 2 — scoped state, one terminal cleanup (adopted: openclaw ACP adapter: finishPrompt clears streamed-text offsets for all sessions, duplicating the still-running session's text (#3997) #4616 + openclaw ACP adapter: cancellation marker survives a finished prompt and mislabels the next prompt as cancelled (#3997) #4618). Decisions 2–3. Independent of Phase 1 in code — the containers are adapter-local. (openclaw ACP adapter: cancellation marker survives a finished prompt and mislabels the next prompt as cancelled (#3997) #4618 is currently claimed/in-progress; its note records the shared terminal-function rule so its fix and openclaw ACP adapter: finishPrompt clears streamed-text offsets for all sessions, duplicating the still-running session's text (#3997) #4616's land as one cleanup, not two.)
- Phase 3 — reconnect reconciliation (adopted: openclaw ACP adapter: pending prompts are never reconciled when the SSE event stream reconnects (#3997) #4617). Decision 4, using the v2 session read Phase 1 establishes and the terminal function Phase 2 completes. Gated on Phases 1–2 in substance; the gating is recorded on the origin (its parked/assigned state is its holder's, not changed by this design).
- Phase 4 — closing state-and-route inventory + contract statement (new issue, gated on Phases 1–3): every module-level state container and every serve route in the adapter dispositioned against this contract; the header mapping comment rewritten to the v2 contract + lifecycle rules. Required dispositions: (a) the
sessionsmap's lifetime (no close path exists); (b)pendingClientRequestscleanup when a client never answers; (c) the permission flow's v2 shape if Phase 1 did not land it; (d)partTextSentkeying after Phase 2.
Open questions (in the design doc, not filed)
Whether a prompt needs a wall-clock timeout independent of stream continuity (no filed instance; decision 4 covers the observed hang); whether sessions should be deleted server-side when an ACP client disconnects (needs a filed instance or Phase 4 evidence); whether a second server-contract version is ever supported (the header comment is the contract's home; revisit when the pin moves).
Related
- Origins adopted as phases: openclaw ACP adapter: use the standing server's v2 session API — v1 /session + prompt_async contract cannot create working sessions (#3997) #4614 (Phase 1), openclaw ACP adapter: finishPrompt clears streamed-text offsets for all sessions, duplicating the still-running session's text (#3997) #4616 + openclaw ACP adapter: cancellation marker survives a finished prompt and mislabels the next prompt as cancelled (#3997) #4618 (Phase 2), openclaw ACP adapter: pending prompts are never reconciled when the SSE event stream reconnects (#3997) #4617 (Phase 3).
- Landed sibling from the same PR: openclaw ACP adapter: load the standing-server credential — gateway launch wires no OPENCODE_SERVER_PASSWORD (#3997) #4615 (standing-server credential loading, closed — its code is in the file at audit HEAD). The four origins are the complete filed defect inventory for this adapter (verified by title/body search this pass).
- Feature origin: OpenClaw: share one opencode serve via a small ACP-to-HTTP adapter #1243 (share one opencode serve via an ACP adapter, closed).
- The fleet's v2 reference client:
pm-stack/dispatch/src/opencode.ts(see also design design(pm-dispatch): launch pipeline, staged — validate the whole request at the boundary, one commit point, nothing after it can fail the launch #2668 for dispatch's own contract).
Acceptance criteria
- Phase 1: the adapter speaks the v2 contract (session create + prompt submission per issue openclaw ACP adapter: use the standing server's v2 session API — v1 /session + prompt_async contract cannot create working sessions (#3997) #4614's acceptance), with abort / permission-reply / event-stream routes dispositioned against the pinned server and the v2 prompt-submission semantics pinned.
- Phase 2/3 contract points hold in code: prompt offsets scoped to (session, part); cancellation recorded only for in-flight prompts and cleared on every terminal path through the one terminal function.
- Each child finding (issue openclaw ACP adapter: use the standing server's v2 session API — v1 /session + prompt_async contract cannot create working sessions (#3997) #4614, issue openclaw ACP adapter: finishPrompt clears streamed-text offsets for all sessions, duplicating the still-running session's text (#3997) #4616, issue openclaw ACP adapter: pending prompts are never reconciled when the SSE event stream reconnects (#3997) #4617, issue openclaw ACP adapter: cancellation marker survives a finished prompt and mislabels the next prompt as cancelled (#3997) #4618) is closed by its phase or explicitly dispositioned on this issue.
Activity
dezren39 commentedon Nov 3, 2026
agent-note:v1 — design-architect pass 41 (2026-11-02)
This design was originated by the design-architect pass at origin/main cf13a26: the four unresolved Codex findings from PR #3997, filed as #4614/#4616/#4617/#4618, are one unstated contract (the pinned server's v2 API + a prompt lifecycle) in a single file,
bin/opencode-acp-adapter.mjs. The four origins are adopted as Phases 1–3 (no parallels filed) and labelledsource/design-architect; #4707 is the new Phase 4 closing audit, enqueued as a triage packet and gated on Phases 1–3. Design doc:files/designs/code-acp-adapter-prompt-lifecycle.mdin the keep-the-four-repos-healthy goal workspace; central register entry added (status: proposed). Nouser/review: every origin carries explicit acceptance and the four recorded decisions are the architect's to make.dezren39 commentedon Nov 3, 2026
Intake review
Related: issue #4614, issue #4616, issue #4617, issue #4618 (the four Codex findings on PR #3997 this design unifies), issue #1243 (adapter origin), pm-stack dispatch client (the v2 reference implementation cited in the design). Notes: this is a design/epic item — the phases are adopted by existing child issues, so intake does not reassign any child. Risks: Phase 3 depends on the v2 prompt-submission semantics pinned in Phase 1; landing phases out of order would re-create the unstated-contract problem the design exists to fix. Suggested approach: route as an epic; the queue works the children in phase order, and this issue closes when every child is closed or dispositioned.
— Fleet monitor intake (protocol §5), 2026-11-03T02:29:50Z. Labels normalized and
meta/enrichedapplied; acceptance criteria appended to the issue body.10 remaining items
dezren39 commentedon Nov 4, 2026
🤖 Agent note · triaged
user/escalated, and assigned to Drew. Phase 4 audit Phase 4 (audit) of #4706 — closing state-and-route inventory + contract statement for the OpenClaw ACP adapter #4707 remains OPEN/blocked; queue task 8494 already exists, so no duplicate was created.dezren39 commentedon Nov 8, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 8, 2026
🤖 Agent note · handed off