# ACP busy follow-up runtime proof matrix — pass 1

PR [#22697](https://git.cafe/pingdotgg/t3code/pull/22697), mirrored as [#22698](https://github.com/pingdotgg/t3code/pull/22698). Branch `fix/issue-22660`.

Before: [6a6c84f990](https://git.cafe/pingdotgg/t3code/commit/6a6c84f9906cbdfa7f533fecfc39ce18c2f4c273), full SHA `6a6c84f9906cbdfa7f533fecfc39ce18c2f4c273` (pinned main and verified merge-base).
After: [eb303fe4b6](https://git.cafe/pingdotgg/t3code/commit/eb303fe4b6458aa2091fbfcba2fcf66bbad3e1ec), full SHA `eb303fe4b6458aa2091fbfcba2fcf66bbad3e1ec`.

## Scope and minimum proof

Server-only change: `packages/provider-acp/src/server/AcpSessionRuntime.ts`, `packages/provider-acp/src/server/adapter.ts`, plus registry adapter tests. No changed components or renders. Viewport, theme, pixel diffs, browser captures and recordings do not apply under the explicitly adapted brief.

Minimum running-server set: R1–R6, each Before + After under identical fixture conditions (12 labeled scenario transcripts; one combined artifact is fine). A deterministic fake ACP v1 subprocess configured as an actual local ACP provider is acceptable. Label it synthetic, not oh-my-pi. Drive a running T3 dev server through its real authenticated RPC transport and observe persisted read-model/events. Do not substitute direct `makeAcpRegistryAdapterV2`, injected `makeRuntime`, replay-test logs, or a fake server. Protocol fixture output alone is also insufficient.

All scenarios start with an actual first prompt producing text and `end_turn`, then submit confirmation `нажал` to that same session. Recognized rejection is ACP `AcpRequestError`, code `-32603`, exact message `Agent is already processing. Use steer() or followUp() to queue messages, or wait for completion.` Capture full outbound prompt payloads to establish equality across retries; redact credentials, never payload fields relevant to equality.

| Row | Branch / fixture data | Affected? | Before expectation | After expectation and assertions | Seed / interaction | Status |
| --- | --- | --- | --- | --- | --- | --- |
| R1 recovery | One exact busy rejection, then accept identical confirmation and reply `Received your confirmation.` | Yes | Follow-up fails with raw busy error; no automatic retry | Queued retry item running, attempt 1, maxAttempts null, delay 1000 ms; no premature terminal; same full prompt payload delivered once successfully; reply persisted; queue item completed; turn completed without failure; reusable thread | Local fake ACP v1 provider, mode recovery; submit first turn, wait persisted completion, submit confirmation, release accept gate after first rejection | MISSING runtime pair; supporting test exists |
| R2 long busy | Reject confirmation 12 times, then accept; delays 1, 2, 4, 8, 16, then seven 30 s waits = 241 s | Yes | First rejection fails follow-up | No attempt limit or premature terminal across >4 minutes; capped delay 30 s and increasing attempts; all prompt payloads equal; eventual reply/completion; no duplicate acceptance | Same provider, mode long-busy; real server wall clock; preserve monotonic timestamps and dispatch count (14 total including first turn) | MISSING runtime pair; supporting TestClock test exists |
| R3 Stop while queued | Busy rejection leaves no outstanding prompt RPC; Stop during retry backoff | Yes | Follow-up already failed; report actual Stop availability/result, do not invent a queued baseline | session/cancel emitted; interrupted turn with no failure; queued notice settled; no resubmission for >=60 s after Stop; total dispatches 2; fresh next turn can run | Same provider, mode queued-stop; stop via real server command after persisted queued state; use long-busy backoff to avoid timing race if necessary and record actual count | MISSING runtime pair; supporting queued-cancel test exists (reusability needs runtime check) |
| R4 Stop during dispatch | Reject once; next retry held in flight until session/cancel; then return exact busy rejection | Yes | Follow-up fails before retry; no in-flight retry to cancel | Stop forwarded before held busy response; interrupted without failure; no queued resurrection or later submissions for >=60 s; expected dispatches 3; fresh next turn can run | Same provider, mode in-flight-stop; wait fixture's retry-start milestone, send Stop, release response on cancel | MISSING runtime pair; supporting in-flight-cancel test exists (reusability needs runtime check) |
| R5 prior output while queued | After exact rejection, emit old root `agent_thought_chunk` text `Previous background work is still running.` before accepting retry | Yes | Busy follow-up fails; record late-output treatment | Queued notice stays running despite old root output; no terminal or recovered notice before delivery; later accepted confirmation replies and settles notice/turn | Same provider, mode old-output; gate emission until queued item persisted; capture projection immediately after thought update and again after accepted retry | MISSING runtime pair; supporting old-output test exists |
| R6 unrelated error | Reject with code -32603 and message `Another internal error`; no acceptance step | Guard / preserved behavior | Failed follow-up with unrelated error; no retry | Same failed result and no queue notice or retransmission for >=2 s; original failure retained | Same provider, mode unrelated-error; submit same first/second prompts | MISSING runtime pair; supporting error-guard test exists |

## How to seed the real app

The production registry adapter accepts settings `source: "local"`, `commandPath`, `commandArgs` (`packages/provider-acp-registry/src/settings.ts`); local resolution launches that command without a shell (`server/AcpRegistrySupport.ts`). Configure a fixture executable through an isolated dev environment's actual provider settings, select that provider instance for a real project/thread, and submit turns over the running server's public RPC command path. No browser is necessary. Use normal isolated dev state; never point a server at live `~/.t3/userdata`.

A standalone executable/script with the six modes and an RPC driver still needs to be supplied by the capturer. Existing `apps/server/scripts/acp-replay-agent.ts` and the committed `openBusyReplaySession` transcripts are design references, not a ready running-server seed: the test injects runtime/catalog and uses Effect TestClock. Do not claim these fixtures are already configured in an app. Use the same fixture content/hash and driver for both revisions; record the selected provider instance/settings with secrets removed. If a scenario cannot be seeded through the real app, disclose it as missing runtime evidence; an isolated harness is supporting evidence only for this task.

## Transcript manifest and pass criteria

For each revision record full git SHA, clean/dirty status, actual launch command, resolved server address/port, isolated home path, Node/runtime version, OS, fixture and driver content/hash, ACP protocol version 1 and synthetic agent identity. Capture authenticated connection success without publishing its token. Include scenario ID, ordered command receipts, relevant persisted events/read-model snapshots, session/run/turn IDs, monotonic timestamps, ACP request/response/cancel frames, and final observations. Capture the state after each named gate rather than relying only on a final success line. R1 proves the normal success path on both revisions through its first turn.

Before R2–R5 cannot reach the new queue branch; that is the expected baseline result, not missing baseline evidence. Execute the same scenario and record the failure/no-retry outcome. After Stop, the fixture must permit a fresh independent prompt so reusability can be checked. Observe a window beyond the maximum 30-second retry delay to detect zombie dispatches; the proposed 60 seconds supplies margin. Capture scripts should await explicit milestones; do not use sleeps as substitutes for persisted-state assertions. Real elapsed observation windows are appropriate only to prove absence of later dispatch.

The written behavior delta should list: exact busy errors become queued and retried indefinitely with capped backoff; existing retry items carry the queued explanation/attempts/delay and settle at completion/Stop; old output does not clear the queue; unrelated errors keep failing. Enumerate any additional observed differences. No claim of durable queue persistence across server restart is made.

## Supporting scoped verification (not runtime proof)

Committed tests in `apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts` contain all six scenario shapes. Inspect or run the scoped file at the after SHA; existing `/tmp/issue-22660/review-1-tests-final.log` reports 103 passing tests across registry/shared adapter/ACP connection suites but is not independently SHA-attested by this audit. TestClock advances prove backoff arithmetic, not a four-minute running-server lifecycle. Add exact-match negatives (same message with a different code; same code with a near-match message) and normal-provider retry notice settlement as focused guard checks if not already covered. These can remain unit-level support rather than expanding the minimum server matrix.

Shared runtime callers include registry/local ACP and Antigravity; Antigravity has wait-for-prompt cancellation and registry uses the standard flavor. Existing scoped adapter/connection tests support those cancellation modes; no claim of live coverage of every ACP provider is warranted. Native Codex/Claude/Cursor/Grok/OpenCode paths outside ACP are not changed. Existing retry contracts reach web/desktop/mobile and local/remote/relay/tunnel consumers; actual visual presentation and each connection topology remain unverified, with no render changes requiring a capture matrix. Chat/MCP/scheduled starts share the server runtime; no new entry point or permission contract is added.

Real oh-my-pi lifecycle and Windows desktop are explicitly UNVERIFIED, accepted exclusions rather than blockers. The running T3 dev-server transcripts above remain mandatory.
