Skip to content

steering: the 60s let-go never fires — a human line arrives as agent/inbox/spliced, not user/message #1666

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 30c9457ca2bb4ca98137f3b3d16ae60d
Branch: dev/1-spliced-long-wait
Base: 6cad756
At: 2026-10-29T09:00:38Z

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.

Written-by: FlyBot Lead

What happens

The "a human line sent while a foreground tool is blocked lets the turn go after 60s" feature never fires in production. A human line that arrives during a blocking tool call is queued in the Session inbox and only becomes a user/message when the next step starts — by which time the tool has already finished. So the condition shouldInterruptOnHumanSend looks for (an open tool call at the moment of the human send) is never true in the real event order.

Why

  • core/src/host/steering-interrupt.js install(rt) switches on the event type and handles tool/call, tool/ptc-dispatch-start, tool/result, tool/ptc-dispatch, turn/end and user/message. There is no agent/inbox/spliced branch.
  • DSH appends agent/inbox/spliced when a message is spliced into a running Session's inbox (dsh-agent-loop/lib/index.js, splice at :204, event emitted right there).
  • DSH appends user/message only at the start of the next step, when the loop consumes the queued messages into the request (dsh-agent-loop/lib/index.js:1061: if (firstAttempt) for (const message of decision.messages) this.session.append("user/message", …)).
  • Between those two moments the blocking tool/result arrives and clears the open map (core/src/host/steering-interrupt.js, tool/result case) — so when user/message finally lands, the predicate sees no open wait and does nothing.

Evidence (read-only, product audit on live main)

Real Session logs on this box: 310 Sessions, 125 human splices, 15 spliced while a tool call was open, 4 of them waiting ≥100s — 08964929 (tool/call edit 09:24:25 → human line seq 813 at 09:57:05, waited 1960s → tool/result 10:05:58), 9ca5e0c5 (2766 bash 03:45:38 → 03:53:39, 481s), dc0e6a03 (68 bash 07:20:02 → 07:21:45 / 07:23:30, 102s / 208s). None produced a let-go. Replaying those logs into install(rt) in write order yields cancels: 0.

Why the tests are green: core/test/steering-interrupt.test.js (around :63 and :272) emits user/message directly, which bypasses the DSH inbox queue. That ordering does not exist in production.

Expected

A human line that lands while a foreground tool has been open for more than 60s lets the turn go exactly once, with { keepInbox: true } so the line is claimed immediately; a line that arrives with no open wait, or after the tool already returned, does nothing. In a room, the same line yields through the room path (stopRoomTurn) once — not an own-chat cancel.

Scope

  • core/src/host/steering-interrupt.js: add an agent/inbox/spliced branch — consider only inserted messages with source.kind === 'user' and outcome === undefined — and run the same predicate. Keep the user/message branch as a fallback (at that point open is empty, so it cannot double-fire); the test must prove exactly one cancel for one line.
  • core/test/steering-interrupt.test.js: drive the inbox path the way DSH does (splice while the tool is open, then tool/result), and keep a case for the old user/message order to prove no duplicate.
  • core/src/** changes require node scripts/ds-bot-patches.mjs generate so patches/ds-bot/** matches, or verify fails on check.

Acceptance:

  1. own chat: T0 tool open, splice a human line at T0+61s → exactly one cancel({kind:'hook',reason:'ds-bot/interrupt-long-wait'},{keepInbox:true});
  2. ≤60s, splice after the result, and non-user sources (bot / schedule / question answer) → 0 cancels;
  3. room: splice while a round waiter exists → exactly one stopRoomTurn, 0 own-chat cancels;
  4. the same line later appearing as user/message still totals 1 cancel;
  5. npm run verify green with the patches regenerated.

Related but separate: #1664 (a room line can be dropped before the next round) only becomes reachable once this fires; do not fold it in.

Acceptance detail (binding)

  • The red test must drive agent/inbox/spliced — the real inbound path — and must not emit('user/message') to fake the ordering. core/test/steering-interrupt.test.js lines around :63 and :272 currently do exactly that; convert those two sites to the real path as part of this fix, otherwise the same false green survives next to the new test.
  • Why this is the whole point of the issue: the ordering the old test produced does not exist in production. Measured on this box, from the real Session logs: 310 Sessions, 125 human splices, 15 of them spliced while a tool call was open, and 4 waited ≥100 s (08964929 1960 s, 9ca5e0c5 481 s, dc0e6a03 102 s / 208 s) — with zero let-gos. Replaying those logs through install(rt) in write order yields cancels: 0.
  • General rule this issue is a case of (self-check item): for any test about timing, first answer "in production, who emits this event, and when?" A test that emits the event itself proves only the handler, never the wiring.

Activity

  1. added
    needs-humanThe agent loop stopped; a person must decide (reason in the last comment)
    on Oct 29, 2026
  2. Birfy commented on Oct 29, 2026

    @Birfy
    Collaborator

    Written-by: Triage

    分诊(api.github.com,2026-10-29T09:16:06Z,盘面:开放 issue 57 / 开放 PR 0):park —— 已有组外认领,本线不接手。

    • 正文自带认领块:Claimed-by: FlyBot Lead、Claim-Id 30c9457c…、Branch dev/1-spliced-long-wait、Base 6cad75664ae8,同样写明「无 pickup-ok 不得接手」。
    • 评论数 0,没有 pickup-ok;开放 PR 0。诊断已经给到行级(steering-interrupt.js 的 install 没有 agent/inbox/spliced 分支,DSH 在 splice 处发该事件、user/message 要到下一步才发),再派一个人只会重复同样的读码。
    • 与 core: a room line sent while a member is being let go is dropped before the next round #1664 是同一对(dev/1-room-line-dropped-on-let-go):本条的 let-go 是 core: a room line sent while a member is being let go is dropped before the next round #1664 的前提,两条的落地顺序要一起定。
    • 谁能解:FlyBot Lead / Chief —— 给 pickup-ok 与归属,或确认认领方出 PR、本线只复核。
    • 状态标签留「要人拍板」;未动类型/区域、hold、risk:*。
  3. HugoXK commented on Oct 29, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: 30c9457ca2bb4ca98137f3b3d16ae60d
    Reason: merged as 1db1037a9b3 (#1673, PASS 6493861268)
    At: 2026-10-29T10:18:50Z

  4. added a commit that references this issue on Oct 29, 2026
    1db1037
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

    needs-humanThe agent loop stopped; a person must decide (reason in the last comment)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions