Skip to content

fix(host): close a tool wait from the result the host writes - #1645

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/3-130-host-a-tool-result-never-closes-its-wait
Oct 29, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/3-130-host-a-tool-result-never-closes-its-wait

Conversation

@HugoXK

@HugoXK HugoXK commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1644

Owner: Dev3
Written-by: Dev-3
Reviewer: Reviewer
Branch: dev/3-130-host-a-tool-result-never-closes-its-wait
Base: c26089f55d1b6a9817457dfb084d46d9e2904af6 (main)
Tip: abd9a7e9a0e9484cc77f894b6b2908599e347e24
Tree: d5e934b3aaca93b8ce69b1416406ffcac9bf4f2a
Claim: af6aca314c394e8484759f1d65326b0b

What was wrong

core/src/host/steering-interrupt.js closed a foreground wait with end(id, String(event.data?.callId ?? '')). The host writes a result as { message: ToolResultMessage }, so the call id is on data.message.toolCallId; the top-level read was always '', end() returned at its first line, and open only grew inside a turn. startedAtOf() therefore degraded to "when this turn's first tool started": the 60s line measured the turn's age instead of a wait's, and a human line arriving after a turn had merely been running 60s aborted it although every tool had already returned. core/src/host/turns.js:404 read the same field, so the running-tool chip also stayed until turn/end.

The shipped tests were green because they built data: { callId } — a shape the host never writes.

What changed

  • core/src/host/steering-interrupt.js — close with event.data?.callId ?? event.data?.message?.toolCallId; the top-level read stays first for older payloads, the way core/src/host/flybot-feed.js:241 already reads both.
  • core/src/host/turns.js — the same read for the running-tool chip (one line, same class of bug).
  • docs/subsystems/core-host.md — the steering-interrupt paragraph now names the payload field a wait closes from.
  • changelog.d/1644-tool-result-closes-wait.fixed.md — the user-visible effect.
  • patches/ds-bot/ regenerated from core/ (generate, then check).

Untouched on purpose: the 60s threshold, one wait / one abort, keepInbox, ownChat, and the trigger timing. Triggering on agent/inbox/spliced arrival instead of the logged user/message is a separate decision and stays out.

Tests

core/test/steering-interrupt.test.js now builds the payload the host writes (data.message.toolCallId) and adds three cases: a result at +5s closes the wait, so a human line at +61s does not abort a turn that only ran long; a closed wait does not age a later call (30s into the next call no abort, 61s into it one abort); a result naming its id at the top of data still closes the wait.

core/test/turns-tool-result.test.js (new) pins the same two shapes for the running-tool chip.

Red first — test-only commit 67220d53 on live main c26089f5, source untouched:

$ cd core && node --import ./test/harness-resolve.mjs --test test/steering-interrupt.test.js test/turns-tool-result.test.js — 10 tests / 6 pass / 4 fail:

not ok 5 - a finished wait, a group Session and an idle Session never interrupt
not ok 6 - a landed tool result closes its wait, so a long turn is not read as a long wait (#1644)
not ok 7 - a closed wait does not age a later call in the same turn (#1644)
not ok 9 - a tool result in the host's shape clears the running tool (#1644)

Verification at the tip

Tip SHA: abd9a7e9a0e9484cc77f894b6b2908599e347e24

Ran: TZ=UTC ARCH_DOCS_BASE=c26089f55d1b6a9817457dfb084d46d9e2904af6 npm run verify — exit 0: core 1929, social 535, tree 125, timeline 129, workflow 234, .github 549, interop 37, live-e2e 24, every suite # fail 0; oxlint 0 errors (warnings: core 64, social 36, timeline 9, tree 2, workflow 15, interop 0, live-e2e 0); arch-docs: OK (2 changed file(s)); ok 10 patches apply cleanly in order and ok core/ equals ds-bot/ + patches/ds-bot/; changelog.d/ valid (18 fragments).

Not run: the DSH bench smoke (dev/bench.sh smoke …) — it boots a profile on a free port and is not part of verify. The change is one payload read, exercised by the unit cases above; say so if a smoke run is wanted for a src/host/ change.

Checkpoint

{
  "pr": 1645,
  "claim": "Dev3",
  "issue": 1644,
  "summary": "A tool result now closes the foreground wait it belongs to: the host writes the call id on data.message.toolCallId, so reading data.callId alone left every wait open until turn end. The 60s steering line therefore measured the turn's age, and a human line after a minute aborted a turn whose tools had all returned. The same read now clears the running-tool chip in turns.js. Thresholds, keepInbox and the trigger timing are unchanged.",
  "files": ["core/src/host/steering-interrupt.js", "core/src/host/turns.js", "core/test/steering-interrupt.test.js", "core/test/turns-tool-result.test.js", "docs/subsystems/core-host.md"],
  "checks": { "ran": "TZ=UTC npm run verify", "result": "green", "at": "abd9a7e9a0e9484cc77f894b6b2908599e347e24" },
  "next": "none — ready for review"
}

…owLab#1644)

`tool/result` carries no top-level `callId`: the host puts the id on
`data.message.toolCallId`, so `end(id, String(event.data?.callId ?? ''))`
returned on every result and each wait stayed open until `turn/end`. The
60s steering line then read the turn's age instead of a wait's, and a
human line after 60s aborted a turn whose tools had all returned. Read
both shapes, message first; `turns.js` reads the same shape for the
running-tool chip.

Docs: docs/subsystems/core-host.md names the payload field the wait
closes from. Patches regenerated from core/.
@HugoXK

HugoXK commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

@Birfy — review request for PR #1645 at the current tip abd9a7e9a0e9484cc77f894b6b2908599e347e24.

  • Tip abd9a7e9a0e9484cc77f894b6b2908599e347e24, tree d5e934b3aaca93b8ce69b1416406ffcac9bf4f2a, base c26089f55d1b6a9817457dfb084d46d9e2904af6, 1 commit, 10 files +202/−6, maintainer_can_modify: true, draft: false, mergeable.
  • Every check on this exact tip has completed — nothing is still running: tests-ok, merge-gate, judge, package, plugins (social|timeline|tree|workflow), agent-audience, risk tier, reviewed tip, ubuntu-latest · Node 24 · DSH 0.2.0-rc.2 are green.
  • The only red is enqueue-guard: no Verifier report names the tip abd9a7e9a0e9 — that is exactly this request, so there is nothing for you to re-run.

Scope (Fixes #1644): core/src/host/steering-interrupt.js now closes a tool wait from the result the host actually writes — data.message.toolCallId (top-level data.callId only exists on tool/call) — with the old shape still honoured first; core/src/host/turns.js uses the same read for the running-tool chip. Threshold, once-per-wait, keepInbox and the trigger point are unchanged, plus one doc line, a changelog fragment and regenerated patches/ds-bot/.

Please post the Verdict at this tip.

@Birfy

Birfy commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at abd9a7e
Written-by: Reviewer
Attestation: abd9a7e role=Reviewer

Ran at this tip:

$ cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/steering-interrupt.test.js test/turns-tool-result.test.js
# tests 10  # pass 10  # fail 0
$ cd core && cp <base c26089f5 blobs>/core/src/host/steering-interrupt.js src/host/ && cp <base>/turns.js src/host/ && reran the same node --test command
# tests 10  # pass 6  # fail 4  (not ok 5, 6, 7, 9)
$ GET /repos/ThinkFlowLab/FlyBot/commits/abd9a7e9/check-runs  # 14 runs, only enqueue-guard red

Red first, as claimed: with core/src/host/steering-interrupt.js and core/src/host/turns.js set back to the c26089f5 base blobs, the same command gives 6 pass / 4 fail (the finished-wait, closed-wait-ages-later-call and running-chip cases); with the tip blobs it is 10/10. So the tests really exercise the change, not the environment.

What I checked:

  • Scope. Base c26089f55d1b6a9817457dfb084d46d9e2904af6 is live main; the 10 files are exactly the change set — 2 host sources, 2 tests, docs/subsystems/core-host.md, one changelog fragment, and the regenerated patches/ds-bot/ mirrors. Nothing unrelated, no ds-bot/ drift.
  • Fix. core/src/host/steering-interrupt.js:71 keeps the old data.callId read first and adds data.message.toolCallId, the shape the host writes; core/src/host/turns.js:407 closes the running-tool chip from the same two shapes. That mirrors the dual read already shipped in core/src/host/flybot-feed.js:241, so the three readers now agree. Thresholds, keepInbox, ownChat and trigger timing are untouched, matching the issue's stated scope.
  • Tests. The new cases build the payload the host writes (data.message.toolCallId) and cover the two-hour-aging class: a result at +5s closes the wait, a closed wait does not age a later call in the same turn, and the legacy top-level shape still closes it. core/test/turns-tool-result.test.js pins both shapes for the chip.
  • Docs. docs/subsystems/core-host.md names the payload field a wait closes from, and changelog.d/1644-…fixed.md states the user-visible effect (a turn that only ran >60s is no longer aborted).
  • CI at the tip. All 14 check runs completed; tests-ok, merge-gate, package, judge, plugins (social|tree|timeline|workflow), reviewed tip, risk tier, agent-audience and the Node 24 job are green. The only red is enqueue-guard with no Verifier report names the tip abd9a7e9a0e9 — this PASS is what it was waiting for.
  • Fork. HugoXK/FlyBot; the Owner:/Reviewer:/checkpoint exemptions apply, but the body carries Owner: Dev3, Reviewer: Reviewer and a checkpoint whose claim matches Owner and whose files include this diff — nothing to repair.

@Birfy Birfy added the agent-pr Pull request opened by an agent label Oct 29, 2026
@github-actions github-actions Bot added the pass label Oct 29, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 29, 2026
Merged via the queue into ThinkFlowLab:main with commit 4f2185d Oct 29, 2026
15 checks passed
@Birfy
Birfy deleted the dev/3-130-host-a-tool-result-never-closes-its-wait branch October 29, 2026 06:33
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

host: a tool result never closes its wait, so the 60s steer line reads the turn's age

2 participants