Bound the agent loop

Use ToolLoopAgent and give it a stopWhen it will hit.

AI SDK docsai-sdk.dev
This default is a safety measure to prevent runaway loops that could result in excessive API calls and costs.
Agents · Loop ControlRead docs ↗

From ai-sdk and the Building Agents docs.

A model that keeps searching

Press Run agent, then untick the box and run again.

No step limitBad
0 stepsIdle
    Tokens 0Cost $0.00
    stopWhen: isLoopFinished()
    BoundedGood
    0 stepsIdle
      Tokens 0Cost $0.00
      stopWhen: [isStepCount(8), hasToolCall('done')]

      import { ToolLoopAgent, isStepCount, hasToolCall, tool } from 'ai';
      import { z } from 'zod';
      
      export const researchAgent = new ToolLoopAgent({
        model: 'anthropic/claude-sonnet-5',
        instructions: 'Research the question. Call done when you can answer.',
        tools: {
          search: tool({
            description: 'Search the web',
            inputSchema: z.object({ query: z.string() }),
            execute: async ({ query }) => searchWeb(query),
          }),
          done: tool({
            description: 'Call once you can answer',
            inputSchema: z.object({ answer: z.string() }),
            execute: async ({ answer }) => answer,
          }),
        },
        stopWhen: [isStepCount(8), hasToolCall('done')],
      });
      
      const { text, steps } = await researchAgent.generate({ prompt });

      Why it matters, pros and cons

      Why it matters

      Every step is a paid model call on a growing context. A loop with no cap costs whatever the model decides.

      Pros

      • Worst-case cost is known before the run.
      • hasToolCall('done') ends the run as soon as it can.
      • The agent owns the loop: no hand-rolled while.

      Cons

      • Too low a cap cuts off honest long tasks.
      • Hitting the cap is not an answer; check the last step.
      • isLoopFinished() only when a person can stop it.