Skip to content

Buddy: a follow-up sent while a cold session's turn starts becomes a second turn, not a steer #170

Open

Description

@immakermatty

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

  1. immakermatty commented on Nov 8, 2026

    @immakermatty
    Author

    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.7 fixed works here too. The second message, sent at 17:43:14.825 before any provider turn, was queued with steersRunId = the first run and promoted to a steer at 17:43:16.922, 61 ms after provider turn 36 started (Orchestrator.ts:9497 on cab18ad9). 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:

    1. Hermes refused the steer, because its prompt had not started yet. The gateway logged the first message only at 17:43:16.967 and started the agent turn at 17:43:17.958. _lazurio/steer answers not_running while this editor's prompt has no admission or its receipt is not started (lazurio_active_steer.py L93-L96 and L123-L125 at v0.22.4-lazurio.6). Which of the two held is not verified, because a not_running answer leaves no log line.
    2. The adapter waits for the turn to settle, not to start. After a refusal it waits up to 10 s for the prompt to settle (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 became refused: true (:8411-8417), which ProviderTurnControlService.ts:416-419 marks as turnCompleted. EffectWorker.ts:266-289 re-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.6 this 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):

    • In the adapter: while the prompt is not settled, follow a not_running answer 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.
    • In Hermes (hermes-agent#23, option 1 or 3): hold a steer that names this editor's latest prompt until its admission starts, or answer queued so T3 can tell "not yet" from "ended".

    Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · bot c59bfd74-62a6-4eaa-88dd-5e19f1b3032e

    🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions