Command Palette

Search for a command to run...

Hectal
PHASE 8Advanced ~26 min· topic 7 of 9

Topic 8.7

Multi-Step Loops

In one line

An answer can take several model calls: each one is a step. stopWhen sets the most steps allowed, the UI can show each step as it happens, a model that keeps asking for tools must be stopped by a limit, and every step re-sends the conversation, so cost grows with each one.

Think of it like this

A detective who needs the receipt first, then the shop's opening hours, then the witness list. Each clue leads to the next question, and the detective cannot write the report until the last one. But a detective who keeps asking for one more clue forever never closes the case. A multi-step loop is that investigation, and the step limit is the boss who says "report by Friday".

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Step
One call to the model. It can end in text, or in tool calls that start the next step.
stopWhen
The option that says when the loop stops. Usually stepCountIs(n).
stepCountIs(n)
A stop condition that is true once n steps have run.
step-start
A part that marks where a step begins inside an assistant message.
finishReason
Why a step ended: stop (the model is done), tool-calls (it wants tools), length (it ran out of room).
totalUsage
The tokens used by all steps of one answer, added together.
Runaway loop
A loop where the model keeps asking for tools and never answers.

Step by step

01Show the steps

The first example needs two tools one after the other. The page builds one line per step by splitting the message at each step-start. Read the console: step 1 is working, then done, then step 2 appears, and finally step 3 is the answer being written.

Show the stepsdiagram
Rendering diagram…

02A loop that never ends

The second example uses a model that always asks for checkPayment, and the tool always answers "pending". The limit is 3. The server prints three calls, then "loop ended after 3 steps, finish reason: tool-calls". The page counts three tool calls and no text, so it shows a message that says what happened and what to do.

Remove the stopWhen line (the break-it) and the loop stops after one step. With a limit set too high, it would run longer and cost more.

03Count the cost

The third example prints the token numbers of every step. Step 1 sent about 21 tokens, step 2 about 109 and step 3 about 173. The growth is not the model getting wordier: each step carries everything before it. (These numbers come from the stand-in model's estimate, not from a real tokenizer; real providers report their own counts.)

04Stop on purpose, not only on a count

stopWhen takes a list, and the first condition that is true ends the loop. stepCountIs(4) caps the length, and hasToolCall("cancelOrder") ends the loop after the step in which that tool was called. A scratch run showed the tool's execute still ran, the loop then stopped with finishReason: tool-calls, and the model wrote no text. So your UI must produce the closing sentence itself. Use this kind of condition when a specific tool is the natural end of the work.

Try it yourself

  1. 1

    Lower the limit

    In the first example change stepCountIs(5) to stepCountIs(2).

    The page stops after step 2 with no answer text. A limit lower than the chain you need leaves the user with tool lines and nothing else.

  2. 2

    Make the loop longer

    In the second example change stepCountIs(3) to stepCountIs(6).

    The server prints six calls. The page now says "After 6 lookups". Each extra step was another model call you paid for, with nothing gained.

Code & diagrams

Showing each step

Step 2 only exists because step 1's result was needed to decide it. The last line is the model writing the answer.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Step 1: Checking your order…
Step 1: Checking your order (done)
Step 1: Checking your order (done) | Step 2: Checking the return policy…
Step 1: Checking your order (done) | Step 2: Checking the return policy (done) | Step 3: writing the answer
A loop that hits the limit

The loop stops because of the limit, not because the model finished. The page notices that there are tool calls and no text, and says so.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

(server) checkPayment call number 1
(server) checkPayment call number 2
(server) checkPayment call number 3
(server) loop ended after 3 steps, finish reason: tool-calls
page: 3 tool calls so far | After 3 lookups I still have no clear answer. Please try again later or contact support.
What each step costs

Token counts here are the stand-in model's estimate (prompt length divided by 4). A real provider reports its own numbers, but the shape is the same: input grows with every step.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

step finished | calls: lookupOrder | reason: tool-calls | tokens in: 21 out: 0
step finished | calls: returnPolicy | reason: tool-calls | tokens in: 109 out: 0
step finished | calls: none | reason: stop | tokens in: 173 out: 19
steps: 3
total tokens in: 303 out: 19
answer: ORD-1042 arrived 3 days ago and returns are accepted for 7 days, so yes, you can still return it.

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Remove stopWhen

In the second example delete the line stopWhen: stepCountIs(3), // without this line the SDK stops after ONE step.

terminal
$ Console
── what you'll see ──
(server) checkPayment call number 1
(server) loop ended after 1 steps, finish reason: tool-calls
page: 1 tool calls so far | After 1 lookups I still have no clear answer. Please try again later or contact support.

Myth vs fact

Myth

A higher step limit makes answers better.

Fact

It only allows longer chains. Good descriptions and fewer, clearer tools make answers better. A high limit mostly makes bad loops expensive.

Myth

Steps cost the same each.

Fact

Each step resends the conversation plus earlier tool results, so later steps cost more input than earlier ones.

Myth

If the loop stops at the limit, the model gave its best answer.

Fact

It stops mid-investigation. The last step may have asked for a tool and produced no text at all.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Log finishReason for every answer. A rising share of tool-calls endings means loops are being cut off at your limit, which is a bug in descriptions or tools, not a reason to raise the limit.

  • ▸

    stepCountIs is one stop condition. stopWhen also accepts a list or your own function, so you can stop on a budget or on a specific tool having run.

  • ▸

    Add a server timeout around each execute, and pass req.signal as the abortSignal of streamText, so a user who presses Stop ends the whole loop and its tool calls stop being paid for.

  • ▸

    Keep tool results short. A 5,000-token result is re-sent to the model in every later step.

Remember this

  1. 1

    The problem this solves: Some questions need one tool's result before the next tool can even be chosen: "Can I still return ORD-1042?" needs the delivery date from lookupOrder, then the window from returnPolicy, then the answer. Each model call is a step. Without a limit, a confused model could call tools forever, and each call costs time and money.

  2. 2

    **stopWhen.** streamText keeps looping while the model asks for tools, until a stop condition is true. stopWhen: stepCountIs(5) means "at most 5 steps". The default is a single step: the loop ends after the first model call even if it asked for a tool, so the user gets a tool result and no text (Topic 8.1 showed this). (The ToolLoopAgent class has a different default, 20 steps, according to its type comments. Do not assume the same default across APIs: set stopWhen yourself.)

  3. 3

    Choosing n. Count the longest honest chain your tools need and add a little room. For Mitra, lookup then answer is 2, lookup then policy then answer is 3. A limit of 4 or 5 is plenty. A limit of 50 is not a safety net, it is a bill.

  4. 4

    What the browser sees. The assistant message contains a step-start part at the start of each step, followed by that step's tool parts and text. Split the parts at step-start to show "Step 1: checking your order… (done)", "Step 2: checking the return policy…", and so on. Hide the steps once the answer is done, if you like (Topic 8.4).

  5. 5

    A loop that never ends. When the model keeps asking for the same tool (the payment is always "pending"), the loop runs until the limit and then stops. The last step ended with finishReason: tool-calls, which means the model had asked for yet another tool and got no chance to write text. The message then has tool parts and no text. The UI must notice this (status is ready, tool parts exist, no text) and say so in words, not leave an empty bubble.

  6. 6

    Cost of each step. Every step sends the whole conversation again, plus all earlier tool calls and results. Input tokens grow with each step. A three-step answer therefore costs more than three times a one-step answer in input. Each result you return also becomes input for every later step, so return little. onStepFinish and result.totalUsage give the numbers.

  7. 7

    Prevent waste. Give tools clear descriptions so the model picks the right one the first time. Return short results. Set stopWhen low. Show users a stop button (Phase 3) for long chains, and put a server timeout on every tool.

Explain it without notes

01

What is a step, and what ends a multi-step loop?

02

Why does the default of one step give the user an empty answer when a tool is used?

03

How should the UI behave when the loop stops at the step limit without any text?

04

Why does a three-step answer cost more than three one-step answers?

Practice

01

Write a stopWhen that stops after 4 steps or as soon as cancelOrder has been called.

02

Add the step count to the page as "Step n of at most 5".

Trade-offs

  • ↔

    A larger limit supports harder questions and makes a runaway loop cost more. Start at the longest chain you can name, plus one.

  • ↔

    Showing every step is honest and noisy. Show steps while they run and fold them away when the answer is complete.

Done when you can

  • I can set stopWhen and explain the default of one step.

  • I can split a message into steps at step-start.

  • I can detect and explain a loop that stopped without text.

  • I can explain why later steps cost more input.

  • I can name three ways to avoid wasted steps.