Agentic AI AcademyAgentic AI Academy

The Code Interpreter

A sandboxed environment for code your agent writes

Intermediate 13 minBuilder
What you'll be able to do
  • Explain why a sandboxed Code Interpreter beats letting an LLM 'guess' arithmetic and data results, and where it fits as one of AgentCore's two built-in Tools
  • Drive the session lifecycle (start → execute → stop) with the `code_session` context manager and the `install_packages`/`upload_file`/`execute_code`/`download_file` helpers
  • Map the underlying tool actions — `executeCode`, `executeCommand`, `startCommandExecution`/`getTask`, and `writeFiles`/`readFiles`/`listFiles` — and parse the result `stream`
  • Choose between `SANDBOX` and `PUBLIC` network modes, and articulate the security nuance that SANDBOX is not an airtight network jail
  • Configure a least-privilege execution role that avoids the over-broad Starter Toolkit default, keeps no sensitive secrets, and is monitored
  • Distinguish the control plane (`bedrock-agentcore-control`) from the data plane (`bedrock-agentcore`) and stop sessions to avoid runaway bills
At a glance

AgentCore Code Interpreter is a managed, sandboxed environment where your agent runs real code — Python, JavaScript, or TypeScript — to do data analysis, computation, and file processing instead of hallucinating an answer. This lesson covers the session lifecycle and per-session Firecracker isolation, the `code_session` SDK and its tool actions (`executeCode`, `executeCommand`, file ops, async tasks), the `SANDBOX` vs `PUBLIC` network modes, and the honest security framing: strong cross-tenant isolation but a documented egress story (SANDBOX still resolves DNS) that demands least-privilege execution roles.

  1. 1Why give an agent a code sandbox
  2. 2Pre-installed libraries and runtime installs
  3. 3The session model and microVM isolation
  4. 4The SDK: code_session, helpers, and the two planes
  5. 5Tool actions: execute, commands, files, and async tasks
  6. 6Network modes, the raw API, and the security nuance
  7. 7IAM: caller actions and a least-privilege execution role

Why give an agent a code sandbox

LLMs are notoriously bad at things computers are great at: summing a column of a CSV, parsing a 40-page PDF, fitting a regression, rendering a chart. Ask a model to add up 10,000 numbers and it will confidently produce a number that is plausible and wrong. The fix is not a better prompt — it is to let the agent write code and run it, then read the actual output.

AgentCore Code Interpreter is the managed sandbox for exactly that. It is a secure, isolated code-execution environment so agents can run code for data analysis, computation, and file processing instead of guessing. It is one of AgentCore's two built-in Tools (the other being the Browser tool). It is framework-agnostic — your agent can be Strands, LangGraph, plain Python, whatever — and you reach it through the same SDK regardless.

There is a default/system interpreter you can use immediately, identified as aws.codeinterpreter.v1, with no resource to create. When you need a different posture — say PUBLIC network egress, or a specific execution role — you create a custom interpreter that bundles its own network mode and execution role.

The languages are Python, JavaScript, and TypeScript. You pass the language in each executeCode call, and you can optionally pin a runtime:

Languagelanguageruntime values
Pythonpythonpython
JavaScriptjavascriptnodejs, deno
TypeScripttypescriptnodejs, deno

Crucially, the Python environment ships with a deep stack of data libraries already installed — so the common analytics case needs zero setup.

Key insight

The 'validate by running code' pattern

The highest-leverage use is not exotic — it is forcing the agent to check its own arithmetic. With Strands you wire the tool in and tell the model exactly that: Agent(tools=[AgentCoreCodeInterpreter(region=...).code_interpreter], system_prompt="Validate answers by writing and running Python code."). Now 'what's the 90th-percentile latency in this log?' is computed, not invented.

Pre-installed libraries and runtime installs

The Python sandbox comes batteries-included. As of writing (verify the live page — the exact list changes), the pre-installed selection covers most analytics and ML work:

  • Dataframes & numerics: pandas, polars, numpy, scipy, duckdb, SQLAlchemy
  • Plotting: matplotlib, seaborn, plotly
  • ML: scikit-learn, torch, xgboost, spacy, nltk
  • Files & docs: openpyxl, pypdf/pdfplumber, python-docx, python-pptx, pillow, opencv-python
  • HTTP & web: requests, httpx, beautifulsoup4, fastapi
  • Agent/cloud: boto3, openai, mcp, pydantic

The Node environment includes axios, lodash, uuid, zod, and cheerio.

When you need something that is not pre-installed, add it at runtime two ways:

python
# 1) The SDK convenience helper (preferred)
ci.install_packages(["yfinance", "statsmodels"])

# 2) Or shell out via executeCommand
ci.invoke("executeCommand", {"command": "pip install yfinance statsmodels"})

Because the session keeps state across calls (next section), a package you install in one call is importable in the next.

Watch out

Treat the library list as VOLATILE

The pre-installed set is a fast-moving fact. Do not hard-code an assumption that, say, torch will always be present at a given version. If your agent depends on a specific package or version, install_packages it explicitly so the behavior is deterministic, and confirm the live characteristics page for the current baseline.

The session model and microVM isolation

Everything runs inside a session, and the lifecycle is explicit:

create (or use aws.codeinterpreter.v1) → start session → execute code/commands/files → stop (and optionally delete a custom interpreter)

The key property is that state persists across calls within a session. Variables, imported modules, installed packages, and files you wrote all survive from one execute_code to the next — the session is a live REPL-like process, not a fresh container per call.

Isolation is strong and per-session: each session runs in its own Firecracker microVM with isolated CPU, memory, and filesystem. When the session ends, memory is sanitized and the microVM is destroyed. Access is governed by IAM, and activity is logged to CloudTrail/CloudWatch.

The limits below are [VOLATILE] — teach the shape, verify the live quotas page before you rely on a number:

DimensionValue (as of writing — verify live)
Default session timeout900 s (15 min)
Max session timeout (sessionTimeoutSeconds)8 hours
Default execution window15 min (extend to 8 h via the async task API)
Inline file upload100 MB
File via S3/terminal5 GB
Data TTL30 days
Concurrent sessionsmultiple

The 100 MB-inline vs 5 GB-via-S3 split is the one to internalize early: small payloads go inline through the SDK; anything large goes through S3 and the execution role's S3 permissions.

Watch out

Sessions cost money until you stop them

A started session holds a microVM. If you forget to stop it, you keep paying and you keep holding a concurrency slot. The code_session context manager auto-stops on exit; if you use the explicit CodeInterpreter class, always .stop() in a finally. This is the single most common operational footgun.

The SDK: code_session, helpers, and the two planes

The ergonomic entry point is the code_session context manager, which starts a session against the default aws.codeinterpreter.v1 interpreter and auto-stops it when the block exits:

python
from bedrock_agentcore.tools.code_interpreter_client import code_session

with code_session("us-west-2") as ci:
    ci.install_packages(["pandas", "matplotlib"])
    ci.upload_file(
        path="sales.csv",
        content="date,revenue\n2024-01-01,1000",
        description="CSV",
    )
    result = ci.execute_code(
        "import pandas as pd; print(pd.read_csv('sales.csv')['revenue'].sum())"
    )
    for event in result.get("stream", []):
        for content in event.get("result", {}).get("content", []):
            if content.get("type") == "text":
                print(content["text"])
    out = ci.download_file("sales.csv")
# auto-stopped on exit

The convenience helpers — install_packages, upload_file, execute_code, download_file — wrap the lower-level tool actions for the common cases.

When you need full control (custom interpreter, explicit lifecycle), use the explicit class form and put stop() in a finally:

python
from bedrock_agentcore.tools.code_interpreter_client import CodeInterpreter

ci = CodeInterpreter("us-west-2")
ci.start()
try:
    ci.invoke("executeCode", {"language": "python", "code": "print(2 ** 10)"})
finally:
    ci.stop()  # always

Control plane vs data plane — this distinction matters for IAM and for which SDK client you reach for:

PlaneAPI clientOperations
Controlbedrock-agentcore-controlcreate_code_interpreter, get_code_interpreter, list_code_interpreters, delete_code_interpreter
Databedrock-agentcorestart_code_interpreter_session, invoke_code_interpreter, stop_code_interpreter_session, get_/list_code_interpreter_sessions

The code_session/CodeInterpreter SDK classes wrap the data plane against aws.codeinterpreter.v1. You only touch the control plane when you create or manage a custom interpreter.

Tip

Region is explicit

code_session("us-west-2") and CodeInterpreter("us-west-2") take the region positionally. Code Interpreter is only available in a subset of AgentCore regions (a [VOLATILE] list — check the regions page), so set this deliberately rather than relying on a default.

Tool actions: execute, commands, files, and async tasks

Under the helpers, you invoke the interpreter with a name (the action) and a payload. The full set:

ActionPayload (key fields)Notes
executeCodelanguage, code, optional runtime, optional clearContext (bool)Run code in the persistent context
executeCommandcommandShell command, synchronous
startCommandExecutioncommandAsync, returns a taskId
getTasktaskIdPoll an async task
stopTasktaskIdCancel an async task
writeFilescontent: [{"path", "text"}]Write files into the sandbox
readFilespathsRead files back out
listFilesdirectoryPathList a directory
removeFilespathsDelete files

A direct invoke looks like this:

python
result = ci.invoke("executeCode", {
    "language": "python",
    "code": "total = sum(range(101)); print(total)",
    "clearContext": False,
})

clearContext controls whether you keep the persistent state. clearContext=False (the default behavior you want most of the time) reuses the running interpreter — your earlier variables and imports are still there. clearContext=True gives you a fresh interpreter context, as if nothing had run before. Confusing these two is a classic gotcha: set it True and wonder why your dataframe 'disappeared.'

The async pattern is how you exceed the 15-minute synchronous window — up to 8 hours. A long batch job (startCommandExecution) returns a taskId; you then poll getTask until it completes, and can stopTask to cancel:

python
start = ci.invoke("startCommandExecution", {"command": "python long_etl.py"})
task_id = start["taskId"]
# ... poll ...
status = ci.invoke("getTask", {"taskId": task_id})

Parsing results. Every invoke returns a stream: a list of events, each with result.content, a list of typed blocks. Text output lives in {"type": "text", "text": ...}:

python
for event in result.get("stream", []):
    for content in event.get("result", {}).get("content", []):
        if content.get("type") == "text":
            print(content["text"])

Note

listFiles: directoryPath is canonical for the raw API

The docs are inconsistent here — SDK examples show listFiles with {"path": ""} while the API reference uses {"directoryPath": ""}. When you call the raw HTTPS API, treat directoryPath as canonical. This is a documented gotcha, not your mistake.

Network modes, the raw API, and the security nuance

If you bypass the SDK, the data-plane invoke is a SigV4-signed HTTPS POST. The interpreter ID is in the path and the session ID is a header:

text
POST https://bedrock-agentcore.<region>.amazonaws.com/code-interpreters/aws.codeinterpreter.v1/tools/invoke
x-amzn-code-interpreter-session-id: <session-id>
(Authorization: SigV4 required)

Network mode is the lever that decides what the sandbox can reach. It is set in networkConfiguration.networkMode:

  • SANDBOX — isolated; no general public internet egress. S3 is reachable via the execution role. This is the default posture for untrusted, agent-generated code.
  • PUBLIC — public internet egress allowed. Use only when the code legitimately needs to call external APIs or fetch from the web.

A VPC mode is not documented for Code Interpreter (UNVERIFIED). Do not assume parity with Runtime's VPC support — if you need VPC-scoped egress, treat it as unconfirmed and verify before designing around it.

Now the part most teams get wrong. SANDBOX is not an airtight network jail. It is [VOLATILE], but as documented, SANDBOX mode still permits DNS (A/AAAA) resolution. Teach it as 'no general internet egress, S3-reachable' — not as a perfect isolation boundary.

This is not theoretical. External security research has demonstrated real egress paths out of SANDBOX:

  • BeyondTrust — DNS-based command-and-control out of SANDBOX (the DNS-resolution gap weaponized as a covert channel).
  • Sonrai — credential exfiltration via the instance metadata service (169.254.169.254) plus a 'global S3' covert channel.
  • Palo Alto Unit 42 — a network-isolation bypass.

The honest framing: Code Interpreter is strong on cross-tenant isolation (one customer cannot reach another's microVM), but it is not a perfect data-egress boundary. Verify the current remediation state of this research against the live characteristics page before you make a trust decision.

Watch out

SANDBOX ≠ no network

If your threat model is 'the agent will run code that might try to phone home,' SANDBOX alone is not sufficient. DNS still resolves, and documented techniques have tunneled data out. The control that actually limits blast radius is the execution role and what it can touch — covered next — not the network mode by itself.

IAM: caller actions and a least-privilege execution role

There are two IAM surfaces: what the caller (your agent/app) may do, and what the sandbox's execution role may touch.

Caller actions operate on resource code-interpreter/*:

json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "bedrock-agentcore:CreateCodeInterpreter",
      "bedrock-agentcore:StartCodeInterpreterSession",
      "bedrock-agentcore:InvokeCodeInterpreter",
      "bedrock-agentcore:StopCodeInterpreterSession",
      "bedrock-agentcore:DeleteCodeInterpreter",
      "bedrock-agentcore:ListCodeInterpreters",
      "bedrock-agentcore:GetCodeInterpreter",
      "bedrock-agentcore:GetCodeInterpreterSession",
      "bedrock-agentcore:ListCodeInterpreterSessions"
    ],
    "Resource": "arn:aws:bedrock-agentcore:us-west-2:<account>:code-interpreter/*"
  }]
}

The execution role is what the code inside the sandbox assumes. Its trust policy uses the bedrock-agentcore.amazonaws.com principal with sts:AssumeRole, and should carry confused-deputy guards — aws:SourceAccount and aws:SourceArn conditions:

json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"Service": "bedrock-agentcore.amazonaws.com"},
    "Action": "sts:AssumeRole",
    "Condition": {
      "StringEquals": {"aws:SourceAccount": "<account>"},
      "ArnLike": {"aws:SourceArn": "arn:aws:bedrock-agentcore:us-west-2:<account>:*"}
    }
  }]
}

For S3 file movement, scope it tightly to the buckets you actually use and add an s3:ResourceAccount condition rather than granting blanket S3:

json
{
  "Effect": "Allow",
  "Action": ["s3:GetObject", "s3:PutObject"],
  "Resource": "arn:aws:s3:::my-ci-data-bucket/*",
  "Condition": {"StringEquals": {"s3:ResourceAccount": "<account>"}}
}

The single most important hardening step: do not ship the Starter Toolkit's over-broad default execution role. As documented in the security research, that default grants full S3 read plus Secrets Manager — exactly the credentials an exfiltration technique wants. Combine a tight execution role with no sensitive secrets in the role, and monitoring (CloudTrail/CloudWatch) so a misbehaving session is visible.

Key insight

The defense-in-depth recipe

Because SANDBOX is not an airtight network boundary, the real mitigations are role-shaped: (1) least-privilege execution role scoped to specific buckets/actions, (2) zero sensitive secrets reachable from the role (no broad Secrets Manager), and (3) monitoring via CloudTrail/CloudWatch. Network mode narrows the surface; the execution role limits the damage if something gets out.

Try it: Compute a real answer, then harden the sandbox

Goal: use the Code Interpreter to compute (not guess) an answer over a dataset, exercise the session model and async path, then tighten the execution role and reason about the security nuance.

Prereqs: Python with bedrock-agentcore installed, AWS creds, and a region where Code Interpreter is available (verify the regions page). Caller IAM must allow the bedrock-agentcore:*CodeInterpreter* / *CodeInterpreterSession* actions on code-interpreter/*.

  1. Compute over a CSV. Using the code_session("<region>") context manager, upload_file a small CSV (e.g. date,revenue rows), then execute_code that reads it with pandas and prints the revenue sum. Parse the result by walking result["stream"]result.content{"type": "text"} blocks and printing the text. Confirm the auto-stop on block exit.
  2. Prove state persists. In one execute_code call, set a variable (df = pd.read_csv('sales.csv')). In the next call (same session), reference df and compute a second statistic. Then run a call with clearContext=True that tries to use df again — observe the failure and explain it in one sentence.
  3. Install at runtime. Call install_packages([...]) for a package that is NOT pre-installed, then import and use it in a later call. Note that the install survives across calls in the session.
  4. Go async. Issue a longer job with startCommandExecution (e.g. a command that sleeps then prints), capture the taskId, poll getTask until done, and read the output. Note that this is the path that extends execution beyond the 15-minute sync window.
  5. Lock down the execution role. Write an execution-role trust policy with the bedrock-agentcore.amazonaws.com principal and aws:SourceAccount / aws:SourceArn confused-deputy conditions, plus an S3 statement scoped to ONE bucket with an s3:ResourceAccount condition. Explicitly do NOT grant broad S3 read or Secrets Manager (the Starter Toolkit default to avoid).
  6. Reflect on the boundary. In three sentences: why is SANDBOX mode insufficient on its own as an exfiltration control (hint: DNS still resolves; BeyondTrust/Sonrai/Unit 42), and how do the least-privilege role + no-secrets + monitoring controls reduce the blast radius? When would you justify PUBLIC mode, and what extra monitoring would you add?

Key takeaways

  1. 1Code Interpreter is a managed, sandboxed environment where the agent runs Python/JavaScript/TypeScript to compute and process files instead of guessing — one of AgentCore's two built-in Tools. Use the default `aws.codeinterpreter.v1` or create a custom interpreter for a different network mode / execution role.
  2. 2The session lifecycle is start → execute → stop, and state persists across calls within a session (variables, imports, installed packages, files). Each session runs in its own Firecracker microVM; memory is sanitized and the microVM destroyed on stop.
  3. 3Reach it via `from bedrock_agentcore.tools.code_interpreter_client import code_session`; the context manager auto-stops on exit. Helpers `install_packages`, `upload_file`, `execute_code`, `download_file` wrap the tool actions. The explicit `CodeInterpreter` class needs `.stop()` in a `finally`.
  4. 4Tool actions: `executeCode` (`language`, `code`, `clearContext`), synchronous `executeCommand`, async `startCommandExecution`→`getTask`→`stopTask` (exceeds the 15-min sync window up to 8 h), and file ops `writeFiles`/`readFiles`/`listFiles`/`removeFiles`. Results come back in `response["stream"]` with `result.content[{type:'text'}]`.
  5. 5`SANDBOX` = no general public egress, S3 reachable; `PUBLIC` = internet egress. But SANDBOX is NOT airtight — it still resolves DNS, and BeyondTrust/Sonrai/Unit 42 have documented egress/exfil paths. Treat it as 'no general internet egress, S3-reachable,' not a network jail.
  6. 6Strong cross-tenant isolation, weaker data-egress boundary. Mitigate with a least-privilege execution role (avoid the over-broad Starter Toolkit default with full S3 + Secrets Manager), no sensitive secrets in the role, and monitoring.
  7. 7Control plane `bedrock-agentcore-control` (create/get/list/delete interpreter) vs data plane `bedrock-agentcore` (start/invoke/stop session). The SDK wraps the data plane. Always stop sessions — a running session bills and holds a concurrency slot.

Quiz

Lock in what you learned

Check your understanding

0 / 4 answered

1.Your agent installs `statsmodels` in one `execute_code` call, then imports it in the next call and it works. Then you set `clearContext=True` on a later call and the import fails. Why?

2.A teammate claims 'SANDBOX network mode means the code is completely network-isolated, so we don't need to worry about data exfiltration.' What is the accurate correction?

3.You need to run an ETL job that takes about 90 minutes inside the Code Interpreter. The default execution window is 15 minutes. What is the supported approach?

4.You are hardening a Code Interpreter deployment. Which combination most directly limits the blast radius if agent-generated code turns hostile?

Go deeper

Hand-picked sources to keep learning