---
title: "Controlling agent runs"
summary: "Production agents need caps, approvals, and visibility into each step."
track: "The loop"
day: 3
minutes: 16
author: "Akash Panchal"
url: https://ai-sdk-patterns.dev/learn/agents/day-3/multi-step
dateModified: 2026-08-29
---

# Controlling agent runs

Production agents need caps, approvals, and visibility into each step.

*The loop · Day 3: Multi-step agents · ~16 min. Written by [Akash Panchal](https://github.com/akashp1712).*

Canonical: https://ai-sdk-patterns.dev/learn/agents/day-3/multi-step

## After this topic

You will treat an agent run as something with a budget, a log, and a pause button — not a fire-and-forget loop. You will know when to cap steps, when to inspect them, and when execute must wait for a human.

## A budget, not a vibe

> stopWhen: stepCountIs(n) is not optional polish. It is the difference between a product and an unbounded bill.

A production agent needs a budget, visibility into each step, and guardrails on destructive tools.

You already have a loop and a cap. Production adds two more questions: can the user see what the agent is doing, and can it do damage without asking?

stopWhen: stepCountIs(n) is the budget. Each model step — a tool round or the final answer — counts as one. Weather-and-compare might need three. A research agent that browses might need ten, with a higher bill and a higher chance of getting lost.

Without a cap, a confused model can call the same tool until you notice. That is not “autonomy.” That is a missing if.

## See the steps

When you debug with generateText and tools, you get a steps array: which tool, which arguments, which result. Read it. “Why did it call getWeather twice?” is almost always sitting there in plain JSON.

In a streaming UI, tool calls arrive as parts on the assistant message. Show them. A spinner that hides the loop trains users to distrust the product. A line that says “Looking up Berlin…” is the agent being honest.

prepareStep (in the official agents docs) lets you change tools or messages between steps — for example, drop a tool after it has been used. You do not need it for the first agent. You need it when the tool list itself is a context problem.

## Pause before the side effect

For anything that sends an email, charges a card, or deletes a row, do not execute immediately. Pause, show the proposed call, require a click. That is the Human in the Loop lab — same stream, an extra round trip with the user.

Read-only tools (getWeather, searchDocs) can run on their own. Write tools (createTicket, refundCharge) wait. Split your map that way on purpose, not after the first angry customer.

The model still proposes the call. You still validate the schema. The new part is: execute is gated. The agent is not less useful. It is less dangerous.

## Trade-offs

A tight cap fails closed: the user sees an incomplete answer. A loose cap fails expensive. Prefer fail closed, then raise n when logs show real multi-step work.

Approving every tool call is friction. Approving none is how refunds go out at 3am. Draw the line at irreversible.

When the same agent must run from a route, a cron, and a job, ToolLoopAgent is the shared object. For a chat window, the Route Handler you already have is enough.

## Common questions

### What is “Controlling agent runs”?

Production agents need caps, approvals, and visibility into each step.

### What will I be able to do after this lesson?

You will treat an agent run as something with a budget, a log, and a pause button — not a fire-and-forget loop. You will know when to cap steps, when to inspect them, and when execute must wait for a human.

### How long does this lesson take?

About 16 minutes of reading. It is a free chapter in the AI SDK Patterns TypeScript course.

### When should I not use this?

A tight cap fails closed: the user sees an incomplete answer. A loose cap fails expensive. Prefer fail closed, then raise n when logs show real multi-step work.
