Skip to content

fix(subagents): recover a best-effort answer when a worker loops to its step cap#50

Merged
albanm merged 1 commit into
mainfrom
fix-subagent-loop
Jun 29, 2026
Merged

fix(subagents): recover a best-effort answer when a worker loops to its step cap#50
albanm merged 1 commit into
mainfrom
fix-subagent-loop

Conversation

@albanm

@albanm albanm commented Jun 29, 2026

Copy link
Copy Markdown
Member

When a sub-agent exhausts its stepCountIs(10) budget while still calling tools
(finishReason: 'tool-calls'), the orchestrator used to discard the run and report a bare
truncation notice — losing a result the worker often already had in hand.

Now that finish reason triggers one final close-out turn with no tools
(SUBAGENT_CLOSEOUT_PROMPT via generateText): the model can't loop, so it synthesizes a
best-effort answer from its own transcript. That answer reaches the lead via
subAgentModelOutput, prefixed with SUBAGENT_PARTIAL_PREFIX so it's treated as possibly
incomplete. If the close-out call itself fails, it falls back to the standalone step-limit notice.

  • mock-model: loop forever / close-out seams + per-step tool-call-id salting so a
    multi-step loop gets distinct ids.
  • docs/architecture/sub-agents.md: document the step-budget close-out path.
  • unit + e2e coverage for the recovered-answer and empty-fallback cases.

Why: a weak worker that keeps calling tools after it already has the answer would hit the
step cap and have its work reported as a failure; this preserves the result, flagged partial.

Heads-up: adds one bounded generateText call on the step-cap path, and changes what the
lead receives on that path (a partial answer instead of a fixed notice). Unrelated: the
regenerated put-req/.type artifacts drop a stale moderator-description sentence to match the
committed schema source.

…ts step cap

When a sub-agent exhausts its stepCountIs(10) budget while still calling
tools (finishReason 'tool-calls'), the orchestrator previously discarded the
run and reported a bare truncation notice — a result the worker often already
had in hand was lost.

Now that finish reason triggers ONE final close-out turn with no tools
(SUBAGENT_CLOSEOUT_PROMPT via generateText): the model cannot loop, so it must
synthesize a best-effort answer from its own transcript. That answer is carried
as the trailing message content (flagged stepLimitReached) and surfaced to the
lead via subAgentModelOutput prefixed with SUBAGENT_PARTIAL_PREFIX so it is
treated as possibly incomplete. Only when the close-out call itself fails does
it fall back to the standalone step-limit notice.

- mock-model: add "loop forever" / close-out seams and salt tool-call ids per
  step so a multi-step loop gets distinct ids.
- docs/architecture/sub-agents.md: document the step-budget close-out path.
- unit + e2e coverage for the recovered-answer and empty-fallback cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the fix label Jun 29, 2026
@albanm albanm merged commit a86faad into main Jun 29, 2026
4 checks passed
@albanm albanm deleted the fix-subagent-loop branch June 29, 2026 18:31
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.

1 participant