Execution

Control agent iteration limits, retries, rate limiting, and code execution

Execution configuration controls how long an agent runs, how many times it retries, and what code it can execute.

python
from praisonaiagents import Agent
from praisonaiagents.config.presets import EXECUTION_PRESETS

# All spellings apply the 'thorough' preset (max_iter=50):
for value in ("thorough", " thorough ", "THOROUGH"):
    agent = Agent(instructions="t", execution=value)
    assert agent.max_iter == EXECUTION_PRESETS["thorough"]["max_iter"]
Call (Python)Before PR #4186After PR #4186
Agent(execution="thorough")✅ max_iter=50✅ max_iter=50
Agent(execution=" thorough ")⚠ validated → max_iter=20 (default)✅ max_iter=50 (thorough)
Agent(execution="THOROUGH")⚠ validated → max_iter=20 (default)✅ max_iter=50 (thorough)
python
from praisonaiagents import Agent

agent = Agent(
    name="Assistant",
    instructions="You are a thorough research assistant.",
    execution="thorough",
)

agent.start("Analyze the competitive landscape of the electric vehicle market.")

The user sends a research prompt; iteration limits, retries, and rate limits govern how long the agent runs.

Diagram

Execution Flow

Max reached

Error

Done

🚀 Start

⚙️ Iterations

🛑 Stop

🔄 Retry

✅ Result

Quick Start

  1. Level 1 — String (preset)

    Pick a named execution profile — the shortest way to set sensible limits.

    python
    from praisonaiagents import Agent
    
    agent = Agent(
        name="Researcher",
        instructions="You are a research assistant.",
        execution="thorough",
    )
    agent.start("Compare five programming languages for web development.")
  2. Level 2 — Config class (full control)

    Use ExecutionConfig to set exact iteration, time, and retry limits.

    python
    from praisonaiagents import Agent, ExecutionConfig
    
    agent = Agent(
        name="Researcher",
        instructions="You are a research assistant.",
        execution=ExecutionConfig(
            max_iter=50,
            max_execution_time=300,
            max_retry_limit=3,
        ),
    )
    agent.start("Research and summarize the latest AI safety papers.")

Execution Presets

Diagram

Simple Q&A

Most tasks

Research / multi-step

No limits at all

How complex is the task?

fast\nfewer iterations, strict limits

balanced\ngood defaults

thorough\nmore iterations

unlimited\nuse with caution


How It Works

Diagram
LLMExecutionAgentUserLLMExecutionAgentUserloop[Each tool call / step]alt[Budget exceeded][Completed]RequestCheck iteration budgetIncrement counterCheck limits (iter, time, RPM)Final step, wrap-up instruction injectedCoherent final answerWrap-up as final answerDone signalResponse
PhaseWhat happens
1. Budget checkExecution tracks iterations against max_iter
2. Rate limitEnforces max_rpm requests-per-minute
3. Time limitOnce a turn has run max_execution_time seconds, it stops at the next loop boundary (next LLM call or tool dispatch) and agent.last_stop_reason == "timeout". See Execution Time Caps.
4. RetryRetries failed steps up to max_retry_limit times
5. Step-limit wrap-upOn the final permitted step (max_steps, or max_iter when unset), the agent injects a wrap-up instruction so the model produces a coherent final answer summarising progress instead of being hard-cut. Detect truncation with agent.last_stop_reason == "max_steps". See Step Budget.

Configuration Options

Card

Full list of options, types, and defaults — ExecutionConfig

The most common options at a glance:

OptionTypeDefaultDescription
max_steps`int \None`NoneUnified outer-loop step budget honoured by both tool-execution loops (OpenAI-native and LiteLLM). None falls back to max_iter. Must be >= 1 when set. See Step Budget.
output_continuationUnion[bool, int]TrueAuto-continue a final answer cut off by the output-token limit. See Output Continuation.
max_iterint20Legacy per-loop iteration cap, used when max_steps is unset. On the final step the agent receives a graceful wrap-up instruction and returns a coherent summary — not the old canned "Task completed." placeholder.
max_tool_calls_per_turnint10Cap on tool calls within a single LLM response (parallel-tool guardrail). Independent of max_steps.
max_rpm`int \None`NoneMax requests per minute — when set and no rate_limiter is passed, the Agent auto-builds one. max_rpm <= 0 raises ValueError
max_execution_time`int \None`NoneWall-clock seconds per chat turn. The loop guard halts the next tool call, and the LLM loop stops at its next boundary, so a long turn stops even without tool calls. Sets agent.last_stop_reason == "timeout". Sync and async. See Execution Time Caps.
max_retry_limitint2Max retries on failure
code_executionboolFalseGrants the agent an execute_code tool (not just a flag). The tool is classed "critical", so it is approval-gated under every preset except "full".
code_modestr"safe""safe" runs code in an isolated subprocess with resource limits; "unsafe" runs in-process with restricted builtins so code can reach the agent’s tools. Any other value raises ValueError at construction.
code_toolsboolFalseLets model-generated code call the agent’s tools via proxies. Only meaningful with code_mode="unsafe" — setting it in "safe" mode warns and is ignored. Model-generated code calls the agent’s tools through the same policy / guardrail / approval gate as the normal tool-call loop (agent-wide guardrails=, per-tool @tool(input_guardrails=/output_guardrails=), PolicyEngine, PermissionManager deny-rules, require_approval).
code_tools_allow`List[str] \None`NonePer-run allow-list of tool names callable from code. A named tool must also be present in Agent(tools=[...]). None/empty = no tools exposed.
max_budget`float \None`NoneHard USD spend limit per run

Code-execution options

Setting code_execution=True appends a real tool named execute_code to the agent’s tool list — it is not just a flag. Because the tool is named execute_code, the approval registry classes it critical, so it is approval-gated under every preset except "full".

OptionTypeDefaultDescription
code_executionboolFalseAppends the execute_code tool to the agent
code_modestr"safe""safe" runs code in a subprocess with resource limits and a clean env (tools not reachable). "unsafe" runs in-process with restricted builtins so code_tools_allow tools become callable. Any other value raises ValueError at construction
code_toolsboolFalseLet model-generated code call the agent’s registered tools (only meaningful with code_mode="unsafe"). Calls run the same policy / guardrail / approval gate as the normal tool-call loop (agent-wide guardrails=, per-tool @tool(input_guardrails=/output_guardrails=), PolicyEngine, PermissionManager deny-rules, require_approval).
code_tools_allow`List[str] \None`NonePer-run allow-list of tool names callable from code. The tool must also be in Agent(tools=[...]). None/empty exposes no tools
timeout`int \None`NoneIn "safe" mode this is a per-call timeout for execute_code (enforced by the subprocess). In "unsafe" mode there is no hard timeout — code runs in-process

Common Patterns

Pattern 1 — Budget-capped agent

python
from praisonaiagents import Agent, ExecutionConfig

agent = Agent(
    instructions="You are a research assistant.",
    execution=ExecutionConfig(max_budget=0.50, on_budget_exceeded="warn"),
)
response = agent.start("Research the history of computing.")
print(response)

Pattern 2 — Code execution agent

python
from praisonaiagents import Agent, ExecutionConfig

agent = Agent(
    instructions="You are a Python coding assistant that runs code to verify answers.",
    execution=ExecutionConfig(
        code_execution=True,
        code_mode="safe",
        max_iter=30,
    ),
)
agent.start("Write and test a Python function to find prime numbers.")

Pattern 3 — Parallel tool calls for speed

python
from praisonaiagents import Agent, ExecutionConfig

agent = Agent(
    instructions="You are a multi-source research agent.",
    execution=ExecutionConfig(parallel_tool_calls=True, max_iter=40),
)
agent.start("Gather weather, news, and stock prices for New York.")
python
import asyncio
from praisonaiagents import Agent, ExecutionConfig

agent = Agent(
    instructions="You are a multi-source research agent.",
    execution=ExecutionConfig(parallel_tool_calls=True, max_iter=40),
)

# Since PraisonAI PR #4634, parallel_tool_calls is honoured on the async
# tool loop too — independent tool calls in one turn are dispatched
# concurrently via asyncio.gather instead of running one-at-a-time.
asyncio.run(agent.astart("Gather weather, news, and stock prices for New York."))

Pattern 4 — Code that calls your tools

python
from praisonaiagents import Agent, ExecutionConfig

agent = Agent(
    instructions="You solve tasks by writing code that calls the provided tools.",
    tools=[search_web, calculate],
    execution=ExecutionConfig(
        code_execution=True,
        code_mode="unsafe",  # required for code to reach the agent's tools
        code_tools=True,
        code_tools_allow=["search_web", "calculate"],
    ),
)
agent.start("Search for the population of three cities and sum them.")

Pattern 5 — The execute_code tool and its approval gate

Setting code_execution=True appends an execute_code tool to the agent. Because that tool is classed "critical", the approval framework prompts before it runs under every preset except "full".

python
from praisonaiagents import Agent, ExecutionConfig

agent = Agent(
    instructions="You are a Python assistant that runs code to verify answers.",
    execution=ExecutionConfig(code_execution=True),  # code_mode="safe" by default
)

# The agent now has an `execute_code` tool. When it decides to run code,
# the approval prompt fires first (unless the "full" preset is active).
agent.start("Compute the 20th Fibonacci number by running Python.")

Best Practices

Use presets as a starting point

execution="thorough" works well for most research and multi-step tasks. Only switch to custom ExecutionConfig when you need specific limits like budget caps or code execution.

Set max_budget for cost control

For any agent making many API calls, set max_budget=0.50 to cap spend at 50 cents per run. Use on_budget_exceeded="warn" in development and "stop" in production.

Enable parallel_tool_calls for speed

When your agent calls multiple independent tools per turn (e.g., search + fetch + calculate), enable parallel_tool_calls=True to run them concurrently and cut latency. This is the single source of truth for parallel tool calls; ToolConfig.parallel is a deprecated alias for it. Parallel tool calls apply to both sync (chat() / start()) and async (achat() / astart()) tool loops as of PR #4634 — before that, async ran tools sequentially even with parallel_tool_calls=True. Concurrent writes to the same file fall back to sequential — see the write-conflict guard.

Code execution safety

code_mode="safe" is the right default for code_execution=True. It runs code in a subprocess with resource limits, and the timeout on your ExecutionConfig is enforced there as a hard per-call limit. "unsafe" mode runs in the current process — it enforces no hard timeout, so a runaway loop can block the worker; only choose it when code must reach your tools, and prefer "safe" whenever the timeout must be a guarantee. Agent(sandbox=…) only isolates explicit agent.execute_code(...) calls — it does not give the model a tool. For isolation of code the model runs, use AgentFlow(run_on="docker") / run_on="e2b" (whole workflow) or LocalAgent(compute="docker") (per-agent). See Placement.

Scope code_tools with an explicit allow-list

When code_tools=True, always set code_tools_allow to the exact tool names code may call. Leaving it None exposes no tools (the safe default), so name only what the task needs — never grant blanket access.

Detect a timeout stop

When a turn is cut by max_execution_time (or by run_autonomous(timeout_seconds=...)), agent.last_stop_reason == "timeout". Branch on that string instead of parsing the reply. Autonomous runs report the same stop as AutonomyResult(completion_reason="timeout", success=False).

Detecting truncated runs

Set max_steps and check agent.last_stop_reason == "max_steps" to detect truncation — no string-matching needed. The returned text is a real LLM-authored wrap-up (“Here’s what I accomplished… here’s what remains…”), not a placeholder. See Step Budget.

A length_truncated outcome is now reached only after output_continuation continuations are exhausted; usually the agent stitches the pieces into one completed answer first. See Output Continuation.


Related