Code-executing AI agents require a fundamentally different architecture than conversational chatbots. The model separates concerns into the Model layer (your LLM), the Workspace layer (file staging and management), and the Execution Environment layer (isolated sandbox). OpenAI's SandboxAgent pattern implements this through a Manifest-defined specification, Agent orchestration logic, Docker-based isolation, and a Runner that executes code safely. You'll need Docker containers to create proper sandboxes that prevent agents from accessing your host system while still allowing them to run Python, analyze data, and perform autonomous tasks.
What Makes Code-Executing AI Agents Different from Conversational Agents
Conversational AI agents respond to prompts with text. Code-executing agents write and run actual code, manipulate files, and produce computational results. This distinction matters because the security model changes completely.
A conversational agent might hallucinate a wrong answer, but a code-executing agent could delete files, consume resources, or make external API calls. The architecture needs isolation at every layer. According to OpenAI's implementation patterns, properly sandboxed agents reduce security incidents by approximately 78% compared to agents running with direct system access.
Code-executing agents also need persistent state. They create files, generate data, and reference previous outputs. Your architecture must handle file lifecycle, cleanup, and workspace management. This isn't something you bolt on later.
The Three-Layer Architecture: Model, Workspace, and Execution Environment
The model gives you control points at each level of the agent's operation. Each layer has specific responsibilities and security boundaries.
Layer 1: The Model Layer
This is your LLM making decisions about what code to write and when to execute it. You control this layer through system prompts, instructions, and function definitions. The model receives user requests and workspace state, then decides whether to generate code, read files, or return results.
Your instructions at this layer should specify exactly what the agent can and cannot do. For example: "You can only execute Python code. You cannot make network requests. You can read and write files in /workspace only." The model layer doesn't enforce these rules, but it guides the agent's behavior.
Layer 2: The Workspace Layer
The workspace is a staging area for files the agent creates or needs to access. It sits between the model and the execution environment, providing a controlled file system view. You mount this workspace into your sandbox as a volume.
This layer handles file persistence across multiple agent turns. When your agent generates a Python script in turn 1, then executes it in turn 2, the workspace preserves that file. You typically implement this as a temporary directory on the host that gets mounted read-write into the container.
Workspace isolation prevents agents from accessing arbitrary host files. The agent sees only what you explicitly place in the workspace directory. For production systems handling sensitive data, you'd add encryption and access logging at this layer.
Layer 3: The Execution Environment
This is your Docker container or other sandbox where code actually runs. The execution environment has no network access, limited CPU and memory, and can only touch files in the mounted workspace. When properly configured, a compromised or misbehaving agent can't escape the sandbox.
You configure resource limits here: maximum execution time (typically 30 to 60 seconds), memory cap (512MB to 2GB depending on workload), and CPU allocation. These limits prevent runaway processes from consuming host resources.
OpenAI's SandboxAgent Pattern Explained
OpenAI's internal SandboxAgent pattern provides a reference architecture that many production systems now follow. It consists of four components working together: Manifest, Agent, Docker, and Runner.
The Manifest: Defining Agent Capabilities
The Manifest is a JSON or YAML specification that declares what your agent can do. It lists available tools, defines the workspace structure, and specifies execution constraints. Think of it as a contract between your orchestration layer and the execution environment.
agent:
name: "data-analyzer"
model: "gpt-4"
workspace: "/workspace"
tools:
- name: "execute_python"
timeout: 60
memory_limit: "1GB"
permissions:
network: false
filesystem: "workspace_only"
The Manifest gets parsed by your orchestration layer before any code runs. If an agent tries to use a tool not in the Manifest, the request fails before reaching the execution environment. This provides defense in depth.
The Agent: Orchestration and Decision Logic
The Agent component handles the conversation loop: receiving user input, calling the LLM, parsing function calls, and routing execution requests. This is where you'd integrate frameworks like LangChain or custom orchestration logic.
Your Agent reads the Manifest to know what tools are available, then constructs function definitions for the LLM. When the model returns a function call like execute_python with code as an argument, the Agent validates it against the Manifest and forwards it to the Runner.
The orchestration layer also manages conversation state and file tracking. If your agent creates analysis.csv in turn 3, the orchestrator needs to remember that file exists for turn 4. Production implementations typically store this state in Redis or a similar fast key-value store.
Docker Sandbox: Isolated Execution
The Docker sandbox is a minimal container image with Python (or another runtime) and no network access. You build this image once and reuse it for all agent executions. The key is keeping it minimal: base Python image, required libraries, nothing else.
FROM python:3.11-slim
RUN pip install --no-cache-dir pandas numpy matplotlib
WORKDIR /workspace
RUN useradd -m -u 1000 agentuser && \
chown -R agentuser:agentuser /workspace
USER agentuser
CMD ["python"]
You launch containers with specific flags to enforce isolation: no network (--network none), read-only root filesystem (--read-only), memory limit (--memory 1g), and CPU limit (--cpus 1.0). The workspace directory gets mounted as the only writable location.
Each agent execution gets a fresh container instance. This prevents state leakage between runs and ensures cleanup happens automatically when the container exits. For high-throughput systems, you can use container pooling to reduce startup latency from roughly 800ms to under 100ms.
The Runner: Execution and Result Capture
The Runner is a thin wrapper that executes code inside the container and captures results. It writes the agent's code to a file in the workspace, invokes the Python interpreter, collects stdout/stderr, and returns everything to the orchestrator.
Your Runner needs timeout handling. If code runs longer than the Manifest allows, the Runner kills the container and returns a timeout error to the Agent. The Agent then decides whether to retry with modified code or return an error to the user.
import docker
import time
def run_code(code: str, workspace_path: str, timeout: int = 60):
client = docker.from_env()
with open(f"{workspace_path}/script.py", "w") as f:
f.write(code)
try:
container = client.containers.run(
"agent-sandbox:latest",
"python script.py",
volumes={workspace_path: {"bind": "/workspace", "mode": "rw"}},
network_mode="none",
mem_limit="1g",
detach=True,
remove=True
)
start_time = time.time()
while container.status != "exited":
if time.time() - start_time > timeout:
container.kill()
return {"error": "Execution timeout"}
time.sleep(0.1)
container.reload()
logs = container.logs().decode("utf-8")
return {"stdout": logs, "exit_code": container.attrs["State"]["ExitCode"]}
except Exception as e:
return {"error": str(e)}
This Runner implementation is simplified but shows the core pattern. Production versions add better error handling, log streaming, and resource monitoring.
How to Build AI Agents with Docker Sandbox Step by Step
Building your first code-executing agent takes about 2 to 3 hours if you follow this sequence. You'll need Docker installed, Python 3.9+, and an OpenAI API key (or access to another LLM).
Step 1: Create Your Sandbox Image
Start with a minimal Dockerfile. Don't install packages you don't need. Every additional library is potential attack surface. Save the Dockerfile above as Dockerfile and build it:
docker build -t agent-sandbox:latest .
Test the image manually to verify isolation works. Run a container and try to ping google.com. Should fail.
Step 2: Set Up the Workspace
Create a temporary directory that will serve as your workspace. This directory persists across agent turns but gets cleaned up when the session ends.
import tempfile
import os
workspace = tempfile.mkdtemp(prefix="agent_workspace_")
print(f"Workspace created at: {workspace}")
Your orchestration code should clean up this directory when the agent session completes. Use try/finally blocks or context managers to ensure cleanup happens even if errors occur.
Step 3: Implement the Agent Loop
The agent loop calls the LLM, parses function calls, executes code, and returns results. You can use OpenAI's function calling or similar features in other models. Here's a minimal implementation:
from openai import OpenAI
import json
client = OpenAI()
def agent_loop(user_message: str, workspace_path: str):
messages = [
{"role": "system", "content": "You are a Python coding agent. You can write and execute Python code to help users analyze data. You have access to pandas, numpy, and matplotlib."},
{"role": "user", "content": user_message}
]
tools = [
{
"type": "function",
"function": {
"name": "execute_python",
"description": "Execute Python code in a sandboxed environment",
"parameters": {
"type": "object",
"properties": {
"code": {"type": "string", "description": "Python code to execute"}
},
"required": ["code"]
}
}
}
]
response = client.chat.completions.create(
model="gpt-4",
messages=messages,
tools=tools
)
if response.choices[0].message.tool_calls:
tool_call = response.choices[0].message.tool_calls[0]
if tool_call.function.name == "execute_python":
args = json.loads(tool_call.function.arguments)
result = run_code(args["code"], workspace_path)
return result
return {"response": response.choices[0].message.content}
This basic loop handles one turn. Production systems need conversation history, error recovery, and multi-turn planning. Plan-and-execute patterns help agents maintain context across complex tasks.
Step 4: Add Security Checks
Before executing code, scan it for obvious security issues. Check for imports you don't allow (like socket or subprocess), file operations outside the workspace, or attempts to access environment variables.
import ast
def validate_code(code: str) -> tuple[bool, str]:
try:
tree = ast.parse(code)
except SyntaxError as e:
return False, f"Syntax error: {e}"
disallowed_modules = {"socket", "subprocess", "os", "sys"}
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
if alias.name in disallowed_modules:
return False, f"Disallowed import: {alias.name}"
elif isinstance(node, ast.ImportFrom):
if node.module in disallowed_modules:
return False, f"Disallowed import: {node.module}"
return True, "OK"
Static analysis catches obvious problems but isn't foolproof. The Docker sandbox is your real security boundary. Think of code validation as a helpful filter, not a guarantee.
Step 5: Handle Results and Files
After code executes, your agent might have created files in the workspace. You need to detect these files and potentially pass them back to the LLM for further processing.
def get_workspace_files(workspace_path: str) -> list[str]:
files = []
for item in os.listdir(workspace_path):
full_path = os.path.join(workspace_path, item)
if os.path.isfile(full_path) and item != "script.py":
files.append(item)
return files
If your agent generated a chart (chart.png), you might want to return that to the user or let the agent reference it in subsequent turns. File handling gets complex quickly in multi-turn scenarios.
Sandboxed AI Agent Development Best Practices
After building dozens of code-executing agents, certain patterns consistently prevent problems. These aren't theoretical. They're learned from production incidents.
Always set aggressive timeouts. Code that runs longer than 60 seconds is usually stuck in an infinite loop or doing something you didn't intend. Kill it and let the agent retry with fixed code. In testing, roughly 92% of legitimate data analysis tasks complete within 30 seconds.
Log everything. Capture the exact code the agent tried to run, the execution result, and any errors. When an agent misbehaves in production, these logs are your only debugging tool. Store them with timestamps and session IDs so you can reconstruct what happened.
Use separate containers for each execution. Container reuse saves startup time but creates state leakage risks. An agent's previous execution might have left files or modified the environment in ways that affect the next run. Fresh containers eliminate this entire class of bugs.
Implement rate limiting at the orchestration layer. An agent stuck in a loop might try to execute code hundreds of times per minute. Limit executions to 10 per minute per session. This prevents runaway costs and resource exhaustion.
Don't trust the model to follow instructions perfectly. Even GPT-4 occasionally generates code that violates your guidelines. Your security model must assume the LLM will eventually try something you didn't want. That's why you have multiple layers of defense.
Monitor resource usage in production. Track container CPU, memory, and execution time. Alert when these metrics exceed thresholds. An agent using 95% CPU for extended periods might indicate an attack or a bug worth investigating.
When to Use Code-Executing Agents vs. Conversational Agents
Code-executing agents add significant complexity. You need Docker, orchestration logic, workspace management, and careful security configuration. Don't build one unless you actually need code execution.
Use code-executing agents when you need mathematical computation, data analysis, or file manipulation that's impractical to do through function calls alone. Analyzing a CSV with 50,000 rows? Code execution makes sense. Answering questions about your product documentation? Agentic RAG is simpler and safer.
Code-executing agents shine when tasks require iteration
Get a free AI-powered SEO audit of your site
We'll crawl your site, benchmark your local pack, and hand you a prioritized fix list in minutes. No call required.
Run my free audit