Runtime and sandbox
The runtime is split into three replaceable boundaries: conversation orchestration, model inference, and tool isolation.
Agent runtime
AgentRuntime.Run receives a single trigger, the immutable session agent
snapshot, the conversation already projected from event history, the parsed
tool configuration, and a provisioned sandbox.
The default AgentCore:
- sends alternating
messages[]to the model client; - streams assistant text as an optional preview;
- emits the complete
agent.message; - executes allowed built-ins and feeds results back to the model;
- repeats until the model ends the turn or requires client action.
The loop is capped at 20 model/tool rounds to prevent unbounded execution.
Model clients
The model boundary is a small stateless interface with regular and streaming message creation.
- The offline fake is deterministic and keeps the default binary and test suite network-free.
- The real client targets an Anthropic-shaped
/v1/messagesendpoint and supports API-key or bearer authentication.
Conversation ownership remains in this server. This is why earlier self-contained harness integrations were removed: a second component owning history would create competing sources of truth.
Tool runtime
Agent tool configuration is parsed into:
- the
agent_toolset_20260401built-in toolset; - custom tool definitions;
- MCP toolset references.
bash, read, write, edit, glob, and grep currently execute.
web_fetch and web_search can be declared but return a not-implemented tool
result. MCP references are parsed but not resolved.
Built-ins with always_allow execute inside the current run. Custom tools and
always_ask built-ins park the session for a client response.
Sandbox provider
The application provisions a sandbox only when the resolved toolset contains tools. The same interface supports process execution and confined file reads and writes. This is currently an in-process Go interface, not a separate sandbox HTTP service. See the sandbox backend matrix for support levels, backend requirements, and the ordered evolution path.
Local provider
The default provider creates a temporary work directory, rejects path escapes, clears the child environment, limits command duration, and caps output.
The local provider shares the host process and filesystem namespaces. Never use it to execute untrusted code.
Because of this, a real (network-backed) model paired with the local sandbox
is refused at startup by default: a real model can be steered into running
tool commands on the host with no isolation. The offline deterministic fake
model plus the local sandbox (the zero-config default) always starts. To run a
real model, either select the Docker provider (below) or, as a dev-only explicit
override, set MANAGED_AGENT_ALLOW_UNSAFE_LOCAL_SANDBOX=1 — this accepts the
risk of running tool commands on the host unisolated and must never be used with
untrusted input or in production.
Docker provider
Set MANAGED_AGENT_SANDBOX=docker to run each sandbox in a container. The
provider uses a separate filesystem, Linux namespaces/cgroups, configurable
resource limits, and --network none by default. This is the recommended
sandbox for a real model, and it satisfies the startup guard without the unsafe
override.
Containers share the host kernel. This provider has not been audited for hostile multi-tenant use; stronger isolation such as gVisor or a remote sandbox can be added behind the same provider interface.
Remote providers
E2B, CubeSandbox, OpenSandbox, and Daytona are selected through the same deployment-level registry. The worker remains the lifecycle owner: it maps one session to one external sandbox, persists only the provider name and opaque ID, reattaches after restart, and deletes the resource through a durable Temporal Activity. The external service owns isolation and workspace storage.
Provider credentials, endpoint routing, templates, images, and auto-pause settings stay in worker environment variables. They do not change the Managed Agents Environment or Session wire models.
Session-scoped ownership
A sandbox is scoped to the session, not to a single run. The first run in a session that needs tools provisions a logical sandbox; every later run in the same session reuses that same instance, so filesystem state a tool creates in one run is visible to the next. Different sessions acquire under different keys and never share a sandbox, so they stay isolated even when they use the same agent and environment.
Ownership lives in a session-scoped manager that wraps the provider inside the
internal/sandbox package. PostgreSQL stores the provider name, opaque external
ID, and spec hash; the in-memory map is only a live client cache. The
AgentRuntime is unaware of this — the application resolves the sandbox and
passes it in the run request.
Entering idle does not tear the sandbox down. After a worker restart, Attach
reconstructs the client from the persisted reference; if the resource has
disappeared, acquisition fails instead of silently replacing session state.
Session deletion runs provider teardown as a retryable Temporal Activity and removes the binding before PostgreSQL permits the Session row to be deleted. Local references require the same host filesystem and Docker references require the same daemon. Remote multi-worker execution therefore needs a service-backed provider. Checkpoint/restore, quotas, eviction, and orphan reconciliation are not implemented.
Streaming previews
The runtime may report event_start and event_delta through an optional
preview interface. These frames bypass durable storage and go directly to
subscribers that requested the matching event type. The complete event is still
buffered and committed through the normal completion transaction.
This split keeps the event log authoritative while improving latency, but it also means clients must tolerate a preview ending without a persisted event if the process or upstream stream fails.