PandaStack

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.

On this page