Skip to content

feat(spawn): flag zero-tool-call completed runs as likely premature returns (#61) - #71

Merged
rz1989s merged 3 commits into
mainfrom
feat/61-zero-tool-run-signal
Aug 29, 2026
Merged

rz1989s merged 3 commits into
mainfrom
feat/61-zero-tool-run-signal

Conversation

@rz1989s

@rz1989s rz1989s commented Aug 29, 2026

Copy link
Copy Markdown
Member

Problem (#61)

A foreground implementer dispatched with maxTurns: 70 returned after its first turn with only a planning statement ("Let me read the relevant existing files first…"), zero tool calls, zero file changes — and the result shape was indistinguishable from a normal completion. The controller had to inspect the repo to discover nothing happened, and a re-dispatch of the identical task succeeded (so the task was feasible; the run just exited early).

Changes (surfacing-only by design)

  • engine/spawnSubagent.ts — counts executed tool calls (tool_execution_end) and surfaces toolCallCount on SpawnResult and on the run:ended journal event (post-hoc diagnosability, same rationale as fix(subagent): surface primary error when fallback retry masks it + journal failure reason (#59) #69's error field).
  • tools/subagent.ts — a completed run with toolCallCount === 0 gets its result text prefixed with [FLEET] zero-tool-call run — likely a premature return (#61); verify with git status/log before trusting this result. and exposes details.toolCallCount. Failed runs are unchanged (they already carry an error).

Design notes:

  • Warning, not error: the run genuinely completed; the controller decides. A zero-tool run made no side effects, so auto-retry stays a controller call (the issue lists auto-retry as an option; surfacing is the safe half — the trivial "reply ok" probe case shows why blanket auto-retry would be wrong).
  • The details.toolCallCount also gives lifecycle/workflow consumers the raw signal for later policy.

Tests

  • Engine (2): toolCallCount on SpawnResult + run:ended with 2 executed tools; zero-tool completed run → 0.
  • Tool (2): zero-tool completion → warning prefix present, original finalText preserved, details.toolCallCount === 0; run with a tool → no prefix, count 1.
  • 715/715 pass, pnpm typecheck clean.

Closes #61

…eturns

#61 dogfood finding: an implementer returned after a single planning
statement with zero tool calls and no turn-budget exhaustion, looking
like a normal (terse) completion. The controller had to inspect the repo
to discover no work was done.

- spawnSubagent counts executed tool calls and surfaces toolCallCount on
  SpawnResult + the run:ended journal event.
- The subagent tool prefixes a completed zero-tool result with a
  '[FLEET] zero-tool-call run - likely a premature return' warning and
  exposes details.toolCallCount, so the controller verifies (git
  status/log) before trusting the result. Failed runs are unchanged
  (they already carry an error).

Surfacing-only by design: a zero-tool run made no side effects, so the
retry/auto-retry question stays with the controller.
…CAL)

Review on PR #71 caught that the claude backend never emitted tool
events (mapClaudeEvent flattened tool_use into message content), so the
#61 zero-tool-call signal would have falsely flagged every completed
claude run as a premature return.

- mapClaudeEvents(line) returns ALL events a CC line implies: an
  assistant message with tool_use blocks yields message_end + one
  tool_execution_end per block (toolCallId/toolName from the block).
  mapClaudeEvent stays as a first-event compat wrapper (detector).
- ClaudeChildSession.onLine iterates the mapped events.
- ChildSessionEvent documents the tool-event fields (toolCallId/
  toolName/result/isError) that consumers previously cast for.

Tests: mapper events (multi-block, text-only, compat wrapper) + a
claude-path counting test through spawnSubagent (mapped CC line ->
toolCallCount >= 2).
…review NIT)

Claude Edit/Write blocks now carry their input through to the engine, so
claude children contribute to the #49 filesTouched report (extractTouched
Files reads the file_path arg). Previously claude was blind to #49.
@rz1989s
rz1989s force-pushed the feat/61-zero-tool-run-signal branch from ab5b873 to 9f3efd3 Compare August 29, 2026 04:54
@rz1989s
rz1989s merged commit 837a740 into main Aug 29, 2026
1 check passed
@rz1989s
rz1989s deleted the feat/61-zero-tool-run-signal branch August 29, 2026 04:56
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.

subagent: premature return after a single planning statement (zero work, no turn-budget exhaustion) — re-dispatch succeeds

1 participant