Skip to content

Node Agent API

The gRPC service every client talks to: barista.node.v1alpha1.NodeAgent, served over TCP or a unix domain socket. The barista CLI is a thin client over it, and so is everything else.

The protobuf schema is the single source of truth. Generated clients exist for Rust and Python; any gRPC-capable language works.

grpcurl -plaintext 127.0.0.1:7070 list barista.node.v1alpha1.NodeAgent

Service

Identity and capabilities

RPC Returns
GetNodeInfo(GetNodeInfoRequest) NodeInfo

Lifecycle

Lifecycle mutations are asynchronous, idempotent operations except SetWake, which is a last-writer-wins assignment returning the updated Instance.

RPC Notes
CreateInstance(CreateInstanceRequest) Takes the spec, an idempotency key, and require_hardware_isolation.
StartInstance(StartInstanceRequest) From CREATED or STOPPED. From STOPPED this is a cold boot.
StopInstance(StopInstanceRequest) grace_seconds before kill.
PauseInstance(PauseInstanceRequest) keep_memory (unset means true), require_memory.
ResumeInstance(ResumeInstanceRequest) Target is instance_id (latest snapshot) or snapshot_id.
CheckpointInstance(CheckpointInstanceRequest) Live snapshot. Requires live_checkpoint.
CreateSnapshot(CreateSnapshotRequest) Retained, optionally named. Declares froze_workload when the source was running.
DestroyInstance(DestroyInstanceRequest) keep_snapshots.
SetWake(SetWakeRequest) Absolute wake timestamp; unset clears the alarm.

Introspection

RPC Returns
GetInstance(GetInstanceRequest) Instance
ListInstances(ListInstancesRequest) Filter by states and label_selector; returns bounded pages of at most 256 rows and below the server's encoded response budget. Follow next_page_token for a complete inventory.
ListSnapshots(ListSnapshotsRequest) Empty instance_id lists the whole node.
DeleteSnapshot(DeleteSnapshotRequest) Returns an Operation.
GetOperation(GetOperationRequest) Operation
CancelOperation(CancelOperationRequest) Calls an in-flight operation off and returns it CANCELED. Takes op_id and a human-readable reason; no idempotency key, because it creates no operation.
WatchEvents(WatchEventsRequest) stream Event

ListInstancesRequest.page_size selects the requested row bound. Zero uses the server default; values above 256 are refused. page_token is opaque and bound to the filter that produced it. Reusing it with different states or labels is refused. ListInstancesResponse.next_page_token is empty only after the filtered inventory is exhausted. Servers may return fewer rows than requested to keep the encoded response below the transport limit. The CLI's ls and doctor commands consume every page automatically.

CancelOperation records a cancellation; it does not interrupt work already under way. A substrate call in flight runs to completion, and its side effect may land after the cancellation is recorded. What the cancellation buys is that the operation's reported outcome is final: the executor's finalization cannot overwrite the answer you were given, so it stays CANCELED with its reason.

The instance still settles where the work left it. Cancelling moves no instance itself, but the finalization behind it runs after the work, so the state it records was measured on the substrate rather than inferred from the verb — a cancelled stop whose stop succeeded leaves the instance STOPPED, and one whose stop failed leaves it FAILED. An instance is not left stranded in the transitional state its submission wrote (STOPPING, PAUSING, RESUMING, …).

The reason is recorded on the operation and on the OPERATION_PROGRESS event, not in Operation.error, which stays unset for a cancellation. An operation this node does not know is NOT_FOUND; one that has already settled is FAILED_PRECONDITION, and the refusal leaves its recorded outcome untouched — read it back with GetOperation.

Guest passthrough

RPC Notes
Exec(stream ExecFrame) → stream ExecFrame Bidirectional: stdio, resize, exit status. One stream per exec.
ReadFile(ReadFileRequest) → stream FileChunk offset, limit (0 = to EOF).
WriteFile(stream WriteFileRequest) → WriteFileResponse First frame is WriteOpen.

Passthrough calls count as user activity and reset the TTL.

Messages

InstanceSpec

Immutable after create.

message InstanceSpec {
  string instance_id = 1;            // client-chosen ULID, unique per node
  TemplateRef template = 2;
  Resources resources = 3;           // vcpu, mem_mib, disk_mib
  Process process = 4;               // start_cmd, ready_cmd, env, workdir
  Hooks hooks = 5;                   // pre_snapshot_cmd, post_restore_cmd + timeouts
  uint64 ttl_seconds = 6;            // 0 = no TTL; reset on activity
  TtlAction ttl_action = 7;          // PAUSE (default) | STOP | DESTROY
  map<string, string> labels = 8;
  EgressPolicy egress = 9;
  optional TtlAction idle_action = 10; // absent = ignore workload idle hints
}

idle_action opts the instance into workload idle hints (see the Guest Agent API's WorkloadService). When present, an idle declaration triggers that action with the same capability-degradation semantics as ttl_action; absent, idle declarations have no effect.

TemplateRef

message TemplateRef {
  OciImageRef oci = 1;
  string runtime_bundle_ref = 3;     // pinned; must match exactly on resume
  string template_hash = 4;
  string arch = 5;                   // aarch64 | x86_64
}

message OciImageRef {
  string image = 1;                  // human-readable label; not identity
  string digest = 2;                 // sha256:… — required
}

An empty digest is rejected with INVALID_SPEC.

Instance

message Instance {
  InstanceSpec spec = 1;
  InstanceState state = 2;
  bool ready = 3;                    // ready_cmd result — not a state
  string runtime = 4;
  google.protobuf.Timestamp created_at = 5;
  google.protobuf.Timestamp updated_at = 6;
  google.protobuf.Timestamp ttl_deadline = 7;   // absent when no TTL
  string latest_snapshot_id = 8;
  google.protobuf.Timestamp wake_at = 9;        // absent when no alarm
  StopReason stop_reason = 10;                  // absent unless STOPPED
  InstanceNetwork network = 11;                 // absent unless RUNNING & dialable
}

message InstanceNetwork {
  string address = 1;   // published workload endpoint, host:port
}

network carries the host:port at which the instance's workload is dialable from wherever the operator declared the node reachable (--ingress-advertise): the host is that declaration, the port a listener the node allocated from --ingress-ports and forwards to the workload through the substrate's ingress. It is resolved from the substrate at read time — never cached — present only while the instance is RUNNING on a node that publishes workloads, and stable across pause/resume for the instance's lifetime. It is absent for a paused or stopped instance, on a node with no ingress advertise configured, on a runtime that publishes nothing (fake), and when the substrate could not be asked. A guest-internal sandbox address is never reported in its place. The workload learns the port to bind as $PORT unless its spec set one.

Snapshot

message Snapshot {
  string snapshot_id = 1;
  string instance_id = 2;
  SnapshotKind kind = 3;             // MEMORY_AND_DISK | DISK_ONLY
  string cpu_class = 4;              // restore-compat key
  string template_hash = 5;          // invalidation key
  string runtime_bundle_ref = 6;     // must match exactly on resume
  SnapshotTier tier = 7;             // LOCAL today; OBJECT_STORE is reserved
  uint64 size_bytes = 8;
  google.protobuf.Timestamp created_at = 9;
  HookOutcome pre_snapshot_hook = 10;
  string name = 11;                  // set for named snapshots
}

Operation

message Operation {
  string op_id = 1;
  string kind = 2;                   // "create" | "start" | "pause" | …
  string instance_id = 3;
  OperationState state = 4;          // QUEUED | RUNNING | AWAITING_INPUT | DONE | FAILED | CANCELED
  string current_step = 5;
  ErrorDetail error = 6;             // set when FAILED — never for CANCELED
  string degraded = 7;               // set when it succeeded through a downgrade
  google.protobuf.Timestamp created_at = 8;
  google.protobuf.Timestamp finished_at = 9;
}

degraded is never empty for a downgraded success, and a downgrade always also emits a DEGRADATION event.

NodeInfo

message NodeInfo {
  string node_id = 1;
  string arch = 2;
  string cpu_class = 3;              // snapshot restore-compat key
  repeated RuntimeInfo runtimes = 4;
  Resources total_resources = 5;
  Resources allocatable_resources = 6;
  string agent_version = 7;
  FleetInfo fleet = 8;               // bucket configured, leases held
}

message RuntimeCapabilities {
  bool memory_snapshot = 1;
  bool disk_snapshot = 2;
  bool live_checkpoint = 3;
  bool guest_agent = 4;
  bool hardware_isolation = 5;
  bool lazy_restore = 6;
  bool cow_fork = 7;
  bool egress_control = 8;
}

RuntimeInfo.health is an enum, not a bool: HEALTHY, UNREACHABLE, or UNSPECIFIED for an agent too old to report it. UNREACHABLE constrains mutations only — running sessions keep running and keep being reported as running.

Event

message Event {
  uint64 cursor = 1;                 // monotonic per node
  EventType type = 2;
  string instance_id = 3;
  string op_id = 4;
  InstanceState state = 5;           // for STATE_CHANGED
  string message = 6;
  google.protobuf.Timestamp at = 7;
}

WatchEventsRequest.from_cursor of 0 means new events only. A non-zero cursor replays everything after it, or fails with CURSOR_TOO_OLD if retention has already deleted those events.

Enums

InstanceState — CREATING, CREATED, STARTING, RUNNING, CHECKPOINTING, PAUSING, PAUSED, RESUMING, STOPPING, STOPPED, DESTROYING, DESTROYED, FAILED.

OperationState — QUEUED, RUNNING, AWAITING_INPUT, DONE, FAILED, CANCELED. AWAITING_INPUT is still in flight — a paused operation holds its session and a restart resolves it. CANCELED is terminal, carries no error, and is reached through CancelOperation; it means this operation's result will not be used, not that the work was stopped. See Lifecycle and operations.

SnapshotKind — MEMORY_AND_DISK, DISK_ONLY.

SnapshotTier — LOCAL, OBJECT_STORE.

TtlAction — PAUSE (the default when unspecified), STOP, DESTROY.

EventType — STATE_CHANGED, OPERATION_PROGRESS, READY_CHANGED, TTL_WARNING, DEGRADATION, WAKE_FIRED, RESTORED, FENCED, IDLE_FIRED.

SubstrateHealth — HEALTHY, UNREACHABLE, UNSPECIFIED.

ErrorReason — see Errors.

Versioning

The package is barista.node.v1alpha1. Breaking changes are gated by buf breaking: fields are reserved rather than reused, and a break requires explicit ratification. Hand-written duplicates of contract types are not supported — generate your client from the schema.