Prompt
How do I set up an autonomous agent SDK for code execution workflows with sandboxing and retries?
Latest observation
A good pattern is:
- Use an agent framework to plan and call tools.
- Run code in a sandbox that has strict resource and network limits.
- Wrap execution in retry logic for transient failures.
- Add guardrails: allowlisted files, timeouts, max output size, and human approval for risky steps.
Below is a practical setup that works well for code-execution workflows.
1) Choose the architecture
You generally want these pieces:
- LLM / planner: decides what to do next
- Tool layer: functions like
run_code,read_file,write_file,git_diff,tests - Sandbox runner: isolated execution environment
- Orchestrator: retries, state tracking, logs, cancellation
A common loop is:
- Agent proposes a step.
- Tool call runs in sandbox.
- Result is returned.
- If error is retryable, retry with backoff.
- If non-retryable, stop or ask for clarification.
2) Pick a sandbox strategy
Recommended options
A. Docker container per run
- Good default for most teams
- Easy to enforce:
- filesystem isolation
- CPU/memory limits
- process limits
- network disablement
- Best for local or server-side code execution
B. Firecracker / microVM
- Stronger isolation
- Better for untrusted code at scale
- More operational complexity
C. Language-specific sandboxes
- e.g. Pyodide, JS VM, WASM
- Great for limited use cases
- Not ideal for general code workflows
If you’re starting out, use ephemeral Docker containers.
3) Define your tool interface
Typical tools:
run_code(language, code, files, timeout)run_tests(command)read_file(path)write_file(path, content)apply_patch(diff)list_files(path)
Keep tools narrow. Don’t expose raw shell unless you must.
4) Implement sandbox execution
Example: Python worker with Docker
You can create a container per execution, mount a working directory, and disable network.
Pseudocode
def run_in_sandbox(image, workdir, cmd, timeout=30):
return docker_run(
image=image,
command=cmd,
mount=workdir,
network_disabled=True,
memory_limit="512m",
cpu_limit="1.0",
pids_limit=128,
timeout=timeout,
read_only_root=True,
)
Key safety settings
network_disabled=Truetimeout=...- memory and CPU limits
- read-only root FS
- mount only a temp workspace
- run as non-root
- cap stdout/stderr size
5) Add retry logic
Retries should only happen for transient failures:
- container start failure
- temporary resource contention
- flaky tests
- external service timeout, if you allow any networked tools
Do not retry endlessly on:
- syntax errors
- failing assertions
- permission errors
- missing file errors
- deterministic crashes
Retry policy
- max 2–3 retries
- exponential backoff with jitter
- classify errors into retryable / non-retryable
Example
import time, random
def retry(fn, max_attempts=3):
for attempt in range(1, max_attempts + 1):
try:
return fn()
except RetryableError as e:
if attempt == max_attempts:
raise
sleep = (2 ** (attempt - 1)) + random.uniform(0, 0.5)
time.sleep(sleep)
Error classification
- Retryable: timeout, transient container failure, deadlock from resource limits
- Not retryable: code error, test failure, bad input
6) Create the agent loop
Your orchestrator can look like this:
while not done:
action = agent.next_action(context)
if action.type == "run_code":
result = retry(lambda: sandbox.run(action.payload))
context.add(result)
elif action.type == "write_file":
workspace.write(action.path, action.content)
elif action.type == "run_tests":
result = retry(lambda: sandbox.run_tests(action.command))
context.add(result)
elif action.type == "finish":
done = True
Important:
- feed the agent only relevant outputs
- truncate very large logs
- keep state explicit
7) Put guardrails on code execution
Strongly recommended:
- Allowlist commands
- No arbitrary outbound network
- File path restrictions
- Execution timeouts
- Output size limits
- Max number of steps per task
- Audit logs for every action
If the agent can edit files, restrict it to a project workspace only.
8) Use structured outputs from the LLM
Instead of free-form text, have the model output structured actions like:
{
"tool": "run_tests",
"args": {
"command": "pytest -q"
}
}
This reduces ambiguity and makes retries easier.
9) Observability and debugging
Track:
- prompt and tool calls
- sandbox command, exit code, duration
- stdout/stderr
- retries and reasons
- final artifact/diff
This is essential for diagnosing flaky behavior.
10) A simple production-ready stack
A solid MVP stack could be:
- Agent orchestration: LangGraph, Semantic Kernel, AutoGen, or your own loop
- LLM: any tool-calling model
- Sandbox: Docker
- State store: Redis/Postgres
- Job queue: Celery, RQ, Sidekiq, or managed queue
- Logging: structured JSON logs + tracing
11) Minimal implementation plan
Phase 1
- Build
run_codetool in Docker - Disable network
- Add timeout and memory limits
Phase 2
- Add agent loop with structured tool calls
- Add retries for transient execution failures
Phase 3
- Add file tools and test runner
- Add output truncation and audit logs
Phase 4
- Add policy checks:
- disallow dangerous commands
- require approval for sensitive actions
12) If you want a concrete example
I can give you one of these:
- Python example using Docker + OpenAI tool calling
- Node.js example with a sandboxed executor
- LangGraph-based autonomous coding agent
- Kubernetes-backed sandbox runner design
- Firecracker microVM architecture
If you want, I can provide a full working starter template next.
Observation history
Unlock observation history
Create a free Obsurfable account to unlock historical responses, model comparisons, and deeper brand intelligence.
How did Obsurfable measure this prompt?
Obsurfable records AI answers to buyer-style prompts in its research corpus (1 observation for this page). Metrics are distributions over observations, not a single static ranking.
Which AI systems does Obsurfable collect answers from?
OpenAI, ChatGPT, Google, Gemini, Google AI Mode, Anthropic, Claude, Perplexity, Grok, DeepSeek, Mistral, Copilot, and Meta AI.