fix(agent): stop tool-call loop from scalar-vs-array tool inputs - #101
Open
sm0keyafterdark wants to merge 2 commits into
Open
fix(agent): stop tool-call loop from scalar-vs-array tool inputs#101sm0keyafterdark wants to merge 2 commits into
sm0keyafterdark wants to merge 2 commits into
Conversation
Weak/local models (e.g. qwen via Ollama/LiteLLM) emit a scalar where an
array-typed tool input is expected (e.g. TodoAdd {"content":"x"}). This
failed Zod validation, the tool never executed, no role:"tool" result was
produced, the todo store was never mutated, and the model looped re-issuing
the same call until the iteration cap.
Fixes:
- Accept scalar-or-array on todo tool inputs via `scalarOrArray`/`toArray`
(TodoAdd/TodoComplete/TodoRemove), and coerce scalars to arrays in the
tool-input repair pass as a general backstop for all array-typed params.
- Guarantee the tool-use contract: `ensureToolCallsResolved` injects a
synthetic error result for any assistant tool_call left unanswered before
the next model call.
- Add a no-progress circuit breaker (`hasRepeatedToolCalls`) as a second
`stopWhen` condition to bound identical-call loops well before the cap.
- Strip inline `<think>...</think>` reasoning both from replayed history
(context savings) and at the display layer, so empty think blocks no
longer clutter or bloat the transcript.
Adds unit tests for repair coercion, the loop guard, tool-call resolution,
and think-tag stripping.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
sm0keyafterdark
force-pushed
the
fix/agent-tool-call-loop
branch
from
July 15, 2026 21:25
c51e3af to
771c52b
Compare
Two defects surfaced by weak/local models (qwen via Ollama) that broke the
todo workflow mid-task:
1. A recoverable tool error wiped the todo store. `onError` in the transport
called `context.clearTodos()`, so a single malformed tool call destroyed
the user's todos mid-run — the model then reported "the todos are cleared
each turn" and restarted endlessly. Todos are now cleared only on normal
completion (onFinish).
2. TodoComplete/TodoRemove only accepted `ids` as an array, but the model
commonly sends a singular `id` and/or a numeric string (e.g.
`TodoComplete {"id":"1"}`). That failed validation, never executed, and
produced no tool result. These tools now accept `id` or `ids`, scalar or
array, and numeric strings (coerced to numbers); TodoStart likewise accepts
numeric-string ids. Shared helpers `idNumber`/`idListInput`/`collectIds`.
Adds schema tests covering the accepted shapes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
|
Hi @sm0keyafterdark, thanks for the PR! A few things I noticed during review:
Once these are fixed I will take another look :D |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Weak/local models (e.g. qwen via Ollama/LiteLLM) emit a scalar where an array-typed tool input is expected (e.g. TodoAdd {"content":"x"}). This failed Zod validation, the tool never executed, no role:"tool" result was produced, the todo store was never mutated, and the model looped re-issuing the same call until the iteration cap.
Fixes:
scalarOrArray/toArray(TodoAdd/TodoComplete/TodoRemove), and coerce scalars to arrays in the tool-input repair pass as a general backstop for all array-typed params.ensureToolCallsResolvedinjects a synthetic error result for any assistant tool_call left unanswered before the next model call.hasRepeatedToolCalls) as a secondstopWhencondition to bound identical-call loops well before the cap.<think>...</think>reasoning both from replayed history (context savings) and at the display layer, so empty think blocks no longer clutter or bloat the transcript.Adds unit tests for repair coercion, the loop guard, tool-call resolution, and think-tag stripping.