Skip to main content
Every Podflare sandbox starts a long-lived Python 3.12 process at boot. When you call run_code, your code runs inside that process’s shared globals dict — so anything you define in one call is immediately available in the next. This is the same execution model as a Jupyter kernel or the Anthropic code_execution_20260120 tool: state accumulates across calls, and you only pay the cost of imports and data loading once.

State persists across calls

from podflare import Sandbox

with Sandbox() as s:
    s.run_code("import pandas as pd")
    s.run_code("df = pd.read_csv('/data/customers.csv')")
    r = s.run_code("print(df.shape)")
    print(r.stdout)  # pandas and df are both in scope
Each run_code call sends a block of code to the same running Python process. You don’t need to re-import libraries or re-assign variables between calls.

What persists

  • Named valuesx = 1, df = pd.read_csv(...)
  • Imported modulesimport pandas as pd, from pathlib import Path
  • Open file handles — files opened with open() stay open across calls
  • Sockets and threads — long-lived I/O objects survive between calls
  • Subprocesses started with subprocess.Popen — use Popen if you need a persistent child process
  • Changes to sys.path, environment variables, and CWD

What does not persist

  • Shell commands — each bash run_code call launches a fresh subprocess. Shell variables like X=foo do not carry over between calls. If you need persistent state, use Python.
  • Processes started with subprocess.run — they run, exit, and are gone.

Building up state across multiple calls

This pattern is common for data analysis agents that load large datasets once and then explore them:
with Sandbox() as s:
    # Step 1: load dependencies and data
    s.run_code("""
        import pandas as pd
        import numpy as np
        df = pd.read_csv('/data/sales.csv')
    """)

    # Step 2: derive something from the data
    s.run_code("monthly = df.groupby('month')['revenue'].sum()")

    # Step 3: use both df and monthly — both are still in scope
    r = s.run_code("print(monthly.describe())")
    print(r.stdout)

Streaming output with run_code_stream

Use run_code_stream when you want to receive stdout and stderr as they are produced rather than waiting for the call to complete:
with Sandbox() as s:
    for event in s.run_code_stream("for i in range(5): print(i)"):
        if event.type == "stdout":
            print("out:", event.data, end="")
        elif event.type == "stderr":
            print("err:", event.data, end="")
        elif event.type == "exit":
            print("exit code:", event.code)

Exceptions don’t poison the REPL

A raised exception in one call does not crash or reset the sandbox. The REPL stays alive and the next call works normally:
with Sandbox() as s:
    r1 = s.run_code("1 / 0")
    # r1.exit_code == 1, r1.stderr contains ZeroDivisionError

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

When to create a new sandbox vs reuse

SituationRecommendation
Continuing a multi-step analysisReuse the same sandbox
Starting a completely unrelated taskCreate a new sandbox
Running the same code with different inputsfork(n) from a preloaded parent
Recovering from a corrupted global stateCreate a new sandbox
Only one run_code executes at a time per sandbox — concurrent calls queue behind each other. If you need true parallelism, use fork(n) to get independent REPLs.

REPL state is inherited by fork

When you call fork(n), every child starts from the parent’s exact Python process state — variables, imports, loaded data, and all. Because the fork uses a memory snapshot with copy-on-write semantics, a 1 GB DataFrame loaded in the parent costs near-zero additional memory per child until a child modifies it.
with Sandbox() as parent:
    parent.run_code("""
        import pandas as pd
        import numpy as np
        df = pd.read_csv('/data/1GB.csv')
    """)

    children = parent.fork(n=5)
    # Every child has pandas, numpy, and df in scope immediately.
    # No re-loading. No re-importing.
See Fork for the full tree-search pattern.

Fork

Branch a sandbox and inherit REPL state in every child

Sandboxes

Sandbox lifecycle and resource defaults