Skip to content

Lifecycle and operations

Every change you request is a durable, idempotent operation. Every state a session passes through is visible. Nothing happens that you cannot follow.

The state machine

                    ┌──────────────── Checkpoint ───────────────┐
                    ▼                                           │
CREATING → CREATED → STARTING → RUNNING ──────────→ CHECKPOINTING
                        ▲          │   │
                        │          │   └─ Pause →  PAUSING → PAUSED
                        │          │                            │
                        │        Stop → STOPPING → STOPPED      │
                        │                  │                    │
                        └──── Start ───────┘        Resume → RESUMING → RUNNING

any transitional state → FAILED        any state → Destroy → DESTROYING → DESTROYED
State Means
CREATED Configuration is journaled. No sandbox exists yet.
RUNNING The sandbox is up. Check ready separately for whether the workload can serve.
CHECKPOINTING Transient. A live snapshot is in progress; the workload never stopped, and the session returns to RUNNING by itself.
PAUSED Zero sandbox resources. Only snapshot files and metadata remain.
STOPPED Clean shutdown. Disk preserved, memory lost. Start from here is a cold boot.
FAILED The failing operation is recorded. Destroy is always legal.
DESTROYED Terminal.

STOPPED also carries why: a workload that exited on its own reports its exit code, distinctly from a session that was stopped by request. For a cron-shaped agent that wakes, works, and exits, that reason is the result.

Operations

Every mutating call — create, start, stop, pause, resume, checkpoint, destroy, delete snapshot — returns an Operation immediately and does the work asynchronously:

{
  "op_id": "01J9Z…",
  "kind": "resume",
  "instance_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
  "state": "OPERATION_STATE_RUNNING",
  "current_step": "restoring memory",
  "created_at": "2026-08-08T10:14:02Z"
}

Follow it with GetOperation, or watch the event stream. The CLI does the latter for you: barista resume <instance-id> subscribes first, submits, then waits, so an operation that finishes instantly cannot slip past you.

Journaled before anything happens

An operation is written to the node's journal — SQLite in WAL mode — before any side effect starts. If the node is kill -9ed mid-flight, the journal is replayed on restart and every operation either resumes from its last durable step or is marked FAILED with its cleanup executed.

The invariant this protects is worth stating plainly: no orphan sandboxes, and no half-created sessions invisible to the API. Reconciliation sweeps everything the platform created for a session — sandboxes, volumes, credentials — not just the parts that are easy to enumerate.

Idempotency

Every mutating Contract A call takes an idempotency_key. Replaying an API request with the same key returns the original operation instead of doing the work again.

The CLI generates a fresh key per invocation and does not expose it. Two separate barista stop commands are therefore two intentions. An API client retrying one timed-out request should retain and reuse its key.

One at a time

One in-flight mutating operation per session. A conflicting call fails with CONCURRENT_OPERATION rather than interleaving with the one in progress. Destroy may cancel what is running.

Events

WatchEvents streams everything the node does, with a monotonic cursor per node:

Event Fires when
STATE_CHANGED A session enters a new state.
OPERATION_PROGRESS An operation advances a step.
READY_CHANGED ready_cmd's verdict flips.
TTL_WARNING A TTL deadline is approaching.
DEGRADATION Anything was downgraded — a disk-only snapshot, a cold-boot fallback, a duty that could not run.
WAKE_FIRED A scheduled alarm fired.
RESTORED Post-restore duties are complete, with clock-drift metrics.
FENCED This node lost a fleet lease and is stopping the superseded workload.
barista events                          # everything on this node
barista events --instance <instance-id> # one direct-node instance
barista events --from-cursor 41827      # replay from where you stopped

Hold the cursor and you can resume the stream after a disconnect without missing anything. The journal has a retention window: a cursor older than its floor is refused with CURSOR_TOO_OLD rather than served a stream that silently skips deleted events. Resynchronise with ListInstances and carry on from the current cursor.

DEGRADATION is the event to alert on. It is how Barista tells you that something worked, but not the way you asked.