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.
- Container runtime timeout — Docker’s
--timeoutor the container orchestrator’s kill signal. This is the hard wall; nothing survives past it. - Executor timeout — The Python
CommandLineCodeExecutor(orDockerCommandLineCodeExecutor) parametertimeoutthat wraps the subprocess call. - Agent-level timeout — The
UserProxyAgentorAssistantAgentconfiguration 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 passtimeout=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: passortime.sleep(1e9). Enforce bounds in infrastructure, not prompts.
Verification checklist
Before deploying a workflow that uses code execution agents, confirm:
- Container timeout kills a
sleep 60at 30s (exit code 137). - Executor returns a structured result with timeout logs before the container dies.
- Agent aborts the conversation at
reply_timeoutand surfaces the error in chat history. - Your retry/escalation logic handles the structured timeout result without exceptions.
- Integration test passes in CI, asserting wall-clock time matches the agent timeout.
- 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.