Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.podflare.ai/llms.txt

Use this file to discover all available pages before exploring further.

The model

Each Podflare sandbox runs a long-lived Python process (Python 3.12). Every run_code(lang="python") you issue sends a block of code to that process’s REPL. Execution is in a shared globals dict — so setting a variable in one call makes it visible to the next.
with Sandbox() as s:
    s.run_code("x = 42; import pandas as pd")
    s.run_code("print(type(x), pd.__version__)")
    # both x and pd are in scope
This matches Jupyter kernel / E2B Code Interpreter / Anthropic code_execution_20260120 semantics. Agents that were built against those platforms work the same way on Podflare.

What persists

  • Named values (x = 1, df = pd.read_csv(...))
  • Imported modules (import pandas as pd)
  • Open file handles, sockets, threads, subprocesses
  • Changes to sys.path, environment, CWD
  • Anything else you’d expect to live in Python process memory

What does not persist

  • bash run_code calls. Shell history and $X=foo do NOT carry over — each bash call is a fresh subprocess. This is intentional; shells are not the right abstraction for long-lived state.
  • Child processes you start via subprocess.run. They run, exit, gone. Use subprocess.Popen if you want a long-lived child.

Fork inherits REPL state

This is the reason we built REPL persistence. When you fork(n), each child starts from the parent’s exact Python process state.
with Sandbox() as parent:
    parent.run_code("""
        import pandas as pd
        import numpy as np
        df = pd.read_csv('/data/1GB.csv')
    """)
    # 1 GB DataFrame loaded into parent's Python process

    children = parent.fork(n=5)
    # Each child has pandas, numpy, and df already in scope.
    # No re-loading the CSV. No re-importing modules.
Because fork captures the full guest memory (including the Python process’s heap) and every child shares that memory copy-on-write until it mutates a page, the DataFrame is effectively page-level CoW shared across siblings. Memory cost: near-zero per child.

Exception isolation

A raised exception in one run_code call doesn’t poison the REPL:
with Sandbox() as s:
    r1 = s.run_code("1 / 0")
    # r1.exit_code == 1, r1.stderr contains the ZeroDivisionError traceback

    r2 = s.run_code("print('alive')")
    # r2.exit_code == 0, r2.stdout == "alive\n"
SystemExit is caught too — its code becomes the call’s exit code, not a hostd crash.

The protocol

The agent and the in-VM Python process communicate over stdin/stdout with a minimal framed protocol:
# Agent -> Python (stdin):
__PODFLARE_BEGIN__
<code>
__PODFLARE_END__

# Python -> Agent (stdout), one JSON per line, streamed:
{"t":"out","d":"hello"}
{"t":"err","d":"traceback line"}
{"t":"exit","c":0}
The agent translates these into the NDJSON Events the SDK expects (stdout/stderr/exit). See agent/repl.py for the runner (compiled into the agent via include_str!).

Serialization

Only one run_code executes at a time per sandbox. Concurrent calls serialize via a mutex. If you need parallelism, fork(n) — each child has its own REPL and you can run them in parallel.