max_steps is the unified outer-loop step budget honoured identically by both tool-execution loops (OpenAI-native and LiteLLM). On the final permitted step the model is asked to wrap up, so you get a coherent answer instead of a hard cut.
Quick Start
How It Works
On the final permitted step both loops inject an internal user-role message so the model produces a coherent final answer:“You are approaching the maximum number of tool-use steps for this task. Stop calling tools now and provide your best final answer, summarising the work completed so far and clearly noting anything left incomplete.”The wrap-up message works on a local copy of the conversation, so it never leaks into the caller’s history.
last_stop_reason | Meaning |
|---|---|
"completed" | Task finished normally (also the default before the first run) |
"max_steps" | The max_steps budget was reached and the run was truncated |
"error" | The loop stopped because of an error |
Choosing a Value
Configuration Options
Full list of options, types, and defaults —
ExecutionConfig| Option | Type | Default | Description |
|---|---|---|---|
max_steps | Optional[int] | None | Unified outer-loop step budget honoured by both tool-execution loops. None → fall back to max_iter. Must be >= 1 when set. |
max_iter | int | 20 | Legacy per-loop iteration cap. Still used when max_steps is unset. |
max_tool_calls_per_turn | int | 10 | Cap on tool calls within a single LLM response (parallel-tool guardrail). Independent of max_steps. |
| Helper | Returns |
|---|---|
ExecutionConfig.resolved_max_steps() | max_steps when set, else max_iter |
ExecutionConfig.resolved_max_tool_calls() | max_tool_calls_per_turn (independent of max_steps) |
How it relates to max_tool_calls_per_turn
max_steps bounds outer-loop iterations — one LLM round-trip each. max_tool_calls_per_turn caps how many tool calls the model can fire inside one response. They are independent on purpose: if they were coupled, a single parallel-tool response could exhaust the whole step budget (e.g. max_steps=5 truncating after one round of 5 parallel calls). Set them separately when you need both a long overall budget and a small per-turn burst.
Safe to read on any agent, including LiteLLM-only ones — reading
agent.last_stop_reason never lazily creates the OpenAI client. Returns "completed" by default when no run has finished yet.Common Patterns
Pattern 1 — Raise the budget for long runs
Pattern 2 — Detect and continue after truncation
Pattern 3 — Independent per-turn guardrail
Best Practices
Set an explicit budget for long agentic runs
Set an explicit budget for long agentic runs
The default (20, via
max_iter) is fine for short tasks but truncates deep refactors and multi-step research. Raise max_steps to 50–100 for those.Branch on last_stop_reason, not on message content
Branch on last_stop_reason, not on message content
Check
agent.last_stop_reason == "max_steps" instead of parsing the answer text. The old magic "Tool call limit reached" string is now suppressed when a genuine final answer exists.Keep max_tool_calls_per_turn independent
Keep max_tool_calls_per_turn independent
max_steps and max_tool_calls_per_turn govern different things — a per-turn cap of 5 does not halve your step budget. Coupling them would let one parallel-tool response exhaust the whole budget.max_steps is validated at construction
max_steps is validated at construction
max_steps must be >= 1 when set, otherwise ExecutionConfig raises ValueError. Catch it in config-driven setups.Related
Execution
Iteration limits, retries, rate limiting, and code execution
Error Handling
Catch and recover from agent, tool, and LLM errors

