Python Sandbox

Run untrusted Python in an isolated sandbox, evaluate model-generated code, crunch data, or execute a user script, all with a file system and network policy you control. The python image ships Python ready to run.

Run a snippet

index.ts
import { getOrCreateSandbox } from "@hiver.sh/client";

const sandbox = await getOrCreateSandbox("py", { image: "python" });

const result = await sandbox.exec(["python3", "-c", "print(sum(range(100)))"]);
console.log(result.stdout.trim()); // "4950"

Run a script

Write the code into the sandbox, then run it:

index.ts
await sandbox.writeFile("/workspace/main.py", `
import json
data = [{"n": i, "sq": i * i} for i in range(5)]
print(json.dumps(data))
`);

const { stdout } = await sandbox.exec(["python3", "/workspace/main.py"], { cwd: "/workspace" });
console.log(JSON.parse(stdout));

Install only the packages you allow

By default the sandbox can't reach the network. allowedPythonPackages generates the exact egress rules pip needs for the packages you name, and nothing else, so any other install is blocked.

index.ts
import { getOrCreateSandbox, allowedPythonPackages } from "@hiver.sh/client";

const sandbox = await getOrCreateSandbox("py", {
  image: "python",
  egress: [...allowedPythonPackages("numpy", "pandas")],
});

await sandbox.exec(["pip", "install", "numpy", "pandas"], { cwd: "/workspace" });
const { stdout } = await sandbox.exec(["python3", "-c", "import numpy; print(numpy.__version__)"]);
console.log(stdout.trim());

The allowed* package helpers ship in the TypeScript and Python clients. In Go, add the equivalent pip egress rules to SandboxConfig.Egress directly.

Keep a Python session warm

For repeated evaluations, keep one interpreter alive with execStream and feed it code over stdin, imports and loaded data persist across calls instead of paying startup each time.

End-to-end: an agent that runs Python

Wire the sandbox up as an agent's code interpreter. Give the model a run_python tool whose handler executes the code in the sandbox and feeds the output back, the model writes code, sees the result, and iterates until it has an answer. Everything the model runs stays isolated in the sandbox.

index.ts
import Anthropic from "@anthropic-ai/sdk";
import { getOrCreateSandbox } from "@hiver.sh/client";

const sandbox = await getOrCreateSandbox("py-agent", { image: "python" });
const anthropic = new Anthropic(); // reads ANTHROPIC_API_KEY

const runPython = {
  name: "run_python",
  description: "Execute Python 3 code in a sandbox and return its stdout/stderr.",
  input_schema: {
    type: "object",
    properties: { code: { type: "string" } },
    required: ["code"],
  },
} as const;

const messages: Anthropic.MessageParam[] = [
  { role: "user", content: "Compute the 20th Fibonacci number and print just the number." },
];

while (true) {
  const res = await anthropic.messages.create({
    model: "claude-opus-4-8",
    max_tokens: 1024,
    tools: [runPython],
    messages,
  });
  messages.push({ role: "assistant", content: res.content });

  const toolUse = res.content.find((b) => b.type === "tool_use");
  if (res.stop_reason !== "tool_use" || !toolUse) {
    const text = res.content.find((b) => b.type === "text");
    console.log("answer:", text?.text); // "answer: 6765"
    break;
  }

  // Run the model's code inside the sandbox and return the result.
  const { code } = toolUse.input as { code: string };
  const { stdout, stderr, exit_code } = await sandbox.exec(["python3", "-c", code]);

  messages.push({
    role: "user",
    content: [{
      type: "tool_result",
      tool_use_id: toolUse.id,
      content: stdout || stderr,
      is_error: exit_code !== 0,
    }],
  });
}

The loop is the whole pattern: model → run_python → sandbox → result → model, repeating until the model stops calling the tool. Swap python3 -c for writeFile + python3 file.py to run longer programs, or add allowedPythonPackages so the code can pip install a fixed set of libraries.


Next: Node Sandbox