← All posts

Agent Sandboxes and Blast Radius: Design for Recovery


Agent sandboxes limit permissions, isolate side effects, and make recovery explicit so a bad run stops locally instead of becoming an incident.

An agent does not need malicious intent to cause damage. It only needs an ambiguous goal, a tool with broad authority, and a plausible-looking path through the task. A sandbox is how the harness makes that combination survivable.

The practical takeaway is simple: give each run the smallest useful authority, isolate its mutable state, and define how to reconcile every boundary it can cross. A sandbox is not just a container around code. It is the combined boundary around credentials, data, tools, network reach, side effects, and recovery.

Blast radius is the unit of design

“The agent has access to the database” is not a useful safety property. The important question is what one bad decision can change before a person or a guardrail stops it. That change set is the blast radius.

Consider the difference between these two tools:

database.execute(sql)
support.create_refund_draft(order_id, reason_code)

The first delegates a general-purpose capability. The second carries a narrow business operation, can validate its inputs, and can be built to create no external effect at all. Both may be convenient. Only one gives the harness a clear statement of what can go wrong.

Blast radius has several dimensions:

Boundary Question to ask Good default
Identity Which principal is acting? One short-lived, task-scoped identity
Data Which records may it read or change? A filtered, least-privilege view
Tools Which operations exist? Purpose-built tools instead of general shells or admin APIs
Network Which services can it reach? Explicit allowlist, no ambient access
Time and spend How long and how much can it consume? Runtime-enforced quotas
Effects What can leave the sandbox? Draft, stage, or require approval before commitment

The boundary should match the work, not the model’s confidence. A reliable agent asked to summarize a support ticket still does not need production credentials. A weak agent operating inside a narrow, reversible workflow may be acceptable. This is why permissions are a harness concern, not a prompt-writing concern.

Start with an isolated workspace

Every run should have a place to work that is disposable by default: a fresh directory, database schema, object prefix, queue namespace, or ephemeral environment. The important property is not the technology. It is that files and writes from one task cannot silently become input to the next.

An isolated workspace gives the harness four useful guarantees:

  • Clean input: the run begins from declared sources, not whatever a previous attempt left behind.
  • Scoped output: generated artifacts have a task owner and can be reviewed before promotion.
  • Safe cleanup: deleting a failed workspace cannot delete unrelated production state.
  • Forensic value: the workspace can be retained briefly with its trace when something goes wrong.

This also improves debugging. If a run edits a staged copy of a configuration file, an operator can inspect the diff, compare it to the declared intent, and decide whether to promote it. If it edits the live file directly, the same inspection becomes incident response.

Isolation does not mean every task needs a full virtual machine. A dedicated branch, a temporary bucket prefix, or a transaction that can be rolled back may be enough. The key is to name the boundary explicitly and prove that a task cannot escape it through a shared credential or an unrestricted tool.

Separate preparation from commitment

The strongest sandboxing pattern is a two-phase workflow: let the agent assemble a proposed change in an isolated area, then let a separate, narrowly authorized step commit it.

run:
  workspace: task-8f4e
  allowed_tools:
    - read_ticket
    - search_policy
    - create_response_draft
  output: response_draft

promotion:
  preconditions:
    - draft_schema_valid
    - policy_reference_present
    - reviewer_approval_for_external_send
  effect: send_response_once

This structure removes a dangerous false choice: either trust the model with production writes or make it useless. The agent can do most of the cognitive work—gather evidence, propose a patch, construct a message, prepare a deployment plan—without receiving the right to make the effect permanent.

For high-impact actions, promotion should be a distinct tool with a distinct identity. It should accept a stable artifact ID, validate the artifact again, and record the intended effect. Do not give the preparation tool a hidden publish=true argument. That turns review into a UI convention instead of an authority boundary.

When a human must decide, use an explicit handoff rather than a conversational request. The escalation design should contain the proposed effect, evidence references, bounded options, and an expiry. Until the decision returns, the sandboxed run is allowed to wait or gather new non-sensitive evidence—not to infer approval.

Credentials define the real perimeter

Containers and temporary directories are useful, but a broad production token can punch through both. In practice, the identity available to a run often defines its true blast radius.

Prefer credentials that are:

  • Task-scoped: valid for a specific service, environment, and operation.
  • Short-lived: expiring quickly enough that a lost token is not a standing capability.
  • Audience-restricted: unusable against unrelated APIs even if it leaks.
  • Non-transferable in traces: redacted before logs enter model context or general observability stores.
  • Revocable: tied to an execution record that can be disabled when a run is killed.

Avoid putting long-lived secrets in the model’s context, a project directory, or a tool response. A model cannot reliably keep a secret it has seen: it may quote it, write it into an artifact, or send it to another tool while trying to solve the task. Better is a brokered tool that uses the credential internally and returns only the minimum result.

The same rule applies to data. Give the agent a filtered record set, not an all-access query endpoint plus a request to “only look at relevant rows.” Data minimization reduces accidental exposure and makes the task easier to audit. If the agent did not need a field, it should not have been able to retrieve it.

Treat the network as a tool surface

An unrestricted network path turns every fetched page, webhook, and external endpoint into an implicit tool. It enables data exfiltration, arbitrary uploads, and indirect prompt injection to reach systems the task never named.

Make network access declarative. A research agent may need approved documentation domains and a search service. A build agent may need a package registry and a staging deployment endpoint. A support-drafting agent may need none. Deny the rest by default, including cloud metadata services and internal administrative endpoints.

This is a continuation of tool design: a tool is safe when its name, inputs, output, and authority are legible. Network egress deserves the same treatment. Record the destination, request class, and outcome in the trace; reject destinations outside the run’s contract.

Do not assume that a read-only network policy is harmless. Downloaded content can still influence the agent’s next action. Label untrusted content, keep it separate from system instructions, and ensure it cannot expand permissions or alter the allowed tool list. Sandboxing contains effects; it does not remove the need to verify what the agent believes.

Recovery begins before the first write

The difficult cases are not clean failures. They are unknown outcomes: a tool times out after submitting a request, a worker dies after a remote service accepts a change, or a rollback itself partially succeeds. A sandbox needs a recovery contract for these boundaries.

Before a side effect, persist a durable intent record with a task ID, proposed effect, idempotency key, and current state. After the call, record an acknowledgement if one arrives. On restart, reconcile the external system before retrying.

intent:  invoice:882:send:v1
state:   submitted_without_acknowledgement
next:    query delivery status using the same intent key
never:   issue another send solely because the worker restarted

The idempotency pattern is therefore a sandboxing feature. It contains a duplicate action even when the process boundary has already failed. Pair it with a clear promotion log: what artifact was approved, which identity committed it, what external ID resulted, and whether reconciliation confirmed the result.

Not every effect can be reversed. Email cannot be unread; a payment may need a separate refund; a published page may already be cached. For irreversible operations, recovery often means limiting scope, detecting quickly, and providing a compensating action—not pretending rollback is guaranteed. Design those compensations before the agent is allowed to act.

Test the escape paths

Sandboxing that has only been described is not yet a control. Test it with deliberately awkward cases:

  • A run attempts to access a record outside its tenant or task scope.
  • A generated command tries to write outside the temporary workspace.
  • A tool call requests a host or endpoint outside the network allowlist.
  • A credential expires in the middle of a promotion step.
  • A worker restarts after the external system accepted the action but before it returned a response.
  • A reviewer approves an artifact whose inputs have since changed.

The expected result should be more specific than “the agent fails.” It should say whether the operation was denied, whether the isolated state was retained, which incident record was written, and whether a safe retry is possible. Add these cases to the evaluation suite beside normal success cases. The verifier spectrum helps choose the right check: a path constraint may be enough for a workspace, while a payment status requires an authoritative external lookup.

Operational controls matter here too. A runtime kill switch, quota enforcement, and repetition breakers stop a run that is still inside its nominal sandbox but behaving badly. The guardrail layer provides those limits. Sandboxes constrain where an agent can act; guardrails constrain how long and how far it can keep trying.

Takeaway

A sandbox is not a promise that agents will make good decisions. It is the architecture that makes a bad decision local, inspectable, and recoverable. Give each task a disposable workspace and a narrow identity, split preparation from commitment, control the network and data paths, and preserve durable intent at every external boundary. When a run fails—as eventually it will—the right question should be “which scoped task do we reconcile?” rather than “what else did it touch?”