Repository navigation
Description
What happens
On Buddy's chat (buddy instance, Hermes over ACP, stable 0.1.6-lazurio.1), a follow-up sent while the timeline already says Buddy is working can still become a second turn instead of a steer. It happens in the first seconds of a turn that has to start a provider session first.
Seen on Ring 0 during the #138 point 12 acceptance, 2026-11-03, read back from the Buddy instance's statev2.sqlite:
| UTC | Event |
|---|---|
| 09:58:01.790 | first message, run ordinal:31 requested |
| ~09:58:02 | the composer shows "Buddy na tom pracuje…" (apps/web/src/lazurio/BuddySurface.tsx:111) |
| 09:58:06.655 | provider session ready (a cold Hermes session) |
| 09:58:06.702 | provider turn of run 31 started |
| 09:58:06.786 | second message, run ordinal:32 requested as its own run; the composer shows "1 queued" with "Send now" |
| 09:58:18.467 | run 31 completed, with an answer to the first message only |
| 09:58:18.580 | provider turn of run 32 started; it answers the second message separately |
Nothing is lost or duplicated: each message is stored once, and both got an answer. But the person saw "working" for about 5 s and still got two turns instead of one.
The same check on a warm session two minutes later passed: the second message was sent 3.4 s after the first, shown with the "Steer" label once, and answered in the same run ordinal:33 (one provider turn, 09:59:46.273–10:00:11.837).
Why
resolveDeliveryIntent queues a follow-up while the active run is preparing or starting, or while no provider turn of its active attempt is running (apps/server/src/orchestration-v2/CommandPolicy.ts:172-179 on main at 5176bd32). The provider session's capabilities do advertise supportsActiveSteering: true, so outside that gap the same message steers (:182-184). Which of the three conditions held at 09:58:06.786 is not verified: the projection keeps no event log on this instance (orchestration_v2_events is empty).
This rule is upstream's: CommandPolicy.ts differs from v0.1.6 only by the overlay's pending-request rule (waitsOnPendingRequest, :92-100 and :109, #139). So the rebuild did not introduce it. Buddy's surface makes it more visible, because here the composer showed "working" about 5 s before the provider turn started, and the cold start of a Hermes session takes seconds.
Possible directions (not decided)
- Hold a follow-up sent in that gap and deliver it as a steer once the provider turn of the same attempt is running, instead of queuing it as a new run. This would apply when the provider advertises
supportsActiveSteering. - Or let the Buddy surface show that the message will follow as its own turn, so "working" does not promise a steer.
Either needs a test that sends a follow-up between the run's request and its provider turn start on a steering-capable provider.
Found during the Ring 0 acceptance in #138 and #148; it does not block the people's wave there (recorded in #148).
🤖 Generated with Claude Code
Activity
fix(server): a steer waits behind a turn that waits on the person
fix(server): a steer waits behind a turn that waits on the person
fix(server): a steer waits behind a turn that waits on the person
fix(server): a steer waits behind a turn that waits on the person
fix(server): a steer waits behind a turn that waits on the person
fix(server): a steer waits behind a turn that waits on the person
fix(server): a steer waits behind a turn that waits on the person
immakermatty commentedon Nov 8, 2026
Retest on stable
0.1.7-lazurio.1, Buddy's chat on Ring 0, 2026-11-08 17:43 UTC: still two turns on a cold session. The T3 half works, but the steer is refused before Hermes admits the prompt. Timeline and DB reads are in #138 (comment of today).What
0.1.7fixed works here too. The second message, sent at 17:43:14.825 before any provider turn, was queued withsteersRunId= the first run and promoted to a steer at 17:43:16.922, 61 ms after provider turn 36 started (Orchestrator.ts:9497oncab18ad9). The timeline showed it as "Steer". The same check passed for Codex on a cold session (#172).Why it still became a second turn on Buddy:
_lazurio/steeranswersnot_runningwhile this editor's prompt has no admission or its receipt is notstarted(lazurio_active_steer.pyL93-L96 and L123-L125 atv0.22.4-lazurio.6). Which of the two held is not verified, because anot_runninganswer leaves no log line.packages/provider-acp/src/server/adapter.ts:8400-8410). Turn 36 settled at 17:43:26.172, 9.3 s after it started, so within that wait. The refusal then becamerefused: true(:8411-8417), whichProviderTurnControlService.ts:416-419marks asturnCompleted.EffectWorker.ts:266-289re-dispatched the message as the next turn (run 38, same message id, provider turn 37).So a message lands in the turn only if that turn runs longer than 10 s after the refusal; then the effect's retries steer it. A short turn on a cold session gives two answers. Nothing is lost or duplicated: the text is stored once in T3 and once in Hermes. Compared with
0.1.6this is no regression: there it was two turns as well, shown as "1 queued". Now the label says "Steer" and the person still gets two answers.The same Hermes refusal is tracked for a prompt queued behind another turn in Lazurio/hermes-agent#23. Its note that "a prompt that starts within that time gets the message" does not hold for a turn that settles within the 10 s.
Possible directions (not decided):
not_runninganswer with a short retry of_lazurio/steer, and turn the refusal into the next turn only once the prompt has settled. Test: the first steer is refused before admission, a retry after admission lands, and the result is one provider turn.queuedso T3 can tell "not yet" from "ended".Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · bot c59bfd74-62a6-4eaa-88dd-5e19f1b3032e
🤖 Generated with Claude Code