← Back to DevBytes

AutoGen Code Executors: Safe Sandboxing for Agent Code

What Are AutoGen Code Executors?

AutoGen Code Executors are the execution backend components responsible for running code generated by LLM-powered agents. When an agent produces Python, shell commands, or other executable code, the Code Executor takes that code and runs it in a controlled environment. AutoGen provides multiple executor implementations, each offering different levels of isolation, security, and flexibility — from simple local subprocess execution to fully containerized sandboxes via Docker or cloud-based container services.

The core abstraction is the CodeExecutor interface, which standardizes how code gets executed regardless of the underlying runtime. This allows you to swap execution backends without changing your agent logic. The executor receives a list of code blocks (typically extracted from an agent's response), executes them in order, and returns the combined output, including stdout, stderr, and any generated files.

Why Safe Sandboxing Matters

LLM-generated code is inherently untrusted. An agent may hallucinate destructive commands like rm -rf /, attempt to exfiltrate data via network calls, or inadvertently create infinite resource-consuming loops. Without proper sandboxing, running such code directly on your host machine or production server poses critical risks:

Sandboxing creates an isolation boundary that confines code execution to a disposable, limited-privilege environment. In AutoGen, this is achieved through containerization (Docker), cloud-based isolation (Azure Container Apps), or configurable local restrictions. The goal is to let agents write and run code freely while ensuring that even malicious or buggy code cannot affect the host system or network beyond explicitly permitted boundaries.

Types of Code Executors in AutoGen

AutoGen ships with several executor implementations, each suited to different deployment scenarios and security requirements. Understanding the trade-offs between them is essential for building safe agent systems.

LocalCommandLineCodeExecutor

The LocalCommandLineCodeExecutor runs code directly on the host machine using a subprocess. It accepts a work_dir parameter that confines file operations to a specific directory and supports a timeout to prevent runaway processes. While convenient for development and trusted environments, this executor offers the weakest isolation — it shares the host filesystem, network, and process space.

from autogen.coding import LocalCommandLineCodeExecutor

executor = LocalCommandLineCodeExecutor(
    work_dir="/tmp/agent_workspace",
    timeout=60  # seconds per code block
)

This executor is best used only when you fully trust the agent's output, such as in closed, sandboxed development environments with no sensitive host data accessible. It is not recommended for production deployments processing untrusted agent code.

DockerCommandLineCodeExecutor

The DockerCommandLineCodeExecutor runs code inside a disposable Docker container, providing strong isolation from the host. Each execution session spins up a fresh container (or reuses one if configured), runs the code, captures output, and optionally tears down the container. You can specify a custom Docker image, bind-mount only the necessary working directory, and restrict network access and capabilities.

from autogen.coding import DockerCommandLineCodeExecutor

# Create a Docker executor with a custom image and no network
executor = DockerCommandLineCodeExecutor(
    image="python:3.11-slim",
    timeout=120,
    network_mode="none",  # disable network access entirely
    work_dir="/workspace",
    bind_dir="/tmp/agent_workspace",
    auto_remove=True  # remove container after execution
)

The Docker executor is the recommended choice for most production use cases. It provides near-complete isolation while remaining lightweight and fast to spin up. You can further harden it by using read-only root filesystems, dropping Linux capabilities, and setting memory/CPU limits.

AzureContainerAppCodeExecutor

For enterprise-scale deployments, the AzureContainerAppCodeExecutor offloads code execution to Azure Container Apps, a fully managed serverless container service. This executor provides cloud-grade isolation, auto-scaling, and integration with Azure's security and monitoring infrastructure. It is ideal for multi-tenant agent platforms where you need strong, auditable isolation guarantees.

from autogen.coding import AzureContainerAppCodeExecutor

executor = AzureContainerAppCodeExecutor(
    subscription_id="your-subscription-id",
    resource_group="agent-sandbox-rg",
    container_app_name="code-exec-sandbox",
    image="python-executor:latest",
    timeout=300
)

This executor requires Azure infrastructure setup but delivers the highest isolation level, since code runs in ephemeral cloud containers completely separated from your application infrastructure.

Custom Code Executors

AutoGen's CodeExecutor interface is extensible. You can implement custom executors for specialized environments — such as running code in AWS Lambda, Kubernetes pods, Firecracker microVMs, or even restricted language-specific sandboxes like gvisor or nsjail.

from autogen.coding import CodeExecutor, CodeBlock, CodeResult
from typing import List

class CustomSandboxExecutor(CodeExecutor):
    @property
    def code_extractor(self):
        # Return a code extractor for parsing agent messages
        ...

    def execute_code_blocks(
        self, code_blocks: List[CodeBlock]
    ) -> CodeResult:
        # Implement your custom sandbox execution logic here
        ...
        return CodeResult(output="...", exit_code=0)

Implementing the interface requires providing a code_extractor property and an execute_code_blocks method. This flexibility lets you integrate AutoGen with any execution backend your security policy requires.

How to Use Code Executors with AutoGen Agents

Integrating a code executor into your agent workflow involves three steps: creating the executor instance, configuring the agent to use it, and defining the code execution policy that determines when code gets executed.

Step 1: Create the Executor

Choose the executor type based on your isolation needs and instantiate it with appropriate parameters:

from autogen.coding import DockerCommandLineCodeExecutor

executor = DockerCommandLineCodeExecutor(
    image="python:3.11-slim",
    timeout=120,
    network_mode="none",
    work_dir="/workspace",
    bind_dir="./agent_scratch",
    auto_remove=True,
    memory_limit="512m",
    cpu_limit="1.0"
)

Step 2: Configure the ConversableAgent

Pass the executor to a ConversableAgent via the code_execution_config dictionary. This configuration also lets you specify which executor to use for the agent's code execution and whether to use Docker or local execution as a fallback.

from autogen import ConversableAgent, AssistantAgent

code_execution_config = {
    "executor": executor,
    "last_n_messages": 3,
    "use_docker": True
}

assistant = AssistantAgent(
    name="coding_assistant",
    system_message="You are a Python programmer. Write code to solve tasks.",
    llm_config={"model": "gpt-4", "api_key": "..."},
    code_execution_config=code_execution_config
)

user_proxy = ConversableAgent(
    name="user_proxy",
    human_input_mode="NEVER",
    code_execution_config=code_execution_config
)

Step 3: Initiate the Conversation

When the agent produces code, AutoGen automatically extracts code blocks, passes them to the configured executor, and returns the results to the conversation. The agent can then refine its code based on execution feedback:

# Start the task — agent writes and executes code in the sandbox
user_proxy.initiate_chat(
    assistant,
    message="Write a Python script that fetches data from an API, processes it, "
            "and saves the results as a CSV file. Use the requests library."
)

The executor captures stdout, stderr, and any files produced inside the workspace. The agent receives this feedback and can iterate, fix errors, or extend functionality — all while staying confined to the sandbox.

Complete Working Example

Below is a complete, runnable example that sets up a Docker-sandboxed agent, asks it to write a data processing script, and lets it iteratively refine the code based on execution results:

import os
from autogen import AssistantAgent, ConversableAgent
from autogen.coding import DockerCommandLineCodeExecutor

# Ensure Docker is running and the image is available
executor = DockerCommandLineCodeExecutor(
    image="python:3.11-slim",
    timeout=120,
    network_mode="none",
    work_dir="/workspace",
    bind_dir="./agent_workspace",
    auto_remove=True
)

# Configuration for code execution
code_exec_config = {
    "executor": executor,
    "last_n_messages": 3,
    "use_docker": True
}

# Create the assistant agent that writes code
assistant = AssistantAgent(
    name="assistant",
    system_message="You are a helpful Python programmer. Write clean, "
                   "well-documented code. Use print() to show results.",
    llm_config={
        "model": "gpt-4",
        "temperature": 0.2
    },
    code_execution_config=code_exec_config
)

# Create the user proxy that executes code on behalf of the assistant
user_proxy = ConversableAgent(
    name="user_proxy",
    human_input_mode="NEVER",
    max_consecutive_auto_reply=3,
    code_execution_config=code_exec_config
)

# Launch the task
task = """
Create a Python script that:
1. Generates a list of 50 random integers between 1 and 1000
2. Calculates mean, median, and standard deviation
3. Identifies prime numbers in the list
4. Writes a summary report to a file called 'analysis_report.txt'
5. Prints a confirmation message when done
"""

user_proxy.initiate_chat(assistant, message=task)

# Clean up executor resources
executor.stop()

This example demonstrates the full lifecycle: agent code generation, sandboxed execution, output capture, and iterative refinement. The agent never touches the host filesystem beyond the bind-mounted directory, and network access is blocked entirely.

Best Practices for Safe Sandboxing

1. Always Use Containerized Executors in Production

Never use LocalCommandLineCodeExecutor in any environment where untrusted code runs. Docker-based or cloud-container executors provide the necessary isolation boundary. The minimal overhead of container startup is negligible compared to the security benefits.

# Production-ready Docker executor with hardened settings
executor = DockerCommandLineCodeExecutor(
    image="python:3.11-slim",
    timeout=60,
    network_mode="none",
    read_only=True,  # mount filesystem as read-only
    cap_drop=["ALL"],  # drop all Linux capabilities
    cap_add=["DAC_READ_SEARCH"],  # add only minimal required caps
    auto_remove=True
)

2. Set Aggressive Timeouts

Every code block should have a strict timeout. LLM-generated code can contain infinite loops or blocking operations. A reasonable default is 60–120 seconds per block, but adjust based on your expected workload. Combine with a global execution timeout at the conversation level.

executor = DockerCommandLineCodeExecutor(
    image="python:3.11-slim",
    timeout=30,  # short timeout per code block
    ...
)

# Also configure max conversation turns to prevent infinite loops
user_proxy = ConversableAgent(
    name="user_proxy",
    max_consecutive_auto_reply=5,  # limit total execution rounds
    ...
)

3. Disable Network Access by Default

Most agent coding tasks don't require outbound network access. Disable it unless explicitly needed. If network access is required for a specific task (like API calls), create a separate executor instance with limited egress rules, and scope its usage narrowly.

# Network-disabled executor for general tasks
offline_executor = DockerCommandLineCodeExecutor(
    image="python:3.11-slim",
    network_mode="none",
    ...
)

# Network-enabled executor for API-specific tasks — use sparingly
online_executor = DockerCommandLineCodeExecutor(
    image="python:3.11-slim",
    network_mode="bridge",
    # Restrict outbound to specific domains via firewall rules
    extra_hosts={"api.example.com": "10.0.0.5"},
    ...
)

4. Use Ephemeral, Per-Session Workspaces

Never reuse working directories across different users or sessions. Each execution session should get a fresh, isolated workspace. Docker executors with auto_remove=True handle this automatically. For local executors, generate unique temp directories per session.

import tempfile
import uuid

# Create a unique workspace per session
session_workspace = tempfile.mkdtemp(prefix=f"agent_{uuid.uuid4().hex[:8]}_")

executor = LocalCommandLineCodeExecutor(
    work_dir=session_workspace,
    timeout=60
)

# Clean up after session completes
import shutil
shutil.rmtree(session_workspace, ignore_errors=True)

5. Validate and Sanitize Agent Outputs Before Execution

Even with sandboxing, implement a pre-execution filter to scan for known dangerous patterns. This is a defense-in-depth measure — the sandbox handles isolation, but pattern-based filtering can catch obviously malicious code early and log it for auditing.

import re
from typing import List
from autogen.coding import CodeBlock

DANGEROUS_PATTERNS = [
    r"rm\s+-rf\s+/",
    r"os\.system\s*\(.*rm\s+-rf",
    r"subprocess\.run\s*\(.*rm\s+-rf",
    r"__import__\s*\(.*os",
    r"eval\s*\(.*__",
    r"exec\s*\(.*__",
]

def filter_dangerous_code(blocks: List[CodeBlock]) -> List[CodeBlock]:
    filtered = []
    for block in blocks:
        code = block.code
        dangerous = False
        for pattern in DANGEROUS_PATTERNS:
            if re.search(pattern, code, re.IGNORECASE):
                dangerous = True
                print(f"[SECURITY] Blocked dangerous pattern: {pattern}")
                break
        if not dangerous:
            filtered.append(block)
    return filtered

# Use the filter before passing to executor
safe_blocks = filter_dangerous_code(extracted_code_blocks)
result = executor.execute_code_blocks(safe_blocks)

6. Set Resource Limits on Containers

Constrain CPU, memory, and disk usage to prevent resource exhaustion attacks. Docker executors accept memory_limit and cpu_limit parameters. For disk limits, use Docker's storage options or tmpfs mounts.

executor = DockerCommandLineCodeExecutor(
    image="python:3.11-slim",
    timeout=60,
    network_mode="none",
    memory_limit="256m",   # hard memory cap
    cpu_limit="0.5",       # half a CPU core
    mem_swappiness="0",    # no swap to disk
    read_only=True,
    tmpfs={"/tmp": "size=64m,noexec"},  # limited, no-exec tmpfs
    auto_remove=True
)

7. Log and Audit All Executed Code

Maintain a complete audit trail of every code block executed, including the agent that generated it, the execution output, and any files produced. This is essential for incident investigation, compliance, and improving agent safety over time.

import logging
import json
from datetime import datetime

def audit_code_execution(agent_name: str, code: str, result: "CodeResult"):
    log_entry = {
        "timestamp": datetime.utcnow().isoformat(),
        "agent": agent_name,
        "code": code,
        "output": result.output,
        "exit_code": result.exit_code,
        "files": result.files
    }
    with open("code_execution_audit.jsonl", "a") as f:
        f.write(json.dumps(log_entry) + "\n")
    logging.info(f"Audit: agent={agent_name}, exit_code={result.exit_code}")

8. Regularly Update Executor Images

Container images used for code execution should be regularly rebuilt and patched. Stale images may contain vulnerable libraries that agent code could exploit. Set up a CI/CD pipeline to rebuild executor images weekly and include only the minimal set of required packages.

# Dockerfile for a minimal, hardened executor image
FROM python:3.11-slim

# Create a non-root user
RUN useradd -m -s /bin/bash sandbox && \
    mkdir -p /workspace && \
    chown sandbox:sandbox /workspace

# Install only whitelisted packages
RUN pip install --no-cache-dir \
    numpy==1.26.2 \
    pandas==2.1.4 \
    requests==2.31.0

USER sandbox
WORKDIR /workspace

# Rebuild weekly and tag with date for traceability
# docker build -t executor-image:2025-03-21 .

Handling Execution Results and Errors

When code executes, the executor returns a CodeResult object containing the combined stdout/stderr output, an exit code, and a list of files generated or modified during execution. Agents use this feedback to refine their code. You should handle error cases gracefully in your orchestration layer:

from autogen.coding import CodeResult

def process_execution_result(result: CodeResult, max_retries: int = 3):
    if result.exit_code == 0:
        print(f"[SUCCESS] Output:\n{result.output}")
        return True
    else:
        print(f"[ERROR] Exit code {result.exit_code}")
        print(f"[STDERR/STDOUT]\n{result.output}")
        # Check for common failure modes
        if "MemoryError" in result.output:
            print("[FATAL] Out of memory — increase container memory limit")
            return False
        if "TimeoutError" in result.output or "timed out" in result.output:
            print("[FATAL] Execution timed out — increase timeout or simplify task")
            return False
        if "ModuleNotFoundError" in result.output:
            print("[RECOVERABLE] Missing module — agent can install it")
            return True  # allow retry
        return True  # allow agent to attempt fix

# In your agent loop:
for attempt in range(3):
    result = executor.execute_code_blocks(code_blocks)
    should_continue = process_execution_result(result)
    if not should_continue:
        break

Conclusion

AutoGen Code Executors provide a flexible, layered approach to safe code execution for LLM-powered agents. By abstracting execution behind a common interface and offering multiple backends — from simple local subprocesses to fully isolated Docker containers and cloud-based sandboxes — AutoGen lets you choose the right isolation level for your threat model. The key takeaway is straightforward: in production, always use containerized executors with network disabled, strict timeouts, resource limits, and ephemeral workspaces. Combine this with pre-execution filtering, comprehensive audit logging, and regular image updates to build a defense-in-depth strategy. When implemented correctly, AutoGen's sandboxing capabilities allow agents to write and run code productively while keeping your infrastructure safe from the inherent risks of LLM-generated code.

— Ad —

Google AdSense will appear here after approval

← Back to all articles