Files
buzz/crates
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 a2cabe9cc8 fix(acp): scope dead-turn retry to unanswered response-required events
The first cut of dead-turn detection asked "did this turn produce any
output at all", which is both too broad and too narrow.

Too broad: `PromptSource::Channel` covers every matched batch, not just
mentions. Under `--subscribe all` or a `require_mention: false` rule an
agent watches traffic nobody addressed to it, and base_prompt.md tells
it to stay silent when it has nothing to add. Retrying that turned
correct restraint into ten retries and a user-visible failure notice.
`MatchedRule` now carries `requires_response` — the rule required a
mention, or the event p-tags the agent — and it rides through
`QueuedEvent`, `BatchEvent` and `FlushBatch` so classification can ask
whether anyone was actually owed an answer.

Too narrow: an event injected mid-turn by a native steer is not in the
batch, and the ack used to drop it outright. If the turn then went
silent, that mention was gone with no retry — the same disappearance
this change exists to close, one layer down. A `Success` ack now moves
the event into a delivered-steer ledger keyed by the turn's output
epoch, and the turn's terminal classification settles it: output after
the delivery retires it, silence releases it to the queue front for an
ordinary, fully-budgeted retry. The per-turn boolean became that
monotonic epoch counter because a flag cannot tell "spoke, then went
silent on the new message" from "answered the steer". Expiry and panic
recovery settle the ledger too, so a delivered event can never outlive
its turn.

Two smaller corrections. The ACP schema lets the initial `tool_call`
carry `status: "completed"`, so a connector that sends no follow-up
`tool_call_update` was being read as dead after doing real work; one
shared predicate now covers both events. And the control-signal race
branch, which synthesizes its own `EndTurn`, routes through the same
classifier rather than relying on a polling invariant that lives in
another function — the invariant holds today, but one inserted `.await`
would silently turn that branch into a bypass.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-07-29 21:42:21 -04:00
..
2026-07-27 14:18:24 -04:00