Skip to content

fix(agent): balance history when a tool-call wave aborts mid-flight - #146

Merged
hung12ct merged 1 commit into
mainfrom
fix/fatal-wave-history-balance
Aug 17, 2026
Merged

fix(agent): balance history when a tool-call wave aborts mid-flight#146
hung12ct merged 1 commit into
mainfrom
fix/fatal-wave-history-balance

Conversation

@hung12ct

Copy link
Copy Markdown
Owner

What & why

When the anti-loop detector aborts a turn mid-wave, the loop persisted the assistant message with its tool_calls and no tool results at all, and discarded every result that had already completed in that wave.

Two consequences. The saved transcript is unbalanced, which providers reject — the only reason it does not surface today is that pkg/llm/anthropic repairs history on every call. And completed tool work (a query that ran, a subprocess that finished) is silently thrown away, so the next turn's model never sees results the user already paid for.

Changes

  • synthesizeMissingToolErrors fills any call with no recorded result, leaving completed results untouched. Generalises the existing synthesizeDroppedToolErrors pattern, which covered only the per-turn budget-drop path.
  • The fatal path in runIterationOnce now drains ws.toolMsgs through appendToolResultsInOrder before saveSession, so history leaves the loop balanced by construction.
  • Skipped calls carry a reason naming the abort, so a resumed session does not read the gap as a silent success.

Deliberately not in this PR: removing the defensive PatchDanglingToolCalls call in pkg/llm/anthropic. It is now redundant for this path, but that is a critical-package edit and the two other callers (regenerate.go, loop_stream.go) have not been proven redundant.

Testing

  • TestFatalWave_SavesBalancedHistory — drives a 6-call wave of identical calls so the detector trips partway through, then asserts every tool_call in the saved history has a matching tool result, that at least one real result survived, and that the total is exactly 6. Verified to fail on the pre-fix code: tool_call "dup0" has no matching tool result in saved history (3 messages).
  • TestSynthesizeMissingToolErrors_PreservesCompleted / _FillsEmptyMap — gap-filling never overwrites a recorded result; an empty map still yields a balanced transcript.
  • gofmt -l . empty, make lint 0 issues, make build, make test, make test-race all clean.

Checklist

  • Title is a Conventional Commit
  • gofmt -l . prints nothing
  • make lint is clean
  • make build passes
  • make test (and make test-race) passes
  • Errors wrapped with a package prefix
  • CHANGELOG.md updated if this PR is cut as a release tag
  • No changes to pkg/llm/, pkg/history/, or pkg/telemetry/

The anti-loop fatal path saved the assistant message with its tool_calls
and no tool results, discarding results that had already completed.
@hung12ct
hung12ct merged commit 321750e into main Aug 17, 2026
2 checks passed
@hung12ct
hung12ct deleted the fix/fatal-wave-history-balance branch August 17, 2026 16:17
@hung12ct hung12ct mentioned this pull request Aug 17, 2026
8 tasks
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