Skip to content

Report pi and ACP turn acceptance on consumption - #2021

Merged
ymichael merged 1 commit into
mainfrom
bb/2014-pi-and-acp-emit-input-accepted-at-queue-tim-thr_s88amww8ie
Aug 20, 2026
Merged

ymichael merged 1 commit into
mainfrom
bb/2014-pi-and-acp-emit-input-accepted-at-queue-tim-thr_s88amww8ie

Conversation

@ymichael

Copy link
Copy Markdown
Collaborator

What was wrong

#2013 established the uniform rule that input.accepted means the provider consumed the input, never that bb queued it, because an acceptance still pending when a stale terminal arrives lets that terminal claim the input and complete an empty turn for a message the provider has not answered. Pi and ACP still emitted acceptance at dispatch.

Pi's exposure is not just theoretical timing. PiSdkSession.prompt() resolves as soon as pi queues a prompt that arrives while a run is still unwinding, and the bridge reported that resolution as pi/prompt/settled — a claimIfIdle turn terminal. So a turn/start pi merely queued produced acceptance plus a terminal in the same tick, which the assembler turned into a started-and-completed empty turn while the real answer ran later under an unaccepted turn.

ACP emitted acceptance in the turn/start handler before the turn opened, and for a steer it emitted acceptance at queue time even though the queued input is dropped whenever the turn fails or the session stops — reporting input the agent was never given as accepted into the turn.

What changed

  • PiSdkSession tracks pending input consumption for both of pi's queues instead of steering only, and resolves it from pi's preflight hook (the input entered a run) or from the queue update that delivers a queued message. Its prompt() now returns that consumption signal alongside the settlement of the run it started, and reports no settlement for input pi queued into a run it did not start.
  • The pi bridge answers turn/start and emits input.accepted only once pi read the input.
  • The ACP bridge carries the waiting command with the input and emits input.accepted once the session/prompt request carrying it goes out, so the acceptance names the open turn and a dropped steer is never accepted. Every turn input still leaves with exactly one reply (#853).
  • HOST_DAEMON_PROTOCOL_VERSION 140 to 141: older daemons emit the queue-time semantics and produce those phantom turns.

Two deviations from the issue's proposed fix:

  1. The issue states pi's steer path "already waits for actual SDK acceptance." It does not — PiSdkSession.steer() resolved once the SDK took the message into its queue, the same queue-time violation as turn/start.
  2. Both steer paths deliberately keep answering their command at queue time, because the runtime fails a bridge request that goes unanswered for 30 seconds (sendJsonRpcRequest). Pi delivers steering only between assistant turns, so a steer sent during a long tool call would time out; ACP delivers a steer only when the cancelled prompt is reissued. Neither can manufacture a turn: a steer's acceptance lands in a turn the assembler already holds open, and the #2013 failure mode needs a pending acceptance. Pi keeps reporting a steer its run never read through the session error path.

There are no CLI, guide, configuration, or user-facing documentation changes.

How you verified

  • New pi regression: a turn/start pi queues behind a live run emits no turn events until the queue delivers it, then the acceptance lands in the turn pi opened. Before the change it received turn/started + turn/input/accepted + turn/completed — the phantom turn.
  • New ACP regressions: acceptance is emitted immediately after the turn opens rather than before it, and a steer dropped by thread/stop leaves the turn with one accepted input instead of two. Both fail before, pass after.
  • New PiSdkSession coverage for queued-versus-direct dispatch, and for a queued follow-up surviving the agent_end that continues into it.
  • pnpm exec turbo run typecheck test --filter=@bb/agent-runtime --filter=bb-plugin-provider-acp --filter=@bb/host-daemon-contract --force — 417, 172, and 52 tests passed; typechecks passed.
  • pnpm exec turbo run build typecheck --filter='...[origin/main]' — 62 tasks passed.
  • git diff --check — passed.

Fixes #2014

🤖 Generated with Claude Code

AGENT GENERATED: by Claude Opus 5

## What was wrong

#2013 established the uniform rule that `input.accepted` means the provider
consumed the input, never that bb queued it, because an acceptance still
pending when a stale terminal arrives lets that terminal claim the input and
complete an empty turn for a message the provider has not answered. Pi and ACP
still emitted acceptance at dispatch.

Pi's exposure is not just theoretical timing. `PiSdkSession.prompt()` resolves
as soon as pi queues a prompt that arrives while a run is still unwinding, and
the bridge reported that resolution as `pi/prompt/settled` — a `claimIfIdle`
turn terminal. So a `turn/start` pi merely queued produced acceptance plus a
terminal in the same tick, which the assembler turned into a started-and-
completed empty turn while the real answer ran later under an unaccepted turn.

ACP emitted acceptance in the `turn/start` handler before the turn opened, and
for a steer it emitted acceptance at queue time even though the queued input is
dropped whenever the turn fails or the session stops — reporting input the
agent was never given as accepted into the turn.

## What changed

- `PiSdkSession` tracks pending input consumption for both of pi's queues
  instead of steering only, and resolves it from pi's preflight hook (the input
  entered a run) or from the queue update that delivers a queued message. Its
  `prompt()` now returns that consumption signal alongside the settlement of
  the run it started, and reports no settlement for input pi queued into a run
  it did not start.
- The pi bridge answers `turn/start` and emits `input.accepted` only once pi
  read the input.
- The ACP bridge carries the waiting command with the input and emits
  `input.accepted` once the `session/prompt` request carrying it goes out, so
  the acceptance names the open turn and a dropped steer is never accepted.
  Every turn input still leaves with exactly one reply (#853).
- `HOST_DAEMON_PROTOCOL_VERSION` 140 to 141: older daemons emit the queue-time
  semantics and produce those phantom turns.

Two paths deliberately keep answering their command at queue time, because the
runtime fails a bridge request that goes unanswered for 30 seconds. Pi delivers
steering only between assistant turns, so a steer sent during a long tool call
would time out; ACP delivers a steer only when the cancelled prompt is
reissued. Neither can manufacture a turn: a steer's acceptance lands in a turn
the assembler already holds open. Pi keeps reporting a steer its run never read
through the session error path.

There are no CLI, guide, configuration, or user-facing documentation changes.

## How you verified

- New pi regression: a `turn/start` pi queues behind a live run emits no turn
  events until the queue delivers it, then the acceptance lands in the turn pi
  opened. Before the change it received `turn/started` + `turn/input/accepted` +
  `turn/completed` — the phantom turn.
- New ACP regressions: acceptance is emitted immediately after the turn opens
  rather than before it, and a steer dropped by `thread/stop` leaves the turn
  with one accepted input instead of two. Both fail before, pass after.
- New `PiSdkSession` coverage for queued-versus-direct dispatch, and for a
  queued follow-up surviving the `agent_end` that continues into it.
- `pnpm exec turbo run typecheck test --filter=@bb/agent-runtime
  --filter=bb-plugin-provider-acp --filter=@bb/host-daemon-contract --force` —
  417, 172, and 52 tests passed; typechecks passed.
- `pnpm exec turbo run build typecheck --filter='...[origin/main]'` — 62 tasks
  passed.
- `git diff --check` — passed.

Fixes #2014

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ymichael
ymichael merged commit 6c62596 into main Aug 20, 2026
13 checks passed
@ymichael
ymichael deleted the bb/2014-pi-and-acp-emit-input-accepted-at-queue-tim-thr_s88amww8ie branch August 20, 2026 07:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pi and acp emit input.accepted at queue time, violating the consumed-not-queued rule from #2013

1 participant