Guest Agent API¶
barista.guest.v1alpha1.GuestAgent is the contract between a node and the daemon
inside its sandboxes. It is internal: the Node Agent is its only client, and
it is never exposed to you directly.
It is documented because its semantics are visible through the Node Agent API — readiness, hook outcomes, restore drift — and because knowing what it does tells you what your image has to cooperate with.
Service¶
service GuestAgent {
rpc Health(HealthRequest) returns (HealthResponse);
rpc Exec(stream ExecFrame) returns (stream ExecFrame);
rpc ReadFile(ReadFileRequest) returns (stream FileChunk);
rpc WriteFile(stream WriteFileRequest) returns (WriteFileResponse);
rpc StatPath(StatPathRequest) returns (StatPathResponse);
rpc RunHook(RunHookRequest) returns (RunHookResponse);
rpc RunRestoreDuties(RestoreDutiesRequest) returns (RestoreDutiesResponse);
}
// The one surface the *workload* calls, on a separate in-sandbox unix socket
// whose path is injected as BARISTA_WORKLOAD_SOCKET (barista-031).
service WorkloadService {
rpc DeclareIdle(DeclareIdleRequest) returns (DeclareIdleResponse);
}
WorkloadService is served on its own socket, unauthenticated (caller and agent
share the sandbox's one trust domain), and carries only DeclareIdle — the
management RPCs above are not reachable on it. The agent records the declaration
and reports it as HealthResponse.idle_declared; the Node Agent decides what to
do with it (InstanceSpec.idle_action). See
the idle hint and the
guest agent concept.
What an Exec command's environment contains¶
Three layers, applied in this order. The order is the contract, because each step is what makes the next one safe to state:
- The bootstrap scrub. Every variable in the bootstrap channel — the instance token, the guest and workload socket paths, the TCP port, the three TLS file paths, the encoded process and hooks specs — is removed. That environment is the agent's, not its children's (barista-043).
- The workload's
Process.env. AnExecruns in the session, not next to it, so it observes the same environment as the process already running there. This is where a platform resolves an app's declared secrets — a delegated grant among them — and it is how a client reads back a credential the provider resolved for it (barista-071). - The request's
ExecStart.env. Applied last, so a variable the caller names explicitly is delivered unchanged: the authenticated request is the host speaking, and step 1 removes an inherited default, not an explicit grant.
Step 2 is not a new exposure. An Exec runs same-uid with the workload, so
/proc/<workload>/environ was already readable to it; what changed is that the
value is delivered rather than recovered. The asymmetry with step 1 is
deliberate and holds in both directions: the workload is untrusted code that
must not acquire the agent's credentials by default, whereas an Exec is
the host re-entering a session it owns to read values the host itself put there.
BARISTA_WORKLOAD_SOCKET is injected for the workload alone, after its spec
env, so it is not part of Process.env and does not reach an exec'd command. An
exec'd command has no contract claim on the idle-declaration surface.
Transport and bootstrap¶
The agent authenticates with a per-session token carried in gRPC metadata
(barista-instance-token), and, on the hypeman transport, per-instance
mutual TLS. Connection direction is transport-dependent: on hypeman the
agent binds a TCP listener inside the VM (port 7071) that the host dials; on
fake (and the deferred runsc path) the host reaches it through an exec
bridge or unix socket, with no inbound network port.
| Runtime | Status | Injection and channel |
|---|---|---|
hypeman |
Implemented | Guest binary and credential volume at sandbox create; host dials the guest's in-VM listener (port 7071). |
fake |
Implemented for tooling | Entrypoint wrapper and Docker exec bridge; no inbound listener. |
runsc |
Deferred | The transport shape is reserved for the rank-2 tier; no backend is implemented. |
The token is a credential with a lifecycle: its volume is created with the session, tagged to the owning node, and reaped when the session goes away — including when the session never made it into the journal.
Health and readiness¶
message HealthResponse {
bool alive = 1;
bool ready = 2; // last ready_cmd verdict
int32 ready_cmd_exit = 3;
google.protobuf.Timestamp last_user_activity = 4;
google.protobuf.Timestamp guest_time = 5; // for clock-drift metrics
google.protobuf.Timestamp idle_declared = 6; // last DeclareIdle, else absent
}
last_user_activity is the guest's own activity clock, which is what TTL
decisions are made against. idle_declared carries the workload's last
DeclareIdle; the Node Agent guards it against both the run epoch and
last_user_activity before acting.
Hooks¶
message RunHookRequest {
HookKind kind = 1; // PRE_SNAPSHOT | POST_RESTORE
uint32 timeout_ms = 2;
}
message RunHookResponse {
bool ran = 1; // false when no hook is configured
bool timed_out = 2;
int32 exit_code = 3;
string stdout_tail = 4;
string stderr_tail = 5;
}
The outcome of PRE_SNAPSHOT is recorded on the Snapshot record, so you can
tell after the fact whether a snapshot was taken over a quiesced workload or a
timed-out one.
Restore duties¶
This is a separate RPC from RunHook for a specific reason: RunHook runs
your commands and cannot carry host-supplied material.
message RestoreDutiesRequest {
bytes entropy = 1; // fresh host CSPRNG bytes — required
google.protobuf.Timestamp host_time = 2; // step the guest clock to this
}
message RestoreDutiesResponse {
uint32 entropy_bytes_mixed = 1;
bool entropy_credited = 2; // credited, or only mixed
int64 clock_drift_ms = 3; // guest minus host, before the step
bool clock_stepped = 4;
string degraded = 5; // empty when every duty ran as intended
}
Ordering is normative: duties run before POST_RESTORE, so your reconnect
command already sees fresh entropy and a stepped clock.
entropy is required. A reseed with nothing to mix cannot de-duplicate two
restores of one snapshot, so the agent rejects the request rather than reporting
success. Reseeding forces a CRNG reseed as well as mixing, because a restored
guest's CRNG key and reseed timer come back byte-identical — mixing alone leaves
the first draws repeatable.
entropy_credited and clock_stepped are reported separately from degraded
so a sandbox that lacks the capability to credit entropy or set the clock says
exactly which duty it could not perform.