0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
27 small wins to finish your pathNext lesson →

Capping a run with max_turns

max_turns is a limit on how many model and tool cycles a single run may take before the SDK stops it with an error.

Last updated: 28 Sep, 2026 · openai-agents 0.22.3

A run loops: the model answers, and if it calls a tool the SDK runs the tool and calls the model again. Each model call is one turn. If the model never gives a final answer the loop would run forever, so max_turns caps it and raises MaxTurnsExceeded. The default cap is 10.

The max_turns argument and MaxTurnsExceeded

python
from agents import Runner, MaxTurnsExceeded

try:
    Runner.run_sync(agent, "go", max_turns=3)   # stop after 3 turns
except MaxTurnsExceeded as e:
    print("stopped:", e)

A model that always calls a tool

To force the cap the stand-in returns a tool call every time and never a final message, so the loop can only end at the cap.

python
class LoopModel(Model):
    async def get_response(self, system_instructions, input, model_settings, tools,
                           output_schema, handoffs, tracing, **k):
        call = ResponseFunctionToolCall(id="fc", call_id="c1", name="ping",
                                        arguments="{}", type="function_call")
        return ModelResponse(output=[call], usage=Usage(), response_id=None)

A tool for it to call

The model asks for a tool named ping, so the agent needs one. It returns a value each time, which sends the loop back to the model.

python
from agents import function_tool

@function_tool
def ping() -> str:
    "A tool that always returns pong."
    return "pong"

Running with a small cap and catching the error

Give the agent the tool and the looping model, run with max_turns=3, and catch the exception the cap raises.

python
agent = Agent(name="Looper", instructions="x", tools=[ping], model=LoopModel())
try:
    Runner.run_sync(agent, "go", max_turns=3)
except MaxTurnsExceeded as e:
    print("stopped:", e)

A loop stopped at three turns

The whole program in one file. The model always calls the tool, so the run can only end when it hits the cap.

Example
from agents import Agent, Runner, function_tool, set_tracing_disabled, MaxTurnsExceeded
from agents.models.interface import Model
from agents.items import ModelResponse
from agents.usage import Usage
from openai.types.responses import ResponseFunctionToolCall
set_tracing_disabled(True)

@function_tool
def ping() -> str:
    "A tool that always returns pong."
    return "pong"

class LoopModel(Model):
    async def get_response(self, system_instructions, input, model_settings, tools,
                           output_schema, handoffs, tracing, **k):
        call = ResponseFunctionToolCall(id="fc", call_id="c1", name="ping",
                                        arguments="{}", type="function_call")
        return ModelResponse(output=[call], usage=Usage(), response_id=None)
    async def stream_response(self, *a, **k):
        raise NotImplementedError

agent = Agent(name="Looper", instructions="x", tools=[ping], model=LoopModel())
try:
    Runner.run_sync(agent, "go", max_turns=3)
except MaxTurnsExceeded as e:
    print("stopped:", e)

Why the run raised MaxTurnsExceeded

  • Each turn the model returns a tool call, the SDK runs ping, and it calls the model again.
  • No turn ever returns a final message, so the loop cannot end on its own.
  • At the third turn the run reaches max_turns=3 and raises MaxTurnsExceeded, whose message names the limit that was hit.

Default cap vs a small cap

max_turnsEffect on this looping modelWhen to use it
10 (default)Loops ten times, then raisesNormal runs that end well before ten
3Loops three times, then raisesTesting a limit or bounding a risky loop

When to lower max_turns

  • Bounding an agent that might keep calling tools without settling on an answer.
  • Failing fast in a test instead of waiting for the default ten cycles.
  • Protecting a run from a tool loop that has no natural end.
Watch out. The default max_turns is 10. A model that always calls a tool never returns a final answer, so it will always hit the cap. Give the model a path to a final message when you want the run to finish on its own.
Try it yourself
  • Raise max_turns to 5 and confirm the message names the new limit.
  • Make LoopModel return a final message on the second call and watch the run finish.
  • Remove the try block and read the raised MaxTurnsExceeded.

This is what real progress feels like.