Sandboxes and environments
After this page you can reason about the compute your agent runs on without ever addressing it: you will know how to tell whether a session currently has a sandbox, what happens to the session when it goes away, and how to decide whether more work can still be done.
A sandbox is not a resource
Section titled “A sandbox is not a resource”There is no sandbox object on this API. You cannot create one, list one, extend
one, keep one warm, or delete one. Nothing in /v1 takes a sandbox identifier.
What you get instead is a projection on the session — the workspace field:
{ "session": { "id": "3f7d4f4c-2b6a-4a2e-9c1a-1f2b3c4d5e6f", "workspace": { "state": "attached", "expiresAt": "2026-08-11T10:04:00.000Z" } }}Three states, and that is the entire vocabulary:
workspace.state |
Meaning | expiresAt |
|---|---|---|
unavailable |
No sandbox is associated with this session. The normal state of a session that has just been created, and of one whose compute was released long ago. | absent |
attached |
A sandbox is leased to this session and the lease has not passed its deadline. | present |
expired |
A sandbox was leased and its deadline has passed. | present, in the past |
expired and unavailable are both “there is no compute right now”. They differ
only in whether one existed: expired is your signal that state may be
recoverable from a checkpoint, which is what continuation reports.
Compute is allocated by work, not by request. Creating a session provisions
nothing; submitting a turn is what causes a sandbox to be leased, and the 202
comes back before that has happened. So unavailable immediately after a submit
is expected, not an error.
Leases are bounded, and the bound is not yours to raise
Section titled “Leases are bounded, and the bound is not yours to raise”A single sandbox lease has a hard ceiling of one hour of wall-clock life. It is a fixed constant, not a parameter: there is no request field, no header and no capability that raises it, and no keepalive that extends a lease past it.
This is much less restrictive than it sounds, because the ceiling bounds the sandbox, not the session. When a lease reaches its deadline the session is untouched — its turns, event log, artifact manifests and publications are all still there — and the next turn runs on a fresh sandbox. A conversation that spans a week is ordinary; it simply spans many leases.
What you must not build is a state machine that assumes the workspace it saw a
minute ago is still there. Read workspace.state when it matters, and treat
expired as routine.
Sessions outlive sandboxes, and continuation says at what cost
Section titled “Sessions outlive sandboxes, and continuation says at what cost”Because compute is disposable and the session is not, every session reports what a further turn would actually get:
{ "continuation": { "canContinue": true, "state": "restorable", "conversation": { "fidelity": "exact" }, "workspace": { "fidelity": "portable_tree" }, "environment": { "fidelity": "current" }, "boundaryRunId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }}state is the summary, and there are four values:
state |
canContinue |
What a next turn gets |
|---|---|---|
live |
true |
The lease is still attached. The agent continues in place, conversation and files intact. |
fresh |
true |
Nothing has run yet on this session. A next turn starts clean, which is what you wanted anyway. |
restorable |
true |
Compute is gone, but a checkpoint can be restored: the conversation exactly, and the working tree as a portable copy. |
unavailable |
false |
No sandbox and no restorable checkpoint. Further work in this session cannot resume the previous state. |
The two fidelity fields say what “restore” means, and they are worth reading
rather than collapsing into a boolean:
conversation.fidelity—live,exact,result_only, ornone.exactmeans the agent resumes with the same history.result_onlymeans only the outcome of prior work survived, not the reasoning that produced it.workspace.fidelity—live,portable_tree, ornone.portable_treemeans the files come back as a captured copy rather than the original machine.environment.fidelityis alwayscurrent: a restored session runs on today’s managed environment, never a pinned historical one.
When canContinue is false, reason names the cause — checkpoint_unavailable,
conversation_not_restorable, or workspace_not_restorable. boundaryRunId, when
present, is the run at which the recoverable history ends; it is the honest
dividing line to draw in a UI between “the agent remembers this” and “it does
not”.
Capture is bounded. GET /v1/capabilities advertises the ceilings —
artifacts.conversationCheckpointMaxBytes (16 MiB) and
artifacts.workspaceCheckpointMaxBytes (256 MiB, also reported as
environments[0].workspace.maxCheckpointBytes). A working tree larger than that
cannot be captured, which is one way a session ends up unavailable with
reason: "workspace_not_restorable". Read the numbers from discovery rather than
hardcoding them.
When compute disappears mid-run
Section titled “When compute disappears mid-run”Losing a sandbox during a run is a designed-for case, not an outage:
- The run reaches a terminal state — typically
crashed, and its event iswamp.run.crashed. - Any open interaction expires with
wamp.interaction.expiredandreason: "workspace_lost". It can no longer be answered; submit a fresh turn instead. - Events already recorded stay recorded. The log is append-only and gap-free across sandbox boundaries — sequence numbers belong to the session.
- Artifacts already registered keep their manifests. Whether their bytes are still fetchable is a separate question, answered in Artifacts.
continuationis recomputed. Read it before deciding whether to retry the same session or open a new one.
The errors that mean “compute, not you” are worth handling explicitly:
| Status | Code | What to do |
|---|---|---|
503 |
cloud_workspace_starting |
Retry, honoring Retry-After: 2. A sandbox is coming up. |
503 |
cloud_workspace_unavailable |
No sandbox could be provided. Retry with backoff; surface it if it persists. |
409 |
cloud_workspace_expired |
The lease you were counting on is gone. Re-read the session and decide from continuation. |
409 |
cloud_workspace_expiring |
The lease is too close to its deadline for the requested work. Retry; a fresh lease follows. |
409 |
cloud_resume_unavailable |
The previous state cannot be restored. Start a new session for a clean run. |
409 |
cloud_turn_execution_lost |
The turn’s execution died with its sandbox. Submit a new turn. |
Note the asymmetry in how the retry hint arrives: for these codes
Retry-After is a header only, while a 429 rate-limit response carries
retryAfterSeconds in the body as well. Read the header.
Environments are discovery-only
Section titled “Environments are discovery-only”GET /v1/capabilities reports an environments array. Today it has exactly one
entry, and it is fixed:
{ "capabilities": { "environments": [ { "id": "managed", "label": "WAMP managed sandbox", "default": true, "runtimes": ["wamp", "claude-code"], "workspace": { "checkpoint": "portable_tree", "maxCheckpointBytes": 268435456 } } ] }}There is no environment selector. No request body accepts an environment id,
there is no environment resource to create or configure, and managed is the
only value the server produces. Read the entry for its workspace ceilings and
for the runtimes it lists as usable; do not build a picker for a list of one,
and do not send an environment field — request bodies reject unknown keys with
400 invalid_request.
You also do not choose where in the sandbox your work happens. If the session
has a repository source, the checkout is placed for you and the agent starts
there; if it has none, the agent gets an empty workspace. Paths are not part of
the contract.
Reading it before you act
Section titled “Reading it before you act”One call answers “can I send another turn, and what will the agent remember”:
curl -sS "$WAMP_API/v1/sessions/$SESSION_ID" \ -H "Authorization: Bearer $WAMP_TOKEN" \ | jq '{ workspace: .session.workspace, canContinue: .session.continuation.canContinue, state: .session.continuation.state, conversation: .session.continuation.conversation.fidelity, files: .session.continuation.workspace.fidelity, reason: .session.continuation.reason, busy: (.session.activeRun != null) }'A reasonable policy in a backend: if busy, wait. Else if canContinue, submit
the turn — provisioning is the server’s problem. Else open a new session and, if
the previous one produced a repository change, say so in the new task rather
than hoping the agent recalls it.
Related
Section titled “Related”- Sessions, turns, runs — the objects that survive a sandbox.
- Artifacts — what stays fetchable after compute is gone.
- Follow a run live — watching work that may span leases.
- Sessions operations — the generated
reference for the resource carrying
workspaceandcontinuation, and the API reference for both objects field by field.