AI agents
Give your LLM agent a sandbox so it can actually run code, edit files, and call tools — without compromising your host.
The most common reason people use PandaStack: their LLM agent needs to execute code
or mutate a filesystem as part of its plan. Putting that inside a Firecracker microVM
means a misbehaving model can't rm -rf / your laptop or exfiltrate creds.
Pattern: one sandbox per agent turn
from pandastack import Sandbox
from openai import OpenAI
oai = OpenAI()
def agent_turn(user_msg: str) -> str:
with Sandbox(template="code-interpreter") as sb:
messages = [{"role": "user", "content": user_msg}]
while True:
resp = oai.chat.completions.create(
model="gpt-4.1",
messages=messages,
tools=[{
"type": "function",
"function": {
"name": "run_python",
"parameters": {
"type": "object",
"properties": {"code": {"type": "string"}},
},
},
}],
)
msg = resp.choices[0].message
if not msg.tool_calls:
return msg.content
for tc in msg.tool_calls:
code = json.loads(tc.function.arguments)["code"]
result = sb.run(f"python -c {shlex.quote(code)}")
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": result.stdout + result.stderr,
})Pattern: persistent agent workspace
For agents that have long-running state (a Cursor-like assistant), give each user a persistent volume and mount it into every sandbox:
vol = Volume.get_or_create(name=f"user-{user_id}-workspace", size_gb=20)
with Sandbox(
template="code-interpreter",
volumes=[vol.mount("/workspace")],
) as sb:
sb.run("cd /workspace && git status")When the sandbox shuts down, the volume persists. Next time the user comes back, mount it into a fresh sandbox and pick up where they left off.
Pattern: fork-on-explore
When an agent wants to try multiple approaches without losing state:
parent = Sandbox(template="python-data")
parent.run("import pandas; df = pandas.read_csv('/data.csv'); print(df.head())")
branches = []
for approach in ["fillna", "dropna", "interpolate"]:
child = parent.fork()
child.run(f"df_{approach} = df.{approach}(); print(df_{approach}.describe())")
branches.append(child)
# Pick the best, throw away the rest
winner = branches[0]
for b in branches[1:]:
b.close()Forks share parent memory pages via CoW. 10 forks of a 500 MiB Python session take ~5 MiB extra RSS until each child diverges.
Security defaults
- Egress is netns-isolated. Outbound traffic goes through a NAT'd interface in the agent's network namespace — no access to the host network or other sandboxes.
- All inter-sandbox communication is blocked by default.
- The agent SSH key is per-sandbox (technically per-template-snap; soon per-sandbox).
For paranoid setups, set egress_allowlist on sandbox create to restrict outbound DNS/HTTPS.