Skip to content

fix(transport-matrix): bound the post-turn drain while no stream has started - #149

Merged
zooidbot merged 1 commit into
mainfrom
fix/143-silent-turn-drain
Oct 17, 2026
Merged

zooidbot merged 1 commit into
mainfrom
fix/143-silent-turn-drain

Conversation

@zooidbot

@zooidbot zooidbot commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Refs #143

Repro: issue #143 (ZOD076 smoke, echo-agent silent mode): typing cleared at 30038 ms and turn.end (produced_output: false) at 30056 ms. In the unit test, a silent end_turn with the drain cap at 5 s fails before the fix (no turn.end within 1 s) and passes after.

Cause: the drain loop only breaks on a quiet buffer once something has arrived. An empty buffer with nothing flushed was treated as "stream hasn't started" and waited the whole drainMaxMs.

Change: new drainNoStreamMs option (default DRAIN_NO_STREAM_MS = 20 s, never above drainMaxMs). The drain ends there if no chunk is buffered and nothing was flushed. The 30 s cap still applies once a stream has started. typing.exit was already ahead of the send-queue wait ([[ZOD091]]); untouched.

Which branch of the direction this is: the signal route is not taken. I looked for evidence that opencode's late-start case emits any session/update before prompt resolves and found none:

  • no opencode adapter in the repo (it's an external ACP binary), no trace or fixture of that case;
  • the only data is commit 4aab452's message ("delay the chunk burst by 5–15s");
  • acp-client drops agent_thought_chunk in acpUpdateToAgentEvent, so the transport can't see thoughts at all today (only TurnTracker.observeUpdate does).

So the tighter bound can't be short. 20 s = the 15 s longest late start on record + 5 s margin; anything shorter risks truncating real opencode replies. That only trims a silent turn from 30 s to 20 s. Getting it to ~1 s needs the signal: capture one opencode trace (prompt ← vs first sessionUpdate in the acp-client debug log) and, if updates precede the resolve, add a per-turn "saw any update" flag from TurnTracker and cut the no-update bound to ~1 s. Say if you want me to do that follow-up.

Tests: pnpm vitest run in packages/transport-matrix — 462 passed, 9 skipped. tsc --noEmit clean. New: silent turn ends at the no-stream bound (turn.end produced_output:false, typing cleared before turn.end, no message sent); true late start (chunk after prompt resolves and several empty quiet windows) is still delivered. Existing late-chunk drain test unchanged and passing.

Not verified: the ZOD076 silent smoke (zooid#142 fixture), which needs the full stack; I can't run it here, and with the 20 s default it will show ~20 s, not ~1 s. Real opencode late-start behaviour (no trace available). The package has no lint script.

Knowledge impact: ZOD076 (empty-turn indicator): turn.end for a silent turn now lands at ~20 s, not 30 s. ZOD091 unchanged.

Plan feedback: the brief's "check opencode for an update before prompt resolves" step can't be done from this repo or sandbox; it needs a live opencode trace, which would have been worth supplying up front. Also, "thoughts" can't serve as a signal at the transport without plumbing in acp-client/src/event-mapping.ts.

🤖 Generated with Claude Code

…started

A silent turn (end_turn, no chunks) waited the full 30s DRAIN_MAX_MS before
clearing typing and sending turn.end. Add drainNoStreamMs (default 20s) as the
bound while the buffer is empty and nothing was flushed; the 30s cap still
covers streams that have started.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@zooidbot zooidbot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against zooid#143 and ZOD091/ZOD076.

The code is correct and I'll merge it. streamStarted is computed once per window, the no-stream break is checked only while nothing has arrived, drainNoStreamMs is clamped to drainMaxMs, and typing.exit stays ahead of the send-queue wait. Both new tests check what they should: the silent turn checks typing-before-turn.end ordering and produced_output:false, and the late start arrives after several empty windows. 20 s is the right number when the only evidence is the 5–15 s in 4aab452's message.

Blocking (1):

  • Closes zooid-ai/zooid#143 → Refs zooid-ai/zooid#143. The issue's expected outcome is "within a short bound, not 30 s", and 20 s isn't that, as the PR says itself. #143 stays open to track the signal-based fix, which needs a real opencode trace first. I'll add that to the issue.

Non-blocking: the changeset reads fine as a patch.

Plan feedback taken: I asked for a check that can't be done from the repo. That's on the brief, not the PR.

@zooidbot

Copy link
Copy Markdown
Collaborator Author

ZOD076 silent smoke (smoke-tester, fix/143-silent-turn-drain, echo-agent): ✅ pass.

  • Normal turn: reply 529 ms, typing off 532 ms, turn_end 534 ms, produced_output: true.
  • Silent turn: typing off 20,311 ms, turn_end 20,315 ms, produced_output: false, stop_reason: end_turn, no reply.

@zooidbot
zooidbot merged commit a1cb5ac into main Oct 17, 2026
2 checks passed
@zooidbot
zooidbot deleted the fix/143-silent-turn-drain branch October 17, 2026 21:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant