n4nAI

Handling timeouts in AutoGen code execution agents

Learn how to configure and handle timeouts in AutoGen code execution agents with practical steps, code examples, and verification methods.

n4n Team4 min read932 words

Audio narration

Coming soon — every post will get a voice note here.

AutoGen’s code execution agents are powerful, but they can hang indefinitely when a sandbox runs a long-running or malicious script. Proper autogen code execution timeout handling prevents stuck agents, wasted compute, and cascading failures in multi-agent workflows. This guide walks through configuring timeouts at every layer — Docker, the executor, and the agent — so you can bound execution time predictably.

Step 1: Understand the timeout layers

AutoGen code execution involves three distinct timeout surfaces. Each operates independently, and a gap in any layer leaves you exposed.

  1. Container runtime timeout — Docker’s --timeout or the container orchestrator’s kill signal. This is the hard wall; nothing survives past it.
  2. Executor timeout — The Python CommandLineCodeExecutor (or DockerCommandLineCodeExecutor) parameter timeout that wraps the subprocess call.
  3. Agent-level timeout — The UserProxyAgent or AssistantAgent configuration that governs how long the agent waits for a reply before escalating.

If you only set the executor timeout but the container has no limit, a fork bomb or infinite loop can still orphan the container. If you only set the container timeout, the executor may raise an unclear error after the container is gone. Configure all three.

Step 2: Set the Docker container timeout

Use DockerCommandLineCodeExecutor with the timeout parameter on the container creation side. This maps to Docker’s --timeout flag (API 1.42+ / Docker Engine 20.10+).

from autogen import DockerCommandLineCodeExecutor

executor = DockerCommandLineCodeExecutor(
    image="python:3.11-slim",
    timeout=30,  # seconds; container hard limit
    work_dir="/home/user",
    auto_remove=True,
)

Verify: Run a script that sleeps longer than the limit and confirm the container exits with code 137 (SIGKILL).

code = """
import time
time.sleep(60)
print("done")
"""
result = executor.execute_code_blocks([("python", code)])
print(result.exit_code)  # should be 137
print("timeout" in (result.logs or "").lower())  # True

If you use a custom image, ensure it has a working timeout binary or rely on Docker’s native --timeout (preferred). The auto_remove=True flag cleans up the container even on timeout, preventing zombie containers.

Step 3: Configure the executor timeout

The executor timeout wraps the subprocess.run call that streams code into the container. It should be slightly shorter than the container timeout so the Python process can catch the failure, log it, and return a structured ExecutionResult instead of raising an unhandled subprocess.TimeoutExpired.

executor = DockerCommandLineCodeExecutor(
    image="python:3.11-slim",
    timeout=30,          # container hard limit
    execute_timeout=25,  # executor soft limit (seconds)
    work_dir="/home/user",
    auto_remove=True,
)

Verify: Execute the same 60-second sleep. The executor should return an ExecutionResult with exit_code == -1 (or similar sentinel) and logs containing “timeout” before the container is killed.

result = executor.execute_code_blocks([("python", code)])
assert result.exit_code != 0
assert "timeout" in (result.logs or "").lower()

If execute_timeout >= timeout, the executor waits longer than the container lives, and you’ll see a broken pipe or connection reset instead of a clean timeout result.

Step 4: Set the agent reply timeout

UserProxyAgent (the typical code executor host) accepts a max_consecutive_auto_reply and a reply_timeout parameter. reply_timeout bounds how long the agent waits for any reply — including the code execution round-trip — before treating it as a failure and moving to the next speaker or terminating.

from autogen import UserProxyAgent, AssistantAgent

user_proxy = UserProxyAgent(
    name="user_proxy",
    code_execution_config={"executor": executor},
    reply_timeout=20,  # seconds; agent-level bound
    max_consecutive_auto_reply=3,
    human_input_mode="NEVER",
)

assistant = AssistantAgent(
    name="assistant",
    llm_config={"model": "gpt-4o-mini"},
)

Verify: Initiate a chat that triggers the long-running script. The agent should abort after ~20 seconds and either retry (up to max_consecutive_auto_reply) or end the conversation.

chat_result = user_proxy.initiate_chat(
    assistant,
    message="Run this code: import time; time.sleep(60)",
)
# chat_result.chat_history should show the timeout error and no "done" output

Keep reply_timeout < execute_timeout < container timeout. A typical stack: agent 20s, executor 25s, container 30s. This gives each layer a 5-second margin to surface the error cleanly.

Step 5: Handle the timeout in your workflow code

Don’t let a timeout bubble up as an exception. Catch the ExecutionResult with a non-zero exit code and decide: retry with a simpler prompt, escalate to a human, or terminate the task.

def run_with_timeout_handling(agent, message, max_retries=2):
    for attempt in range(max_retries + 1):
        result = agent.initiate_chat(assistant, message=message, clear_history=True)
        last_msg = result.chat_history[-1]["content"] if result.chat_history else ""
        
        if "timeout" in last_msg.lower() or "exit_code" in last_msg and "137" in last_msg:
            if attempt < max_retries:
                message = f"Previous attempt timed out. Try a simpler approach: {message}"
                continue
            return {"status": "timeout", "attempts": attempt + 1}
        return {"status": "success", "history": result.chat_history}
    return {"status": "failed", "attempts": max_retries + 1}

Verify: Call run_with_timeout_handling with the sleep script. You should see a structured dict with "status": "timeout" after the configured retries, not an unhandled exception.

Step 6: Add a per-block timeout for multi-block scripts

When the LLM returns multiple code blocks, the executor runs them sequentially. A single execute_timeout applies to the entire sequence. If you need per-block bounds, wrap each block execution yourself.

def execute_blocks_with_per_block_timeout(executor, blocks, per_block_timeout=10):
    results = []
    for lang, code in blocks:
        # Temporarily override executor timeout for this block
        original_timeout = executor.execute_timeout
        executor.execute_timeout = per_block_timeout
        try:
            result = executor.execute_code_blocks([(lang, code)])
        finally:
            executor.execute_timeout = original_timeout
        results.append(result)
        if result.exit_code != 0:
            break
    return results

Verify: Pass two blocks — first sleeps 15s, second prints “ok”. With per_block_timeout=10, the first block times out, the second never runs, and you get one result with timeout logs.

Step 7: Test the full stack under load

Write an integration test that spins up the agent, sends a known-hanging task, and asserts the timeout surfaces at the agent level within the expected wall-clock window.

import time
import pytest

def test_agent_timeout_stack():
    start = time.monotonic()
    outcome = run_with_timeout_handling(
        user_proxy,
        "Execute: import time; time.sleep(100)",
        max_retries=0,
    )
    elapsed = time.monotonic() - start
    
    assert outcome["status"] == "timeout"
    # Agent reply_timeout=20, so we should finish ~20-25s, not 100s
    assert 18 < elapsed < 30, f"elapsed {elapsed}s outside expected window"

Run this in CI. It catches regressions where a configuration change accidentally disables a timeout layer.

Step 8: Monitor and alert on timeout rates

In production, log every timeout with context: agent name, task hash, timeout layer hit, and input size. A sudden spike often signals a prompt regression or a model behavior shift.

import structlog

logger = structlog.get_logger()

def log_timeout(layer: str, task_hash: str, input_chars: int):
    logger.warning(
        "code_execution_timeout",
        layer=layer,           # "container", "executor", "agent"
        task_hash=task_hash,
        input_chars=input_chars,
    )

Hook this into your observability stack (Datadog, Prometheus, CloudWatch). Alert when timeout rate exceeds 1% of code execution tasks over a 5-minute window.

Common pitfalls

  • Mismatched timeouts: Container 30s, executor 60s, agent 10s. The agent gives up first, but the container keeps running until its 30s limit — wasted compute. Keep the hierarchy strict: agent < executor < container.
  • No auto_remove=True: Timed-out containers accumulate, eating disk and eventually hitting Docker’s container limit.
  • Using CommandLineCodeExecutor (local) without a timeout: The local executor has no container wall. Always pass timeout= to its constructor, and consider running it inside a systemd-nspawn or firecracker microVM for hard isolation.
  • Assuming the LLM respects timeout instructions: It won’t. The model may emit while True: pass or time.sleep(1e9). Enforce bounds in infrastructure, not prompts.

Verification checklist

Before deploying a workflow that uses code execution agents, confirm:

  1. Container timeout kills a sleep 60 at 30s (exit code 137).
  2. Executor returns a structured result with timeout logs before the container dies.
  3. Agent aborts the conversation at reply_timeout and surfaces the error in chat history.
  4. Your retry/escalation logic handles the structured timeout result without exceptions.
  5. Integration test passes in CI, asserting wall-clock time matches the agent timeout.
  6. Timeout metrics appear in your observability dashboard.

With all three layers configured and verified, autogen code execution timeout handling becomes a reliable guardrail instead of a source of mysterious hangs. Your agents fail fast, clean up after themselves, and give you the signal you need to iterate on the prompt or the task decomposition.

Tagsautogencode-executionerror-handlingagents

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All autogen code-executing agents posts →