### Sandbox Agents This release adds **Sandbox Agents**, a beta SDK surface for running agents with a persistent, isolated workspace. Sandbox agents keep the normal `Agent` and `Runner` flow, but add workspace manifests, sandbox-native capabilities, sandbox clients, snapshots, and resume support so agents can work over real files, run commands, edit repositories, generate artifacts, and continue work across runs. Key pieces: - `SandboxAgent`: an `Agent` with sandbox defaults such as `default_manifest`, sandbox instructions, capabilities, and `run_as`. - `Manifest`: a fresh-workspace contract for files, directories, local files, local directories, Git repos, environment, users, groups, and mounts. - `SandboxRunConfig`: per-run sandbox wiring for client creation, live session injection, serialized session resume, manifest overrides, snapshots, and materialization concurrency limits. - Built-in capabilities for shell access, filesystem editing and image inspection, skills, memory, and compaction. - Workspace snapshots and serialized sandbox session state for reconnecting to existing work or seeding a fresh sandbox from saved contents. ### Sandbox clients and hosted providers Sandbox agents now support local, containerized, and hosted execution backends: - `UnixLocalSandboxClient` for fast local development. - `DockerSandboxClient` for container isolation and image parity. - Hosted sandbox clients for Blaxel, Cloudflare, Daytona, E2B, Modal, Runloop, and Vercel through optional extras. The release also adds provider-specific examples and mount strategies for common storage backends, including S3, Cloudflare R2, Google Cloud Storage, Azure Blob Storage, and S3 Files where supported by the selected backend. ### Sandbox memory Adds a sandbox memory capability that lets future sandbox-agent runs learn from prior runs. Memory stores extracted lessons in the sandbox workspace, injects a concise summary into later runs, and uses progressive disclosure so agents can search deeper rollout summaries only when useful. Memory supports: - Read-only or generate-only modes. - Live updates when the agent discovers stale memory. - Multi-turn grouping through `conversation_id`, SDK `Session`, `RunConfig.group_id`, or generated run IDs. - Separate memory layouts for isolating memory across agents or workflows. - S3-backed examples for persisted memory across runs. ### Workspace mounts, snapshots, and resume This release adds a full workspace entry and mount model for sandbox sessions: - Local files and directories. - Synthetic files and directories. - Git repository entries. - Remote storage mounts for S3, R2, GCS, Azure Blob Storage, and S3 Files. - Provider-specific mount strategies across Docker, Modal, Cloudflare, Blaxel, Daytona, E2B, and Runloop. - Portable snapshots with path normalization, symlink preservation, mount-safe snapshotting, and remote snapshot support. - Resume paths through runner-managed `RunState`, explicit `SandboxSessionState`, or saved snapshots. ### Examples and tutorials Adds a large `examples/sandbox/` suite covering: - Local Unix and Docker sandbox runners. - Docker mount smoke tests for S3, GCS, Azure Blob Storage, and S3 Files. - Sandbox coding tasks with skills. - Sandbox agents as tools and handoff patterns. - Memory examples, including multi-agent/multi-turn memory and S3-backed memory. - Tax-prep and healthcare-support workflows. - Dataroom QA and metric extraction tutorials. - Repository code review tutorial. - Vision website clone tutorial. - Provider examples for Blaxel, Cloudflare, Daytona, E2B, Modal, Runloop, Temporal, and Vercel. ### Runtime, tracing, and model plumbing The release includes the runtime plumbing needed to make sandbox agents work naturally inside the existing SDK: - Runner-managed sandbox preparation, capability binding, session lifecycle, state serialization, and resume behavior. - Sandbox-aware `RunState` serialization. - Unified sandbox tracing with SDK spans. - Token usage on tracing spans. - Runner-managed prompt cache key defaults. - OpenAI agent registration and harness ID configuration. - Safer redaction of sensitive MCP tool outputs when sensitive tracing is disabled. - Additional OpenAI client/model utilities and Chat Completions coverage. ## Documentation & Other Changes - docs: add Asqav to external tracing processors list. - docs: update translated document pages. Co-authored-by: Abdulrahman Alfozan <alfozan@openai.com> Co-authored-by: Aditya Singh <60082699+adityasingh2400@users.noreply.github.com> Co-authored-by: Andi Liu <andi@openai.com> Co-authored-by: Aron <263346377+aron-cf@users.noreply.github.com> Co-authored-by: ashwinnathan-openai <ashwinnathan@openai.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: cploujoux <cploujoux@blaxel.ai> Co-authored-by: elainegan-openai <168589666+elainegan-openai@users.noreply.github.com> Co-authored-by: Elias Freider <freider@users.noreply.github.com> Co-authored-by: Erik Dunteman <erik@erikds-macbook-air.local> Co-authored-by: Jason Liu <jasonliu@openai.com> Co-authored-by: Jason Steving <32336750+jasonsteving99@users.noreply.github.com> Co-authored-by: Kazuhiro Sera <seratch@openai.com> Co-authored-by: Lovre Pešut <lovre.pesut@gmail.com> Co-authored-by: Lucas Wang <lucas_wang@lucas-futures.com> Co-authored-by: Matt Brockman <matt.brockman@e2b.dev> Co-authored-by: Mish Ushakov <mishushakov@users.noreply.github.com> Co-authored-by: Naresh <ghostwriternr@gmail.com> Co-authored-by: nicholasclark-openai <nicholasclark@openai.com> Co-authored-by: qiyaoq-oai <qiyaoq@openai.com> Co-authored-by: Scott Trinh <scott@scotttrinh.com> Co-authored-by: tode-rl <tony@runloop.ai> Co-authored-by: Wendy Jiao <wendyjiao@openai.com>
9.8 KiB
Agent memory
Memory lets future sandbox-agent runs learn from prior runs. It is separate from the SDK's conversational Session memory, which stores message history. Memory distills lessons from prior runs into files in the sandbox workspace.
!!! warning "Beta feature"
Sandbox agents are in beta. Expect details of the API, defaults, and supported capabilities to change before general availability, and expect more advanced features over time.
Memory can reduce three kinds of cost for future runs:
- Agent cost: If the agent took a long time to complete a workflow, the next run should need less exploration. This can reduce token usage and time to completion.
- User cost: If the user corrected the agent or expressed a preference, future runs can remember that feedback. This can reduce human intervention.
- Context cost: If the agent completed a task before, and the user wants to build on that task, the user should not need to find the previous thread or re-type all the context. This makes task descriptions shorter.
See examples/sandbox/memory.py for a complete two-run example that fixes a bug, generates memory, resumes a snapshot, and uses that memory in a follow-up verifier run. See examples/sandbox/memory_multi_agent_multiturn.py for a multi-turn, multi-agent example with separate memory layouts.
Enable memory
Add Memory() as a capability to the sandbox agent.
from pathlib import Path
import tempfile
from agents.sandbox import LocalSnapshotSpec, SandboxAgent
from agents.sandbox.capabilities import Filesystem, Memory, Shell
agent = SandboxAgent(
name="Memory-enabled reviewer",
instructions="Inspect the workspace and preserve useful lessons for follow-up runs.",
capabilities=[Memory(), Filesystem(), Shell()],
)
with tempfile.TemporaryDirectory(prefix="sandbox-memory-example-") as snapshot_dir:
sandbox = await client.create(
manifest=manifest,
snapshot=LocalSnapshotSpec(base_path=Path(snapshot_dir)),
)
If read is enabled, Memory() requires Shell(), which lets the agent read and search memory files when the injected summary is not enough. When live memory update is enabled (by default), it also requires Filesystem(), which lets the agent update memories/MEMORY.md if the agent discovers stale memory or the user asks it to update memory.
By default, memory artifacts are stored in the sandbox workspace under memories/. To reuse them in a later run, preserve and reuse the whole configured memories directory by keeping the same live sandbox session or resuming from a persisted session state or snapshot; a fresh empty sandbox starts with empty memory.
Memory() enables both reading and generating memories. Use Memory(generate=None) for agents that should read memory but should not generate new memories: for example, an internal agent, subagent, checker, or one-off tool agent whose run doesn't add much signal. Use Memory(read=None) when the run should generate memory for later, but the user doesn't want the run to be influenced by existing memory.
Read memory
Memory reads use progressive disclosure. At the start of a run, the SDK injects a small summary (memory_summary.md) of generally useful tips, user preferences, and available memories into the agent's developer prompt. This gives the agent enough context to decide whether prior work may be relevant.
When prior work looks relevant, the agent searches the configured memory index (MEMORY.md under memories_dir) for keywords from the current task. It opens the corresponding prior rollout summaries under the configured rollout_summaries/ directory only when the task needs more detail.
Memory can become stale. Agents are instructed to treat memories as guidance only and trust the current environment. By default, memory reads have live_update enabled, so if the agent discovers stale memory, it can update the configured MEMORY.md in the same run. Disable live updates when the agent should read memory but not modify it during the run, for example if the run is latency sensitive.
Generate memory
After a run finishes, the sandbox runtime appends that run segment to a conversation file. Accumulated conversation files are processed when the sandbox session closes.
Memory generation has two phases:
- Phase 1: conversation extraction. A memory-generating model processes one accumulated conversation file and generates a conversation summary. System, developer, and reasoning content are omitted. If the conversation is too long, it is truncated to fit within the context window, with the beginning and end preserved. It also generates a raw memory extract: compact notes from the conversation that Phase 2 can consolidate.
- Phase 2: layout consolidation. A consolidation agent reads raw memories for one memory layout, opens conversation summaries when more evidence is needed, and extracts patterns into
MEMORY.mdandmemory_summary.md.
The default workspace layout is:
workspace/
├── sessions/
│ └── <rollout-id>.jsonl
└── memories/
├── memory_summary.md
├── MEMORY.md
├── raw_memories.md (intermediate)
├── phase_two_selection.json (intermediate)
├── raw_memories/ (intermediate)
│ └── <rollout-id>.md
├── rollout_summaries/
│ └── <rollout-id>_<slug>.md
└── skills/
You can configure memory generation with MemoryGenerateConfig:
from agents.sandbox import MemoryGenerateConfig
from agents.sandbox.capabilities import Memory
memory = Memory(
generate=MemoryGenerateConfig(
max_raw_memories_for_consolidation=128,
extra_prompt="Pay extra attention to what made the customer more satisfied or annoyed",
),
)
Use extra_prompt to tell the memory generator which signals matter most for your use case, such as customer and company details for a GTM agent.
If recent raw memories exceed max_raw_memories_for_consolidation (defaults to 256), Phase 2 keeps only memories from the newest conversations and removes older ones. Recency is based on the last time the conversation is updated. This forgetting mechanism helps memories reflect the newest environment.
Multi-turn conversations
For multi-turn sandbox chats, use the normal SDK Session together with the same live sandbox session:
from agents import Runner, SQLiteSession
from agents.run import RunConfig
from agents.sandbox import SandboxRunConfig
conversation_session = SQLiteSession("gtm-q2-pipeline-review")
sandbox = await client.create(manifest=agent.default_manifest)
async with sandbox:
run_config = RunConfig(
sandbox=SandboxRunConfig(session=sandbox),
workflow_name="GTM memory example",
)
await Runner.run(
agent,
"Analyze data/leads.csv and identify one promising GTM segment.",
session=conversation_session,
run_config=run_config,
)
await Runner.run(
agent,
"Using that analysis, write a short outreach hypothesis.",
session=conversation_session,
run_config=run_config,
)
Both runs append to one memory conversation file because they pass the same SDK conversation session (session=conversation_session) and therefore share the same session.session_id. This is different from the sandbox (sandbox), which identifies the live workspace and is not used as the memory conversation ID. Phase 1 sees the accumulated conversation when the sandbox session closes, so it can extract memory from the whole exchange instead of two isolated turns.
If you want multiple Runner.run(...) calls to become one memory conversation, pass a stable identifier across those calls. When memory associates a run with a conversation, it resolves in this order:
conversation_id, when you pass one toRunner.run(...)session.session_id, when you pass an SDKSessionsuch asSQLiteSessionRunConfig.group_id, when neither of the above is present- A generated per-run ID, when no stable identifier is present
Use different layouts to isolate memory for different agents
Memory isolation is based on MemoryLayoutConfig, not on agent name. Agents with the same layout and the same memory conversation ID share one memory conversation and one consolidated memory. Agents with different layouts keep separate rollout files, raw memories, MEMORY.md, and memory_summary.md, even when they share the same sandbox workspace.
Use separate layouts when multiple agents share one sandbox but should not share memory:
from agents import SQLiteSession
from agents.sandbox import MemoryLayoutConfig, SandboxAgent
from agents.sandbox.capabilities import Filesystem, Memory, Shell
gtm_agent = SandboxAgent(
name="GTM reviewer",
instructions="Analyze GTM workspace data and write concise recommendations.",
capabilities=[
Memory(
layout=MemoryLayoutConfig(
memories_dir="memories/gtm",
sessions_dir="sessions/gtm",
)
),
Filesystem(),
Shell(),
],
)
engineering_agent = SandboxAgent(
name="Engineering reviewer",
instructions="Inspect engineering workspaces and summarize fixes and risks.",
capabilities=[
Memory(
layout=MemoryLayoutConfig(
memories_dir="memories/engineering",
sessions_dir="sessions/engineering",
)
),
Filesystem(),
Shell(),
],
)
gtm_session = SQLiteSession("gtm-q2-pipeline-review")
engineering_session = SQLiteSession("eng-invoice-test-fix")
This prevents GTM analysis from being consolidated into engineering bug-fix memory, and vice versa.