01Documentation
Supervised Sessions
A supervised session runs a process as a sandbox-owned resource instead of tying the process to one client connection. The session keeps a stable identity and a replayable event journal while its current operating-system process is represented by an attempt.
Use supervised sessions for:
- background workers and agent runtimes that must outlive an SDK call
- processes whose output must be replayed after a client reconnects
- interactive PTY workloads with explicit input, signals, and terminal resize
- processes that should restart after failure or after the sandbox runtime is replaced
Use Contexts for one-shot commands and REPL workflows. Use Sandbox Services when the workload needs a public HTTP entrypoint; a supervised session does not create a public URL by itself.
Closing an SDK event stream, SSE connection, or WebSocket only detaches that client. It does not stop the session and does not close process stdin. Stop, delete, signal, and EOF are explicit operations.
Choose The Right Execution Primitive#
| Primitive | Best For | Process Ownership |
|---|---|---|
| Context | One-shot commands and stateful REPL calls | The context API owns one command or REPL process |
| Supervised session | Long-running, reconnectable, restartable processes | The sandbox owns a stable session and its current attempt |
| Sandbox Service | Public HTTP routes, route policy, and on-demand command or function execution | The service runtime or your application owns the backing process |
| SSH | Human shell access and standard file transfer | The SSH connection owns the interactive shell |
Mental Model#
| Object | Lifetime | Meaning |
|---|---|---|
| Session | Until explicitly deleted, sandbox deletion, or sandbox hard_ttl | Stable identity, process specification, desired state, and lifecycle policy |
| Attempt | One operating-system process execution | Replaced after a restart, explicit replacement, specification change, or runtime recovery |
| Attachment | One SDK event stream or WebSocket connection | Temporary view over retained and live events |
| Event | Retained according to the session policy | Ordered record of lifecycle, output, input acknowledgement, and control activity |
The important distinction is between two kinds of continuity:
- Connection continuity: disconnecting a client leaves the same attempt running.
- Runtime continuity: replacing the sandbox runtime ends the old attempt. The session identity and journal remain, and
runtime_recoverydecides whether Sandbox0 starts a new attempt.
Every event has a monotonically increasing seq. An attempt has its own attempt_id, and events also include runtime_generation so clients can distinguish output produced before and after sandbox runtime replacement.
Create A Long-Running Session#
The following examples create a worker that prints READY, emits one line per second, restarts after a non-zero exit, and starts a new attempt when the sandbox runtime is replaced.
The SDK snippets assume you already created a client and selected a sandbox as shown in Get Started.
gosession, err := sandbox.CreateSession(ctx, apispec.ExecutionSessionSpec{ Name: apispec.NewOptString("docs-worker"), Command: []string{"/bin/sh", "-lc", "echo READY; i=0; while true; do i=$((i+1)); echo tick-$i; sleep 1; done"}, Lifecycle: apispec.NewOptExecutionSessionLifecycleSpec( apispec.ExecutionSessionLifecycleSpec{ Restart: apispec.NewOptExecutionSessionRestartSpec( apispec.ExecutionSessionRestartSpec{ Policy: apispec.NewOptExecutionSessionRestartPolicy( apispec.ExecutionSessionRestartPolicyOnFailure, ), }, ), RuntimeRecovery: apispec.NewOptExecutionSessionRuntimeRecoveryPolicy( apispec.ExecutionSessionRuntimeRecoveryPolicyRestart, ), }, ), Readiness: apispec.NewOptExecutionSessionReadinessSpec( apispec.ExecutionSessionReadinessSpec{ Type: apispec.NewOptExecutionSessionReadinessType( apispec.ExecutionSessionReadinessTypeOutput, ), Output: apispec.NewOptString("READY"), TimeoutMs: apispec.NewOptInt32(30_000), }, ), }, &sandbox0.CreateSessionOptions{ IdempotencyKey: "docs-worker-v1", }) if err != nil { log.Fatal(err) } fmt.Println(session.ID)
Creation defaults to desired_state: running, so the first attempt starts immediately. An Idempotency-Key lets a client safely retry the same creation request: the same key and normalized specification return the existing session, while reusing the key with a different specification returns a conflict.
Follow Output And Reconnect#
Use the SDK event stream for one-way output consumption. The stream first replays retained events after the requested cursor and then continues with live events.
gostream, err := sandbox.WatchSessionEvents(ctx, session.ID, nil) if err != nil { log.Fatal(err) } var lastSeq int64 outputCount := 0 for outputCount < 2 { event, err := stream.Recv() if err != nil { log.Fatal(err) } lastSeq = event.Seq if encoded, ok := event.DataBase64.Get(); ok { data, err := base64.StdEncoding.DecodeString(encoded) if err != nil { log.Fatal(err) } fmt.Print(string(data)) outputCount++ } } if err := stream.Close(); err != nil { log.Fatal(err) } // Closing the stream did not stop the session. Reattach after the last // processed sequence to receive only newer events. stream, err = sandbox.WatchSessionEvents(ctx, session.ID, &sandbox0.SessionEventStreamOptions{ After: lastSeq, }) if err != nil { log.Fatal(err) } event, err := stream.Recv() if err != nil { log.Fatal(err) } fmt.Printf("reattached at seq=%d\n", event.Seq) if err := stream.Close(); err != nil { log.Fatal(err) }
Commit your application side effect before saving seq. On reconnect, replay is expected, so consumers should deduplicate events by sequence.
Paged Events, SSE, And WebSocket#
| SDK Operation | Use It For | Reconnect Behavior |
|---|---|---|
ListSessionEvents / list_session_events / listSessionEvents | Bounded, paged reads | Continue after the final processed seq |
WatchSessionEvents / watch_session_events / watchSessionEvents | Retained plus live events over SSE | Reopen with after or Last-Event-ID |
ConnectSession / connect_session / connectSession | Duplex input, signal, resize, and events over WebSocket | Reopen with after; disconnect does not imply EOF |
SSE is the simplest default for workers and log consumers. Use WebSocket when one attachment needs both event delivery and interactive PTY control.
Inspect And Manage Sessions#
Session lifecycle is controlled independently from attachments.
| Task | Go | Python | TypeScript | CLI |
|---|---|---|---|---|
| List | ListSessions | list_sessions | listSessions | s0 sandbox session list |
| Inspect | GetSession | get_session | getSession | s0 sandbox session get |
| Stop | SetSessionDesiredState(..., apispec.ExecutionSessionDesiredStateStopped) | set_session_desired_state(..., ExecutionSessionDesiredState.STOPPED) | setSessionDesiredState(..., 'stopped') | s0 sandbox session stop |
| Start | SetSessionDesiredState(..., apispec.ExecutionSessionDesiredStateRunning) | set_session_desired_state(..., ExecutionSessionDesiredState.RUNNING) | setSessionDesiredState(..., 'running') | s0 sandbox session start |
| Replace attempt | CreateSessionAttempt | create_session_attempt | createSessionAttempt | s0 sandbox session attempt --replace |
| Delete | DeleteSession | delete_session | deleteSession | s0 sandbox session delete |
Stopping sets the desired state to stopped and ends the current attempt while retaining the session and journal. Starting creates a new attempt. Deleting stops the current attempt and removes the session identity and retained events.
Do not use a signal as a replacement for stop when you want the session to remain stopped. A signal can make the process exit, after which its restart policy may create another attempt. Set the desired state to stopped instead.
Send Input And EOF#
Pipe and PTY sessions can accept binary-safe input. The examples below assume session is a running process that reads stdin, such as /bin/cat.
goattempt, ok := session.Attempt.Get() if !ok { log.Fatal("session has no current attempt") } _, err := sandbox.WriteSessionInput(ctx, session.ID, apispec.ExecutionSessionInputRequest{ InputID: "input-1", ExpectedAttemptID: apispec.NewOptString(attempt.ID), DataBase64: apispec.NewOptString(base64.StdEncoding.EncodeToString([]byte("hello\n"))), EOF: apispec.NewOptBool(true), }) if err != nil { log.Fatal(err) }
Input acceptance means the bytes entered the current attempt's input queue; it does not prove that the application consumed them.
- Generate a unique
input_idfor each logical write. - Retry an ambiguous request with the same
input_idand identical content. - Include
expected_attempt_idwhen input belongs to one specific process attempt. - Set
eof: trueto close stdin after earlier queued bytes. Detaching a client is never EOF.
No process API can guarantee exactly-once application consumption. If the process receives bytes before the input receipt becomes durable, retrying after a transport failure can replay them. Use an application-level request ID when duplicate consumption would be unsafe.
Pipes And PTY#
| Mode | Output | Best For |
|---|---|---|
pipes | Separate stdout and stderr events | Workers, servers, and non-interactive commands |
pty | Combined pty output | Shells and terminal applications |
For PTY sessions, configure initial rows, cols, and term, then use ResizeSessionTerminal, resize_session_terminal, resizeSessionTerminal, or s0 sandbox session resize when the terminal size changes. Include the expected attempt ID so a stale terminal attachment cannot resize a replacement attempt.
Lifecycle And Recovery#
Desired State And Phase#
lifecycle.desired_state is the state requested by the user. phase is the supervisor's current observation.
| Phase | Meaning |
|---|---|
pending, starting | The session is waiting for or starting an attempt |
running | The current attempt passed readiness |
backoff | A restart is scheduled after a failed attempt |
stopping, stopped | The session is stopping or has no running attempt by request |
exited | The attempt exited and restart policy did not restart it |
failed | Startup, readiness, or restart limits failed |
paused, suspended | The attempt was paused or ended during sandbox runtime replacement |
Restart Policy#
Restart policy applies when a process exits inside the current sandbox runtime.
| Policy | Behavior |
|---|---|
never | Do not restart after exit |
on_failure | Restart only after a non-zero exit |
always | Restart after any exit unless the session is being stopped or deleted |
The default restart window allows five restarts within 60 seconds with exponential backoff from 250 ms to 5 seconds.
Runtime Recovery#
Runtime recovery applies after pause/resume or another sandbox runtime replacement.
| Policy | Behavior |
|---|---|
restart | Start a new attempt when the session was active before replacement |
stop | Keep the session and journal but change the desired state to stopped |
Runtime recovery is process restart, not process checkpoint/restore. The old PID, memory, sockets, terminal connection, and in-flight requests do not survive runtime replacement.
The session is scoped to one sandbox identity. A fork receives filesystem state from its source, but copied supervised sessions are cleared before the fork starts so it cannot execute the source sandbox's processes. Sandbox deletion and hard_ttl delete the session and journal.
Readiness#
Readiness controls when an attempt moves from starting to running.
| Type | Ready When |
|---|---|
process | The operating-system process starts |
delay | delay_ms elapses while the attempt stays alive |
output | The configured byte sequence appears in stdout, stderr, or PTY output |
For delay and output, timeout_ms terminates an attempt that never becomes ready. Readiness observes process bytes only; it does not perform HTTP, TCP, or application-protocol health checks.
idle_timeout_seconds measures session activity such as process output, accepted input, signals, and terminal resize. Merely keeping an attachment open or reading session metadata does not keep an otherwise idle session active. max_lifetime_seconds is measured from session creation. Set either value to 0 to disable that limit.
Event Journal And Delivery#
The journal contains lifecycle events, process output, accepted input receipts, signals, and terminal resize events. Common event groups are:
| Group | Examples |
|---|---|
| Session lifecycle | session.created, session.ready, session.backoff, session.failed, session.expired |
| Attempt lifecycle | attempt.starting, attempt.started, attempt.stopping, attempt.exited |
| Process output | output with stream: stdout, stderr, or pty and base64-encoded bytes |
| Control | input.accepted, signal.sent, terminal.resized |
Delivery is cursor-based and at least once:
- Process an event and commit its application side effect.
- Save the event's
seq. - Reconnect after that sequence.
- Deduplicate replayed events by
seq.
The default retention is 64 MiB and 24 hours per session. If a requested cursor is older than retained history, the SDK returns event_cursor_expired with the earliest available sequence. Read the current session state, reconcile it with your application, and continue from the retained window.
A slow live subscriber can be detached when its in-memory buffer fills. Retained journal data remains available, so reconnect from the last committed sequence.
CLI Reference#
| Command | Purpose |
|---|---|
s0 sandbox session list <sandbox-id> | List sessions |
s0 sandbox session create <sandbox-id> -- <command...> | Create and start a session |
s0 sandbox session create <sandbox-id> --spec-file session.yaml | Create from a complete specification |
s0 sandbox session get <sandbox-id> <session-id> | Inspect phase, attempt, generation, and cursor |
s0 sandbox session update <sandbox-id> <session-id> --spec-file session.yaml | Replace the complete specification |
s0 sandbox session start|stop <sandbox-id> <session-id> | Change desired state |
s0 sandbox session attempt <sandbox-id> <session-id> --replace | Replace the current attempt |
s0 sandbox session input <sandbox-id> <session-id> ... | Send input or explicit EOF |
s0 sandbox session signal <sandbox-id> <session-id> TERM | Signal the current attempt |
s0 sandbox session resize <sandbox-id> <session-id> <rows> <cols> | Resize a PTY |
s0 sandbox session events <sandbox-id> <session-id> --follow | Replay and follow events |
s0 sandbox session delete <sandbox-id> <session-id> | Stop and delete the session and journal |