• English
  • Code Execution

    Suppose an agent needs to analyze sales data: write a CSV into the sandbox, load and compute it in a stateful kernel, generate a chart, save it, and download it. The file routes carry the dataset and chart, while the analysis runs in the kernel session.

    If no state needs to survive between calls, one POST /v2/code/executePOST /v1/code/execute call is enough.

    Requirements

    GET /v2/sandbox should include code_interpreter in capabilities.

    GET /v1/capabilities should report code_interpreter.

    The kernel session needs ipykernel in the host Python (code_interpreter.python_kernels is non-empty) plus pandas and matplotlib; the AIO image ships all three.

    Analyze a dataset in a kernel session

    The kernel keeps df between calls and returns rich output: text/html for a DataFrame, image/png for a figure. Files it writes stay in the sandbox, and the file plane serves them. The example renders with the Agg backend and saves the chart with savefig, so it runs on either Python backend and the chart comes back over the file plane.

    Python
    TypeScript
    BASE_URL = "http://127.0.0.1:18091"
    SESSION = "analysis"
    sb = Aio(BASE_URL)
    
    # 1. The dataset, written through the file plane.
    sb.post("/v2/fs/write", path="/tmp/analysis/sales.csv", content=(
        "month,revenue,cost\n2026-01,120,80\n2026-02,135,82\n2026-03,150,90\n"
        "2026-04,142,95\n2026-05,168,99\n2026-06,180,104\n"
    ))
    
    # 2. Name a session, then load and compute in it.
    first = sb.post("/v2/code/execute", language="python", session_id=SESSION, code="""
    import pandas as pd
    df = pd.read_csv("/tmp/analysis/sales.csv")
    df["margin"] = df.revenue - df.cost
    print(df.margin.describe())
    """)
    print(first["outputs"][0]["text"])
    # -> "count     6.000000\nmean     57.500000\nstd      13.546217\n…"
    
    # 3. Plot in the same session, and save the chart into the sandbox.
    sb.post("/v2/code/execute", language="python", session_id=SESSION, code="""
    import matplotlib
    matplotlib.use("Agg")
    import matplotlib.pyplot as plt
    df.plot(x="month", y=["revenue", "cost"], kind="bar", figsize=(6, 3))
    plt.tight_layout()
    plt.savefig("/tmp/analysis/plot.png", dpi=120)
    """)
    
    # 4. The chart is a sandbox file now; the agent sees it on the file plane.
    files = sb.get("/v2/fs/list", path="/tmp/analysis")["files"]
    print([(f["name"], f["size"]) for f in files])
    
    # 5. Download the bytes: attachment; filename="plot.png".
    chart = sb.http.get("/v2/fs/download", params={"path": "/tmp/analysis/plot.png"})
    print(len(chart.content))
    open("margin.png", "wb").write(chart.content)
    
    sb.delete(f"/v2/code/sessions/{SESSION}")
    Python
    TypeScript
    from agent_sandbox import Sandbox
    
    BASE_URL = "http://127.0.0.1:18091"
    # The SDK's default timeout is 60 s; kernel start can be slower, so raise it.
    client = Sandbox(base_url=BASE_URL, timeout=120)
    
    # 1. The dataset, written through the file plane.
    client.file.write_file(file="/tmp/analysis/sales.csv", content=(
        "month,revenue,cost\n2026-01,120,80\n2026-02,135,82\n2026-03,150,90\n"
        "2026-04,142,95\n2026-05,168,99\n2026-06,180,104\n"
    ))
    
    # 2. Create a session, then load and compute in it.
    session_id = client.jupyter.create_session().data.session_id
    
    first = client.jupyter.execute_code(session_id=session_id, code="""
    import pandas as pd
    df = pd.read_csv("/tmp/analysis/sales.csv")
    df["margin"] = df.revenue - df.cost
    print(df.margin.describe())
    """).data
    print(first.outputs[0].text)
    # -> "count     6.000000\nmean     57.500000\nstd      13.546217\n…"
    
    # 3. Plot in the same session, and save the chart into the sandbox.
    client.jupyter.execute_code(session_id=session_id, code="""
    import matplotlib
    matplotlib.use("Agg")
    import matplotlib.pyplot as plt
    df.plot(x="month", y=["revenue", "cost"], kind="bar", figsize=(6, 3))
    plt.tight_layout()
    plt.savefig("/tmp/analysis/plot.png", dpi=120)
    """)
    
    # 4. The chart is a sandbox file now; the agent sees it on the file plane.
    files = client.file.list_path(path="/tmp/analysis").data.files
    print([(f.name, f.size) for f in files])
    
    # 5. Download the bytes: attachment; filename="plot.png".
    chart = b"".join(client.file.download_file(path="/tmp/analysis/plot.png"))
    print(len(chart))
    open("margin.png", "wb").write(chart)
    
    client.jupyter.delete_session(session_id=session_id)

    outputs is the notebook output list:

    output_typeCarries
    streamtext from stdout or stderr
    execute_resultdata with text/plain, and text/html for a DataFrame
    display_datadata with image/png for a figure
    errorename, evalue, traceback

    An agent loop feeds stream text and error tracebacks back to the model and reuses the session, so the model iterates on the same df. A kernel session is reaped after 300 s idle; the last line of the block above ends it early.

    The payloads a model sees

    Both shapes below come from the kernel backend. df.head(3) in that session returns one execute_result holding both representations of the table; the full text/html is 819 characters, trimmed here to the shape. A figure drawn after %matplotlib inline is a display_data, and image/png is bare base64 with no data: prefix.

    Only the keys an output type uses are present:

    {
      "data": {
        "text/html": "<div>\n<style scoped>\n…\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>month</th>\n…\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>2026-01</td>\n      <td>120</td>\n      <td>80</td>\n      <td>40</td>\n    </tr>\n…\n  </tbody>\n</table>\n</div>",
        "text/plain": "     month  revenue  cost  margin\n0  2026-01      120    80      40\n1  2026-02      135    82      53\n2  2026-03      150    90      60"
      },
      "execution_count": 2,
      "metadata": {},
      "output_type": "execute_result"
    }
    {
      "data": {
        "image/png": "iVBORw0KGgoAAAANSUhEUgAAAk4AAAEiCAYAAAAPh11JAAAAOnRFWHRTb2Z0…",
        "text/plain": "<Figure size 600x300 with 1 Axes>"
      },
      "metadata": {},
      "output_type": "display_data"
    }

    Every output object carries the same nine keys, null where the type does not use them:

    {
      "data": {
        "text/html": "<div>\n<style scoped>\n…\n</style>\n<table border=\"1\" class=\"dataframe\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th></th>\n      <th>month</th>\n…\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>2026-01</td>\n      <td>120</td>\n      <td>80</td>\n      <td>40</td>\n    </tr>\n…\n  </tbody>\n</table>\n</div>",
        "text/plain": "     month  revenue  cost  margin\n0  2026-01      120    80      40\n1  2026-02      135    82      53\n2  2026-03      150    90      60"
      },
      "ename": null,
      "evalue": null,
      "execution_count": 2,
      "metadata": {},
      "name": null,
      "output_type": "execute_result",
      "text": null,
      "traceback": null
    }
    {
      "data": {
        "image/png": "iVBORw0KGgoAAAANSUhEUgAAAk4AAAEiCAYAAAAPh11JAAAAOnRFWHRTb2Z0…",
        "text/plain": "<Figure size 600x300 with 1 Axes>"
      },
      "ename": null,
      "evalue": null,
      "execution_count": null,
      "metadata": {},
      "name": null,
      "output_type": "display_data",
      "text": null,
      "traceback": null
    }

    One-shot execution

    A run without a session happens in a process that is discarded afterwards:

    Python
    TypeScript
    BASE_URL = "http://127.0.0.1:18091"
    sb = Aio(BASE_URL)
    
    py = sb.post("/v2/code/execute", language="python", code="print(sum([1,2,3]))")
    js = sb.post("/v2/code/execute", language="javascript",
                 code="console.log([1,2,3].reduce((a,b)=>a+b))")
    print(py["stdout"].strip(), js["stdout"].strip())
    # -> 6 6
    Python
    TypeScript
    from agent_sandbox import Sandbox
    
    BASE_URL = "http://127.0.0.1:18091"
    client = Sandbox(base_url=BASE_URL)
    
    py = client.code.execute_code(language="python", code="print(sum([1,2,3]))").data
    js = client.code.execute_code(language="javascript",
                                  code="console.log([1,2,3].reduce((a,b)=>a+b))").data
    print(py.stdout.strip(), js.stdout.strip())
    # -> 6 6

    Both forms read data out of the envelope. The whole reply for the Python call is:

    {
      "success": true,
      "message": "ok",
      "data": {
        "code": "print(sum([1,2,3]))",
        "language": "python",
        "status": "ok",
        "execution_count": 1,
        "exit_code": 0,
        "outputs": [
          {
            "output_type": "stream",
            "name": "stdout",
            "text": "6\n"
          }
        ],
        "stdout": "6\n",
        "stderr": "",
        "session_id": null
      }
    }
    {
      "success": true,
      "message": "ok",
      "data": {
        "code": "print(sum([1,2,3]))",
        "language": "python",
        "status": "ok",
        "execution_count": 1,
        "exit_code": 0,
        "outputs": [
          {
            "output_type": "stream",
            "name": "stdout",
            "text": "6\n"
          }
        ],
        "stdout": "6\n",
        "stderr": null,
        "session_id": null
      }
    }

    Do not judge execution by the HTTP status alone.

    A code exception or timeout still returns HTTP 200, but success is false:

    Casesuccessdata.status
    Code exceptionfalseerror
    Timeoutfalsetimeout

    An error output contains these key fields:

    {
      "output_type": "error",
      "ename": "<exception type>",
      "evalue": "<error message>",
      "traceback": [
        "<stack trace>"
      ]
    }

    Failure handling depends on the caller:

    CallerOn failure
    Aio helperChecks success and raises RuntimeError when it is false.
    SDKReturns the response; the caller checks success.

    Rich output and backends

    Rich output is available when the Python backend is kernel, even for a run without a session.

    Python backendDataFrame outputFigure output
    kernelexecute_result includes text/htmldisplay_data includes image/png
    Native REPLtext/plain onlyNo figure output

    Check capabilities.code_interpreter.backend in GET /v2/sandbox to see the active backend:

    {
      "capabilities": {
        "code_interpreter": {
          "backend": "kernel"
        }
      }
    }

    Check code_interpreter.backend in GET /v1/capabilities to see the active backend:

    {
      "data": {
        "code_interpreter": {
          "backend": "kernel"
        }
      }
    }

    Set AIO_CODE_BACKEND=kernel to pin the code routes to the kernel. /v1/jupyter/execute always uses the kernel, regardless of that setting.

    Reuse a session

    The same session_id on consecutive calls keeps variables alive. The session is created on first use, so nothing has to open it:

    Python
    TypeScript
    BASE_URL = "http://127.0.0.1:18091"
    sb = Aio(BASE_URL)
    
    sb.post("/v2/code/execute", language="python", code="x = 21", session_id="s1")
    r = sb.post("/v2/code/execute", language="python", code="print(x * 2)",
                session_id="s1")
    print(r["stdout"].strip())
    # -> 42
    Python
    TypeScript
    from agent_sandbox import Sandbox
    
    BASE_URL = "http://127.0.0.1:18091"
    client = Sandbox(base_url=BASE_URL)
    
    client.code.execute_code(language="python", code="x = 21", session_id="s1")
    r = client.code.execute_code(language="python", code="print(x * 2)",
                                 session_id="s1").data
    print(r.stdout.strip())
    # -> 42

    A session is reclaimed once it goes idle: after 300 s on the kernel tier, 1800 s on the native REPL. A run may take timeout seconds, 30 by default and 900 at most; the code plane's info route reports those limits next to the backends and the interpreter versions.

    Stateful JavaScript

    A named session keeps JavaScript globals the same way; a call without one is stateless:

    Python
    TypeScript
    BASE_URL = "http://127.0.0.1:18091"
    sb = Aio(BASE_URL)
    
    sb.post("/v2/code/execute", language="javascript", code="globalThis.n = 41",
            session_id="s1")
    r = sb.post("/v2/code/execute", language="javascript", code="console.log(n + 1)",
                session_id="s1")
    print(r["stdout"].strip())
    # -> 42
    Python
    TypeScript
    from agent_sandbox import Sandbox
    
    BASE_URL = "http://127.0.0.1:18091"
    client = Sandbox(base_url=BASE_URL)
    
    client.nodejs.execute_code(code="globalThis.n = 41", session_id="s1")
    r = client.nodejs.execute_code(code="console.log(n + 1)", session_id="s1").data
    print(r.stdout.strip())
    # -> 42

    Backend selection

    Python on the code routes uses the first available backend:

    1. AIO_JUPYTER_ENDPOINT, when reachable
    2. the embedded kernel, when ipykernel is installed
    3. the native Python REPL

    AIO_CODE_BACKEND=auto|native|kernel pins the choice. JavaScript always runs on the native REPL. AIO_CODE_PREWARM and AIO_KERNEL_PREWARM enable a warm pool; both default to 0.

    Errors

    ConditionResult
    Missing or mistyped field, unsupported language422
    Session not found404
    No interpreter for the language503, names what it looked for
    Exception or timeout in the code200, success: false, data.status error or timeout