Skip to content

chat(): maxIterations doesn't bound tool calls — one turn can fan out unbounded parallel calls #964

Description

@tombeckenham

Problem

maxIterations reads like a step/tool-call budget but counts model turns, not tool calls. A single turn can emit unbounded parallel tool calls, so the budget never trips.

Repro

agentLoopStrategy: combineStrategies([
  maxIterations(20),
  untilFinishReason(['stop']),
])

Expected ~20 tool calls. Got ~160, blowing a 300s serverless timeout. The model emitted ~160 calls across a few turns; iterationCount only saw a handful. ~99% of the wall clock was the model generating the fat multi-call turns — tool execution was negligible.

Root cause (3 layers)

  1. iterations ≠ tool calls. One assistant turn can carry an array of parallel tool calls; the loop counts turns.
  2. No tool-call count in strategy state. AgentLoopState exposes only { iterationCount, messages, finishReason } (packages/ai/src/types.ts:833) — so maxToolCalls is unbuildable in userland.
  3. Budget checked only between turns. shouldContinue() runs the strategy at turn boundaries (packages/ai/src/activities/chat/index.ts:1936); a single runaway turn is never inspected mid-flight.

Proposed

  • Add cumulative toolCallCount (and per-turn lastTurnToolCallCount) to AgentLoopState.
  • Ship a maxToolCalls(n) strategy.
  • Cap parallel fan-out per turn (bound calls executed in one turn — the direct fix for the 160-in-one-turn case).
  • Optional: a deadline(ms) / wall-clock-aware strategy, since call-counting alone can't catch a single slow turn.
  • Docs note: iterations ≠ tool calls when parallel tool use is enabled.

Minimum to close the report: AgentLoopState.toolCallCount + per-turn fan-out cap. Needs E2E coverage (fixture emitting a fat parallel-tool-call turn) + docs update per repo convention.

Seen on @tanstack/ai@0.28.0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions