Documentation
¶
Index ¶
- Constants
- Variables
- func BackgroundRequestData(bgID string, payload map[string]any) map[string]any
- func ExecRequestData(execID string, cmd toolbroker.ShellCommand) map[string]any
- func IsRefused(verdict string) bool
- func OutcomeClass(err error) string
- func RefusedVerdict(reason string) string
- func RelayInbound(ctx context.Context, session *LeaseSession, tools *ToolServer, ...)
- func RelayInboundServing(ctx context.Context, session *LeaseSession, servers MachineServers, ...)
- func RelayInboundWithBackground(ctx context.Context, session *LeaseSession, tools *ToolServer, ...)
- func ShipLogs(ctx context.Context, current Identity, lines []LogLine, config LogShipConfig) error
- func StartWorkspaceSync(ctx context.Context, cfg SyncConfig) (stop func(), err error)
- func WorkspaceRequestData(wsID, op, root string, payload map[string]any) map[string]any
- type BackgroundServer
- type BootstrapConfig
- type EngineRequest
- type EngineResult
- type FrameLedger
- type FrameSink
- type Identity
- type Lease
- type LeaseSession
- func (l *LeaseSession) Close() error
- func (l *LeaseSession) Complete(ctx context.Context, outcome, reason, stderrDigest string) error
- func (l *LeaseSession) Lease() Lease
- func (l *LeaseSession) ReceiveMessage(ctx context.Context) (contracts.RunnerMessage, error)
- func (l *LeaseSession) SendEngineFrame(ctx context.Context, frame contracts.EngineFrame) error
- func (l *LeaseSession) SendExecResult(ctx context.Context, message contracts.RunnerMessage) error
- type LeaseWorkspaceClient
- func (c *LeaseWorkspaceClient) Deliver(message contracts.RunnerMessage)
- func (c *LeaseWorkspaceClient) Fetch(ctx context.Context, digest string) ([]byte, error)
- func (c *LeaseWorkspaceClient) Head(ctx context.Context) (ws.Manifest, bool, error)
- func (c *LeaseWorkspaceClient) Missing(ctx context.Context, digests []string) ([]string, error)
- func (c *LeaseWorkspaceClient) Publish(ctx context.Context, m ws.Manifest) (string, error)
- func (c *LeaseWorkspaceClient) Put(ctx context.Context, digest string, body []byte) error
- type Limits
- type LogBuffer
- type LogLine
- type LogShipConfig
- type MachineServers
- type RenewConfig
- type ServeConfig
- type Session
- type Settings
- type SettingsConfig
- type StreamSupervisor
- type Supervisor
- type SyncConfig
- type ToolServer
- type WorkspaceClient
- type WorkspaceServer
Constants ¶
const ( // VerdictApplied: this process changed its behaviour. The value is live now. VerdictApplied = "applied" // VerdictNotRead: this runner build reads no such setting, so nothing will happen to it — not now and // not after a restart. It is reported rather than silently dropped because the alternative is a panel // showing a saved value against a machine that will never act on it, which is the defect the whole // surface exists to prevent, moved one hop further away where it is harder to see. VerdictNotRead = "not_read" // VerdictRefused is the PREFIX of a refusal, and the prefix — not the whole string — is the contract. // A machine that will not take a value says so WITH its reason attached ("refused: not a positive // integer"), because "refused" on its own sends an operator to read logs on a machine they may have no // shell on. Until 2026-08-06 that string was an ad-hoc literal at one call site: the reason reached the // panel and NOTHING COULD CLASSIFY IT. The panel is TypeScript and cannot ask Go what a refusal looks // like, so "show me the machines that rejected a setting" had no answer, and a second refusal site would // have invented its own wording. Build one with RefusedVerdict and read one with IsRefused. VerdictRefused = "refused" )
Verdicts a machine reports about one setting. They are the machine's OWN answer and the control plane does not derive them: whether a value can take effect without a restart is a fact about this binary's code, and a control plane that computed its own would be reporting the machine's state from a table instead of from the machine.
const ( // ExecRequestType carries one command the control plane asks THIS machine to run. ExecRequestType = "exec.request" // ExecResultType carries the answer back. It is always sent — see ToolServer.Handle. ExecResultType = "exec.result" // The BACKGROUND triple (A.3 T7). Siblings of exec.request, not subtypes of it, and the difference // is lifetime: an exec.request is answered inside the tool call that made it, while these three are // about a process that OUTLIVES its attempt — so they arrive on a connection that may be parked // rather than serving a lease, and the control plane routes their answers by machine rather than by // attempt. // // The names were measured free before they were chosen (2026-08-04): // // grep -rhoE '"(bg|probe|kill)\.[a-z_]+"' --include='*.go' . -> (nothing) // // which is the check `tool.*` failed in T1: that spelling already had sixteen hits on engine.v1, and // a wire type that greps together with something else is one somebody eventually confuses. BackgroundStartType = "bg.start" BackgroundProbeType = "bg.probe" BackgroundKillType = "bg.kill" // BackgroundResultType answers all three. ONE reply type rather than three, because the correlation // id already says which question is being answered and a second discriminator would be a second // thing to keep in step. BackgroundResultType = "bg.result" // BackgroundErrorHandleLost is `error_kind` for the ONE refusal the control plane BRANCHES on rather // than merely reports (toolbroker.ErrHandleLost). It exists because a wire flattens a typed error into // a string: before it, three control-plane arms tested errors.Is(err, ErrHandleLost) and none could // ever be true for a task on a machine — so a handle that could not be proven ours fell through to the // generic arm, and "a lost handle is settled as lost, never signalled" became "a lost handle errors on // every tick forever". // // A KIND AND NOT A MESSAGE MATCH. The refusal text is the machine's own and stays the machine's; a // control plane that recognised it by prefix would be one adapter's wording away from silently losing // the branch again, which is exactly how it was lost the first time. BackgroundErrorHandleLost = "handle_lost" )
The runner.v1 message types this file adds, and the first pair on that wire addressed to the MACHINE rather than to the engine it supervises. Every type that existed before them — lease.offer, lease.complete, engine.frame, engine.ready, controller.frame, controller.test — is about the engine's lifecycle, and the one control-plane->runner message among them (controller.frame) is relayed straight into the engine subprocess's stdin without the runner reading its type (stream.go injectControllerFrames). So a tool call could not be expressed at all: there was nothing a control plane could say to a machine that the machine itself would act on.
They are a SIBLING of controller.frame, not a subtype of it, because the runner does the opposite thing with them: controller.frame is relayed, exec.request is EXECUTED.
The domain is `exec` and not `tool` or `shell` because both of those spellings are already taken in this tree by unrelated namespaces, and a wire type that greps together with something else is a wire type somebody will eventually confuse: "tool.request"/"tool.result" are ENGINE frame types on the engine.v1 protocol (apps/control-plane/internal/execution/tool_dispatch.go:449), and "shell.exec" is a worker capability operation name (apps/control-plane/internal/workers). Neither travels on runner.v1, and none of the three should answer the same search.
const ( // WorkspaceGrantsRequestType asks for presigned URLs for a set of blobs (PUT, or GET when read). WorkspaceGrantsRequestType = "workspace.grants.request" // WorkspaceGrantsResultType carries them back. WorkspaceGrantsResultType = "workspace.grants.result" // WorkspacePublishRequestType asks the control plane to record a manifest and move the head. WorkspacePublishRequestType = "workspace.publish.request" // WorkspacePublishResultType carries the manifest id, or the refusal. WorkspacePublishResultType = "workspace.publish.result" // WorkspaceHeadRequestType asks where the workspace currently is. WorkspaceHeadRequestType = "workspace.head.request" // WorkspaceHeadResultType carries the manifest, or "never published". WorkspaceHeadResultType = "workspace.head.result" )
The workspace-store message types the MACHINE originates.
‼️ THEY GO ON THE LEASE CONNECTION RATHER THAN ON NEW HTTP ROUTES, and that is the whole reason this file is small. The agent already holds one outbound `wss://` connection under its enrolled identity, and it is duplex: the control plane sends ws.request and exec.request down it, the machine sends results and engine frames back up. A machine-originated request is a frame type this channel did not have, not a surface it did not have — no new listener, no second credential, no inbound port.
It is the mirror of execution.RemoteWorkspace: there the control plane asks and the machine answers, here the machine asks and the control plane answers. The correlation id and the pending map below are that file's shape, reflected.
const ( // WorkspaceRequestType carries one workspace operation the control plane asks THIS machine to // perform, named by data.op. WorkspaceRequestType = "ws.request" // WorkspaceResultType carries the answer back. It is always sent — see WorkspaceServer.Handle. WorkspaceResultType = "ws.result" )
THE WORKSPACE PAIR (A.3 T5), AND IT IS A SIBLING OF exec.* AND bg.*, NOT A SUBTYPE OF EITHER.
exec.request asks this machine to RUN something; ws.request asks it to READ OR WRITE something. They are separate because their answers are separate: a command answers with an exit code and captured output, a file operation answers with bytes or a confinement refusal, and folding the second into the first would mean a traversal refusal arriving as `exit 1` with a message the control plane would then have to parse. This tree already records what parsing a message instead of reading a code costs.
WHY A MACHINE NEEDS THIS AT ALL. Before it, five of the six coding tools reached the workspace through the control plane's own filesystem, so the moment a lease could place a run on another machine the shell tool edited files on a Mac and the file tool read a directory somewhere else. Measured 2026-08-04, before this file existed:
grep -n "WorkspaceRoot" apps/control-plane/internal/execution/tools/{file,commit,push,media}.go
# -> five call sites, every one of them a path in the control plane's own filesystem
The name was measured free before it was chosen, the same check bg.* passed and `tool.*` failed:
grep -rhoE '"ws\.[a-z_]+"' --include='*.go' . -> (nothing)
const ( WorkspaceOpOpen = "open" // create the §29.9 layout under an allocation root, idempotently // WorkspaceOpMaterialize writes the workspace store's current head into this allocation. // // ‼️ IT IS THE CLONE'S REPLACEMENT, NOT ITS COMPANION. Where the store is the origin, the repository // has already been fetched ONCE, centrally, into content-addressed blobs; asking the machine to // clone as well fetches the same bytes a second time, over a WAN, under a credential that then has // to reach the machine at all. Materialising fetches only the blobs this machine does not already // hold, through short-lived signed URLs, with no repository credential anywhere near it. WorkspaceOpMaterialize = "materialize" WorkspaceOpRead = "read" // confined file read WorkspaceOpWrite = "write" // confined atomic file write WorkspaceOpList = "list" // confined directory listing WorkspaceOpStat = "stat" // confined path metadata WorkspaceOpChecksum = "checksum" // confined content digest WorkspaceOpHead = "head" // the workspace repository's current commit + tree WorkspaceOpCommit = "commit" // commit the worktree under the platform's fixed identity WorkspaceOpGlob = "glob" // confined filename search, newest modification first WorkspaceOpGrep = "grep" // confined content search, RE2 syntax // WorkspaceOpArchive and WorkspaceOpRestore move an allocation BETWEEN machines (Faz A.5 T5). They // are the two verbs without which a pool of Macs cannot exist: everything above acts on a tree that // is already on this machine, and these two are how a tree GETS here from somewhere else. // // WHY THEY ARE ON THIS SURFACE AND NOT ON THE CONTROL PLANE'S. The control plane already had both — // snapshot.Archive and snapshot.Restore — and called them against ITS OWN filesystem, which is // correct exactly while the control plane and the machine are one host and silently wrong the moment // they are not (idle_release.go named that ceiling; this is it being paid). The bytes live where the // lease placed them, so the party that can tar them is the party holding them. // // THE ARCHIVE CROSSES WHOLE, AND THAT IS THE BOUND ON THEM. One ws.request carries one tar, so an // allocation bigger than the lease's frame limit cannot move — the same bound // execution.MaxSnapshotArchiveBytes already places on what the object store will take back. A // streaming pair is what a repository larger than that needs, and it is a different design, not a // bigger number. WorkspaceOpArchive = "archive" // tar this allocation (SAN-005 manifest) for the control plane WorkspaceOpRestore = "restore" // untar a control-plane-held archive into this allocation, verified )
The operations one ws.request can name. They are the union of what the six coding tools and the provisioner need and NOTHING ELSE: this is a surface a control plane can drive against a machine's disk, so every verb on it is a verb somebody has to justify.
const EngineProtocolV1 = "engine.v1"
EngineProtocolV1 is the JSONL frame protocol the supervisor speaks with an engine.
const MaxLogBatch = 500
MaxLogBatch is how many lines one shipment carries. A machine that was offline has a backlog, and the answer to a backlog is several shipments — one unbounded body would be a body large enough to exhaust the reader that accepts it, sent by the machine that is least able to notice.
const RunnerProtocolV1 = "runner.v1"
RunnerProtocolV1 is the control-plane leasing protocol the session speaks.
Variables ¶
var ( // ErrInvalidEngineOutput reports stdout that is not strict, in-protocol JSONL. ErrInvalidEngineOutput = errors.New("invalid engine output") // ErrStdoutLimit reports stdout that exceeded its configured bound. ErrStdoutLimit = errors.New("engine stdout exceeded configured bound") // ErrEngineTimeout reports an engine force-killed at the wall-time bound. It is a // terminal, lost outcome — never a success. ErrEngineTimeout = errors.New("engine exceeded wall-time bound") // ErrEngineExit reports a non-zero engine exit. ErrEngineExit = errors.New("engine exited unsuccessfully") // ErrForbiddenEnv reports a requested environment key outside the engine allowlist. ErrForbiddenEnv = errors.New("engine environment key is not on the allowlist") )
var ErrFrameHashConflict = errors.New("frame id reused with a different payload hash")
ErrFrameHashConflict reports a frame id reused with a different payload — a protocol violation under the stable request-id discipline.
var ErrIncompatibleEngine = errors.New("engine did not complete the handshake within the startup deadline")
ErrIncompatibleEngine reports an engine that did not answer the supervisor.hello with engine.ready inside the startup deadline. It is a terminal attempt failure — nothing downstream proceeds past an incomplete handshake (ENG-001).
var ErrMutableLeaseImage = errors.New("lease offer image must be an immutable sha256 digest")
ErrMutableLeaseImage reports a lease.offer without an immutable sha256 image digest.
Functions ¶
func BackgroundRequestData ¶
BackgroundRequestData builds the data payload of a bg.* request. It is the sibling of ExecRequestData and carries its correlation the same way — a caller-minted id, because the side that must MATCH an answer to a question is the side that names the question.
`bg_id` rather than `exec_id` so a message carrying both would be malformed rather than ambiguous: the two pairs travel the same connection and a shared field name would let a stray exec.result satisfy a background wait.
func ExecRequestData ¶
func ExecRequestData(execID string, cmd toolbroker.ShellCommand) map[string]any
ExecRequestData builds the data payload of an exec.request.
CORRELATION IS THIS PAIR'S OWN FIELD and deliberately not contracts.EngineFrame.ReplyTo. That field looks like the tree's correlation mechanism and is not one: it is written in exactly one production place and compared in none (`grep -rn ReplyTo --include='*.go' . | grep -E '==|!=' | wc -l` -> 0, 2026-08-03), so building on it would inherit a mechanism nothing implements. It is also the wrong envelope — ReplyTo lives on engine.v1 frames, and this pair travels on runner.v1.
The id is minted by the caller (the control plane) rather than by the machine, because the side that has to MATCH an answer to a question is the side that must choose the question's name.
func IsRefused ¶
IsRefused classifies a reported verdict. It accepts the bare word as well as the prefixed form so a reader is not the thing that breaks when a refusal is reported without a reason.
func OutcomeClass ¶
OutcomeClass maps a supervised streaming outcome to the lease.complete outcome class the control plane records: a wall-time kill is lost, any other failure is failed, and a clean run is succeeded.
func RefusedVerdict ¶
RefusedVerdict is the verdict a machine reports for a value it will not take. The reason is the operator's only account of WHY, so it names the value's fault rather than the code path that noticed it.
func RelayInbound ¶
func RelayInbound(ctx context.Context, session *LeaseSession, tools *ToolServer, inbound chan<- contracts.EngineFrame, logf func(string, ...any))
RelayInbound reads control-plane->runner messages for the life of the lease and routes each one by type: an engine frame goes to the engine's stdin through inbound, an exec request runs on this machine. It closes inbound when the connection ends, which is how the supervisor learns the controller stopped sending.
It is EXPORTED so that a test outside this package can drive the routing that ships instead of re-spelling it. That is not hypothetical: apps/control-plane/e2e/responses/gateway_parity_test.go carried a hand-written copy of this loop, and the copy had already diverged — it could only relay, so the parity it proved was the parity of a runner that does not exist. A test that reimplements the path it means to measure stays green while the real path changes underneath it.
AN EXEC RUNS IN ITS OWN GOROUTINE SO THE READER KEEPS MOVING. Handling it inline would be simpler and wrong: a command is allowed to take minutes (`xcodebuild` is the case this epic exists for), and for those minutes nothing else could be read — including the interrupt frames the command pump sends mid-run (apps/control-plane/internal/execution/command_pump.go). A stop the operator pressed would arrive after the build it was meant to stop. Concurrent writes are safe here: the websocket documents every method except Reader/Read as safe for concurrent use (github.com/coder/websocket@v1.8.15 conn.go:30), and the reads all happen on this goroutine.
An unknown type still ends the relay, which is the behaviour that shipped before this branch existed: the runner has no way to act on a message it cannot name, and continuing would leave the control plane waiting on a reply the runner will never form.
func RelayInboundServing ¶
func RelayInboundServing(ctx context.Context, session *LeaseSession, servers MachineServers, inbound chan<- contracts.EngineFrame, logf func(string, ...any))
RelayInboundServing is the relay loop itself. The two functions above are its named shapes.
func RelayInboundWithBackground ¶
func RelayInboundWithBackground(ctx context.Context, session *LeaseSession, tools *ToolServer, background *BackgroundServer, inbound chan<- contracts.EngineFrame, logf func(string, ...any))
RelayInboundWithBackground is RelayInbound plus the bg.* triple (A.3 T7). The two are separate entry points rather than one with a nil argument at every call site, because every caller that predates the background triple relays exactly what it always did — a machine with no background server answers a bg.* request with a refusal rather than ending the lease on an unknown type.
func ShipLogs ¶
ShipLogs sends one batch. It presents the SAME certificate and pins the SAME controller identity the settings poll and the renewal do, through renewTLS, because a second spelling of a TLS configuration is a second thing that can be weakened on its own.
A REFUSAL IS NOT AN OUTAGE. Logs are diagnostics: a machine whose shipment is rejected must keep doing its work, so every failure here is returned for the caller to drop rather than acted on. What it must never do is block the agent, which is why the caller holds the buffer and this call holds nothing.
func StartWorkspaceSync ¶
func StartWorkspaceSync(ctx context.Context, cfg SyncConfig) (stop func(), err error)
StartWorkspaceSync materialises the workspace and then keeps it published until the context ends.
THE ORDER IS NOT NEGOTIABLE AND IT IS THE WHOLE LIFECYCLE. Materialise first, because a watcher started before the tree is written would report every file the materialisation itself created and republish the tree it just fetched. Then watch, because from that moment the machine's disk is the authority and the store has to follow it.
It returns as soon as the tree is on disk, so the caller can start the engine — the publishing half runs in the background for the life of the lease. A caller that waited for the whole thing would be waiting for a loop that never ends.
func WorkspaceRequestData ¶
WorkspaceRequestData builds the data payload of a ws.request. Correlation is this pair's own field, `ws_id`, for the reason exec_id and bg_id are separate from each other: the three pairs travel the same connection, and a shared field name would let a stray answer of one kind settle a wait of another.
Types ¶
type BackgroundServer ¶
type BackgroundServer struct {
// contains filtered or unexported fields
}
BackgroundServer answers the bg.* triple on this machine's own background runner. It is the background half of what ToolServer is for a synchronous command: the executor here is the one THIS machine was built with, so a task started through it is a process on this box and the handle names this box's kernel.
func NewBackgroundServer ¶
func NewBackgroundServer(r toolbroker.BackgroundRunner) *BackgroundServer
NewBackgroundServer binds the detached executor. A nil one is a machine that cannot start a task that outlives an attempt; it still answers every request (see Handle).
func (*BackgroundServer) Handle ¶
func (s *BackgroundServer) Handle(ctx context.Context, request contracts.RunnerMessage) contracts.RunnerMessage
Handle answers one bg.* request. LIKE ToolServer.Handle IT NEVER RETURNS AN ERROR, and for the same reason: a control plane that asked is blocked on the answer, so silence is not a degraded mode, it is a sweep that never settles a row and a run that waits forever.
A REFUSAL IS DISTINGUISHABLE FROM AN OUTCOME. data.error means the machine did not do the thing; data.status / data.handle mean it did. Collapsing them would let "this machine runs no background task" read as "the task is gone", which is the wrong answer this whole seam exists to stop.
type BootstrapConfig ¶
type BootstrapConfig struct {
RunnerID string
RunnerDNS string
EnrollmentToken string
EnrollmentURL string
// ControllerCAs is the ADDITIONAL trust anchor for a private deployment, and NIL IS NOW A NORMAL
// VALUE rather than a refusal: a publicly trusted runner gateway is verified by the host's own root
// store, which is DoD item 2 and the reason `PALAI_CONTROLLER_CA` stopped being a required input.
//
// Nil means "the system roots decide". A non-nil pool means "these roots and only these", which is
// what every deployment built before this sent and what a self-signed self-host still needs.
ControllerCAs *x509.CertPool
ControllerDNS string
Now func() time.Time
// Posture is what this machine SAYS it is: "sandboxed-linux" (a container runner) or
// "sandboxed-host" (a rented Mac). The control plane compares it with the pool's and refuses the
// enrolment on a disagreement (E24 T2) — it does NOT verify it, and cannot.
//
// Empty declares nothing, which is what every runner built before E24 sends and is why this field
// is omitempty below: an undeclared machine's request body is byte-identical to the one this
// package has always sent, so no deployment observes the field's existence.
Posture string
// PoolID is the pool this machine was CONFIGURED to join (E24 T3). It is a declaration, not an
// authorisation: the pool a certificate is actually minted into comes from the presented KEY, and a
// declaration that disagrees with the key REFUSES the enrolment instead of being silently widened
// or silently overridden.
//
// Why declare it at all, when the key already decides: because the realistic operator mistake is
// pasting the wrong pool's key onto a machine, and posture comparison (T2) cannot catch it when
// both pools have the same posture. Saying where the machine believes it belongs is what turns that
// into a refusal at the door instead of a machine quietly running another pool's work.
//
// Empty declares nothing and inherits the key's pool, which is what every runner built before E24
// sends — hence omitempty, so an unconfigured machine's request body is unchanged to the byte.
PoolID string
// Capacity is how many sessions this machine can hold AT ONCE (Faz A.4 T5). It is a declaration in the
// same sense Posture is — the control plane records it and enforces it, and cannot verify it.
//
// ZERO DECLARES NOTHING AND NO CEILING IS ENFORCED, which is the shipped posture and not a fallback.
// Before this field the column existed, refused zero, and was filled by a clamp in the control plane:
// every machine in every deployment carried a 1 that no operator had chosen. Enforcing that would have
// capped every Mac at one session on the strength of an artefact. So the ceiling exists only where
// somebody typed a number, and an unconfigured machine's request body is unchanged to the byte.
Capacity int
// DeviceKey is the machine's DURABLE keypair (packages/device). When it is set, Enroll signs a CSR
// with it and generates nothing — which is the whole of "a restart is the same machine": the
// registry keys a row on this key's fingerprint, so the id follows the key and the key follows the
// disk (plan §3.4).
//
// NIL KEEPS THE OLD BEHAVIOUR — a fresh keypair per call, no CSR, the raw public key on the wire —
// because every control plane and every test built before this sends exactly that, and a package
// that could only enrol the new way would be a package no existing deployment can use.
DeviceKey crypto.Signer
// RecoverRunnerID is the id this machine ALREADY HOLDS, read from its own disk, presented so the
// control plane reissues that identity instead of minting a second one. Empty asks for a new
// identity, which is what a machine enrolling for the first time is doing.
//
// IT AUTHORISES NOTHING. The fingerprint of DeviceKey is what proves who this machine is; this field
// is a claim, and a claim that disagrees with the fingerprint is REFUSED rather than honoured — that
// is what stops a device holding a stolen identity file from becoming the machine it names.
RecoverRunnerID string
// The MEASURED facts (plan §3.3, DoD 9): what this binary was compiled for, what version it is, and
// which session-isolation modes this machine can actually provide. The gateway checks them against
// the key's pool BEFORE issuing an identity, so a machine that cannot execute never becomes ready
// capacity. All are omitempty on the wire: a runner that measures nothing sends the bytes it has
// always sent.
OS string
Arch string
Version string
IsolationModes []string
}
BootstrapConfig is the enrollment input. EnrollmentToken is presented and never stored — not one-use, but not retained either; ControllerCAs and ControllerDNS are the trust anchor and exact server identity the runner requires for every outbound connection.
type EngineRequest ¶
type EngineRequest struct {
ImageDigest string
RunID contracts.RunID
AttemptID contracts.AttemptID
Fence uint64
Env map[string]string
Limits Limits
// WorkspaceHostPath, when set, is the host allocation directory bind-mounted to /workspace in
// the engine sandbox (spec §29.9). Empty means no workspace — the pre-E09 behaviour. ReadOnly
// binds a child's read-only snapshot; a root writer binds read-write (spec §29.8, enforced in
// E09 Task 6). The engine never learns the host path (§29.9 — exact host paths are hidden).
WorkspaceHostPath string
WorkspaceReadOnly bool
}
EngineRequest is one attempt to supervise: the pinned image, the run/attempt identity, the lease fencing token, an allowlisted environment, and the execution bounds. Fence is carried into the supervisor.hello as a hash so the engine can bind the handshake to the lease that authorized it (§25.6).
type EngineResult ¶
type EngineResult struct {
ContainerID string
ImageID string
ExitCode int64
Frames []contracts.EngineFrame
StdoutBytes int64
Stderr []byte
StderrBytes int64
StderrTruncated bool
}
EngineResult is the classified outcome of a supervised attempt. Frames are the parsed, in-protocol stdout; they are populated only on a clean success.
type FrameLedger ¶
type FrameLedger struct {
// contains filtered or unexported fields
}
FrameLedger deduplicates engine frames by id under the stable request-id discipline: a repeated id with the same payload hash is an idempotent retransmit, while a repeated id with a different payload is a protocol violation.
func (*FrameLedger) Admit ¶
func (l *FrameLedger) Admit(frame contracts.EngineFrame) (bool, error)
Admit records frame and reports whether it duplicates an already-seen frame. It returns ErrFrameHashConflict when the id was seen with a different payload.
type FrameSink ¶
type FrameSink func(ctx context.Context, frame contracts.EngineFrame) error
FrameSink receives each validated, de-duplicated engine frame in stream order, starting with engine.ready. The runner gateway (Task 11c) relays it to the controller.
type Identity ¶
type Identity struct {
RunnerID string
Certificate tls.Certificate
NotAfter time.Time
// Settings is the configuration the control plane sent with this identity, nil when it sent none. It is
// carried on the identity rather than returned beside it because the two answer one question a machine
// asks once — who am I, and what am I — and splitting them lets a caller take the certificate and forget
// the configuration.
Settings map[string]string
}
Identity is the runner's short-lived enrolled identity: the run-scoped client certificate and the private key that never leaves the runner. It deliberately carries no enrollment token — the bootstrap credential is presented during Enroll and discarded, never retained. It is not one-use (the control plane admits one redemption per issued-certificate lifetime), which makes NOT retaining it the stronger property: the runner re-reads it from disk when it needs it.
func Enroll ¶
func Enroll(ctx context.Context, config BootstrapConfig) (Identity, error)
Enroll exchanges the bootstrap credential for a short-lived client identity over an outbound, server-authenticated TLS connection. The runner generates its own keypair locally; only the public key is sent, and the returned certificate plus that local key form the identity. The token is presented and discarded — it is not one-use, so a machine whose certificate expired may re-present it, but nothing here retains it.
func Renew ¶
Renew rolls the runner's client certificate forward over its existing mutually authenticated identity. It presents the current certificate to the renew endpoint (an expired certificate cannot complete the mTLS handshake, so renewal must happen before expiry — the serve loop drives it at ~80% of the TTL), keeps the same private key, and returns the identity with the freshly issued certificate. No enrollment token is involved.
type Lease ¶
type Lease struct {
LeaseID string
RunID contracts.RunID
AttemptID contracts.AttemptID
Fence uint64
ImageDigest string
Limits Limits
// WorkspaceHostPath is the host allocation directory the supervisor bind-mounts to /workspace
// (spec §29.9, FLAG A); empty for a workspace-less lease. WorkspaceReadOnly binds it read-only.
// WorkspaceUnsafe marks a §30.13 unsafe-local-bind that is exempt from the allocation-root check.
WorkspaceHostPath string
WorkspaceReadOnly bool
WorkspaceUnsafe bool
}
Lease is the internal projection of a lease.offer: the fenced run/attempt identity, the pinned engine image, and the execution bounds the supervisor enforces.
func ParseLeaseOffer ¶
func ParseLeaseOffer(message contracts.RunnerMessage) (Lease, error)
ParseLeaseOffer validates a runner.v1 lease.offer and projects it to a Lease. It rejects a mismatched protocol major, a missing fence, or a mutable image reference, so nothing downstream ever runs on an unverified offer.
type LeaseSession ¶
type LeaseSession struct {
// contains filtered or unexported fields
}
LeaseSession is a lease held over a live connection. After OpenLease the runner relays each supervised engine frame to the controller with SendEngineFrame, routes the inbound messages with RelayInbound — engine frames to the supervisor, exec requests to this machine's executor — and reports the terminal outcome and redacted stderr digest with Complete. It stays outbound-only: the runner still opens no inbound port, it keeps the one connection it dialed.
func (*LeaseSession) Close ¶
func (l *LeaseSession) Close() error
Close tears the lease connection down without reporting an outcome — an aborted lease.
func (*LeaseSession) Complete ¶
func (l *LeaseSession) Complete(ctx context.Context, outcome, reason, stderrDigest string) error
Complete reports the terminal outcome class, the machine's own reason for it, and the redacted stderr digest in a lease.complete message, then closes the connection.
THE REASON EXISTS BECAUSE THE OUTCOME CLASS IS NOT ONE, and the cost was measured on a live stack on 2026-08-04. A run failed with the control plane telling its operator: "the run's repository workspace could not be prepared: ask the machine to clone the workspace: runner reported lease outcome \"failed\". Check the binding's clone_url and default branch, and whether a private repository needs a connection_ref." Nothing was wrong with the binding. This machine's own log, at the same instant, said `inspect engine image: Error response from daemon: No such image: sha256:8be1ff…` — the pinned engine image had been rebuilt out from under the control plane's PALAI_ENGINE_IMAGE. The sentence that would have ended the investigation existed, on this side of the wire, and the wire carried three words of it.
WHAT MAY TRAVEL, stated because the tree's rule is real: this is the RUNNER's own sentence about its own infrastructure — a Docker daemon answer, a bound it enforced, a handshake it timed out — the same class as the git stderr failProvisioning already puts in a problem document. It is NOT engine output and NOT provider text: the engine's own bytes leave here as `stderr_digest`, hashed, exactly as before. It is bounded at maxLeaseReason and flattened to one line HERE rather than at a reader, so no reader can be the one that forgets.
func (*LeaseSession) Lease ¶
func (l *LeaseSession) Lease() Lease
Lease returns the lease this session holds.
func (*LeaseSession) ReceiveMessage ¶
func (l *LeaseSession) ReceiveMessage(ctx context.Context) (contracts.RunnerMessage, error)
ReceiveMessage reads one controller->runner message off the lease connection and returns it WITHOUT deciding what it is. The type branch belongs to the caller (RelayInbound), because the runner now does two different things with an inbound message: a controller.frame is relayed into the engine's stdin, an exec.request runs on this machine. A reader that decoded straight to an engine frame could only express the first.
func (*LeaseSession) SendEngineFrame ¶
func (l *LeaseSession) SendEngineFrame(ctx context.Context, frame contracts.EngineFrame) error
SendEngineFrame relays one runner->controller engine frame inside an engine.frame message, carrying the single frame in its data.
func (*LeaseSession) SendExecResult ¶
func (l *LeaseSession) SendExecResult(ctx context.Context, message contracts.RunnerMessage) error
SendExecResult writes a tool server's answer back to the control plane, wrapping it in the runner.v1 envelope with this lease's identity. The write is bounded so a control plane that stopped reading cannot leak one blocked goroutine per command this machine ran.
type LeaseWorkspaceClient ¶
type LeaseWorkspaceClient struct {
// contains filtered or unexported fields
}
LeaseWorkspaceClient is the machine's side of the workspace protocol, spoken over the lease.
func NewLeaseWorkspaceClient ¶
func NewLeaseWorkspaceClient(session *LeaseSession, logf func(string, ...any)) *LeaseWorkspaceClient
NewLeaseWorkspaceClient builds a client that talks over the connection this lease already holds.
func (*LeaseWorkspaceClient) Deliver ¶
func (c *LeaseWorkspaceClient) Deliver(message contracts.RunnerMessage)
Deliver routes one control-plane answer to the request waiting for it. RelayInboundServing calls it. An answer with no waiter is logged and dropped rather than blocking the reader: the only ways to have one are a duplicate answer or an answer that arrived after its request timed out, and a reader blocked here is a reader that cannot see the interrupt frame the command pump sends next.
func (*LeaseWorkspaceClient) Missing ¶
Missing asks which digests the store lacks.
The control plane answers a grant request with URLs for the digests it does NOT already hold, so the set it grants IS the set that is missing. Asking twice — once for presence, once for permission — would double the round trips for an answer that is the same question, so the grants are kept and spent by Put.
func (*LeaseWorkspaceClient) Publish ¶
Publish hands the manifest to the control plane, which records it and moves the head under the fence.
THE MACHINE DOES NOT MOVE THE HEAD ITSELF, and it must not: the fence lives in the database and a machine cannot evaluate it. What the machine knows is what its tree contains; what the plane knows is whether this machine is still the one entitled to say so.
func (*LeaseWorkspaceClient) Put ¶
Put uploads one blob through the presigned URL its grant carried.
THE BYTES DO NOT TOUCH THE CONTROL PLANE, which is the point of doing it this way: the plane holds the object-store credential and never hands it over, and what crosses is a signature over one key. Where the plane is in one cloud and the machines in another, this is the difference between every workspace byte crossing a WAN twice and crossing it once.
type Limits ¶
type Limits struct {
WallTimeMS int64 `json:"wall_time_ms"`
MaxStdoutBytes int64 `json:"max_stdout_bytes"`
MaxStderrBytes int64 `json:"max_stderr_bytes"`
MaxFrameBytes int64 `json:"max_frame_bytes"`
// MaxWorkspaceBytes bounds ONE ws.* message, and it is separate from MaxFrameBytes because the two
// carry different things down one connection: a frame is a line of a conversation, a workspace
// message is a REPOSITORY.
//
// ‼️ WITHOUT IT NO SESSION COULD EVER RESUME. The lease made the machine read-limit its connection at
// MaxFrameBytes+64K — 1,114,113 bytes — and a restore sends the snapshot archive as one message. The
// archive of a real repository is 10,552,320 bytes, so the machine answered
//
// close frame: status = StatusMessageTooBig, reason = "read limited at 1114113 bytes"
//
// and the control plane, whose next read then failed, reported "the machine's lease connection ended
// before the command answered". Nothing in that sentence is about a size, and the engine — killed
// downstream when the relay ended and its stdin closed — took the blame for four bring-ups.
//
// A FRESH SESSION NEVER HIT IT, which is why the demo looked half-working: a clone is fetched by the
// machine over its own network and nothing large crosses the lease. Only a resume puts an archive on
// this wire.
//
// Zero means "this deployment sent none" — an older control plane — and the reader falls back rather
// than refusing, because a machine that will not accept a lease from a plane one version behind is a
// machine that cannot be upgraded.
MaxWorkspaceBytes int64 `json:"max_workspace_bytes,omitempty"`
MaxMemoryBytes int64 `json:"max_memory_bytes"`
MaxProcessCount int64 `json:"max_process_count"`
}
Limits are the lease-carried execution bounds, wire-shaped (milliseconds and byte counts) as they arrive from the control plane.
func (Limits) ReadLimit ¶
ReadLimit is the largest single message this lease may carry, which is the workspace bound when the plane sent one and the frame bound otherwise. The 64 KiB is the envelope around either payload.
It is a method rather than an expression repeated at each call site because there are two — the machine's session and the control plane's gateway — and they must agree: a reader that is stricter than its writer closes the connection mid-transfer, which is exactly the defect this exists to fix.
type LogBuffer ¶
type LogBuffer struct {
// contains filtered or unexported fields
}
LogBuffer holds what the agent has written since the last shipment.
IT DROPS THE OLDEST RATHER THAN BLOCKING THE WRITER, and that direction is the whole point: this buffer sits behind the agent's own log output, so a full buffer that blocked would stop the machine doing its work in order to complain about it. A dropped line is a missing diagnostic; a blocked agent is a missing machine.
func NewLogBuffer ¶
type LogLine ¶
type LogLine struct {
At time.Time `json:"at"`
Level string `json:"level,omitempty"`
SessionID string `json:"session_id,omitempty"`
Message string `json:"message"`
}
LogLine is one line this machine wrote, as it crosses the wire.
type LogShipConfig ¶
type LogShipConfig struct {
LogsURL string
ControllerCAs *x509.CertPool
ControllerDNS string
Now func() time.Time
}
LogShipConfig is the transport for a shipment: the same controller trust and exact DNS identity every other runner-plane call pins.
type MachineServers ¶
type MachineServers struct {
Tools *ToolServer
Background *BackgroundServer
Workspace *WorkspaceServer
// Store routes the ANSWERS to requests this machine originated for the workspace store. It is not a
// server: the other three handle questions the control plane asks, this one releases a waiter for a
// question the machine asked. Nil leaves the store's frames unrouted, which is every deployment that
// does not run it — and the arm below then falls through to the default, exactly as before.
Store *LeaseWorkspaceClient
}
MachineServers is everything on this machine a lease may be asked to reach: the executor behind exec.*, the detached executor behind bg.*, and the disk behind ws.*. It is one struct rather than a fourth positional argument because the list grew twice in one epic, and a call site that passes three nils in a row is a call site whose next reader cannot tell which nil means what.
EVERY FIELD MAY BE NIL AND NIL IS NEVER SILENCE. Each server answers its own request type with a refusal when it is unwired (ToolServer.Handle, BackgroundServer.Handle, WorkspaceServer.Handle), so an unconfigured machine tells the control plane so instead of leaving a tool call waiting forever.
type RenewConfig ¶
type RenewConfig struct {
RenewURL string
ControllerCAs *x509.CertPool
ControllerDNS string
Now func() time.Time
}
RenewConfig is the input to a certificate renewal: the renew endpoint and the controller trust anchor and exact DNS identity the runner pins on every outbound connection. The current identity (its client certificate and private key) authenticates the renewal — the bootstrap credential is not on this path at all, which is why revoking it stops new enrolments and stops no machine that already holds an identity.
type ServeConfig ¶
type ServeConfig struct {
Session Session
Supervisor *StreamSupervisor
// Renew rolls the client certificate forward over the runner's existing identity; nil
// disables renewal (a one-shot, single-lifetime runner). Renewal runs on its OWN mTLS
// connection and never touches a parked or in-flight lease connection, so a rollover is
// always lease-safe. It authenticates with the current certificate — the bootstrap token is
// never presented on this path; Reenroll below is the only thing that presents it again.
Renew func(ctx context.Context, current Identity) (Identity, error)
// Reenroll is the RECOVERY path renewal cannot serve: it re-presents the file-mounted
// bootstrap credential to obtain a wholly new identity. It runs when and only when the
// runner holds no usable identity — the current certificate has already expired, so
// renewal-over-mTLS is impossible for the same reason it was needed. nil disables it (the
// pre-recovery behaviour: an expired identity is terminal until the process is restarted).
Reenroll func(ctx context.Context) (Identity, error)
// PersistIdentity is called with every identity this loop obtains after the first — each renewal and
// each recovery — so a device can write the new certificate beside its durable key.
//
// ‼️ IT EXISTS BECAUSE AN UNPERSISTED RENEWAL IS INVISIBLE UNTIL A RESTART, AND THEN EXPENSIVE. The
// runner rolls its certificate forward in memory; without this hook the disk keeps the certificate
// the machine enrolled with, so a restart after that one expires takes the RECOVERY path and spends a
// pool key to be issued something the machine had already been issued. On a machine whose key file
// was removed after installation — a provisioner that deleted it, which is a reasonable thing to do —
// there is no recovery path at all and the machine is simply out of the fleet.
//
// nil is every runner built before this field, and it costs them nothing: they had no disk identity
// to keep in step.
PersistIdentity func(Identity) error
Now func() time.Time
Log func(format string, args ...any)
Backoff time.Duration // between a failed dial/renewal and the next attempt; zero = 1s
// Concurrency is how many leases the runner parks at once on its shared enrolled identity.
// Zero or one is the sequential one-lease-at-a-time default (LP-0 unchanged); >1 lets a
// delegating run's parent hold its engine while an inline child dials its own on the same
// runner (spec §25.18), instead of deadlocking on a single lease slot.
Concurrency int
// WorkspaceRoot is the runner's managed allocation root: a lease's workspace host path must sit
// under it before the runner bind-mounts it, so a control plane cannot make the runner mount an
// arbitrary host path (spec §30.13). A §30.13 unsafe local bind (REP-012) is the only exception,
// and only when AllowUnsafeBind is set. Empty disables the under-root check — the pre-E09
// behaviour for a runner with no configured workspace root.
WorkspaceRoot string
// AllowUnsafeBind lets this runner honour a lease's WorkspaceUnsafe flag (a §30.13 direct host
// bind mount). Default false: a control plane alone cannot make the runner mount an arbitrary host
// path — the runner's OWN operator must opt in (PALAI_WORKSPACE_UNSAFE_BIND=1), preserving the §24
// trust boundary between control plane and runner.
AllowUnsafeBind bool
// Shell runs an exec.request on THIS machine (toolserver.go). It is what makes where a command
// runs a property of the machine that took the lease rather than of the control-plane process, and
// it is the reason this package now depends on the tool-broker seam at all.
//
// nil is a runner that serves engines but runs no commands, which is every runner built before
// this field existed. It is not silent: an exec.request still gets an exec.result carrying a
// refusal, because a control plane blocking a tool call on this machine's answer must be told it
// will not get one.
Shell toolbroker.ShellRunner
// Settings polls the control plane for this machine's current configuration, reporting in the same
// round trip what the machine did with the previous document. nil disables the poll entirely — a
// machine then receives its configuration once, at enrolment, which is the behaviour of every runner
// built before this field existed and the posture of every Docker-free wire proof.
//
// It takes the report and returns the document, so the two can never be wired to different endpoints.
Settings func(ctx context.Context, current Identity, report Settings) (Settings, error)
// Update moves this machine to a named version and reports whether anything changed. Nil = this
// build has no updater wired, and PALAI_AGENT_TARGET_VERSION then reports NOT READ.
//
// IT IS A SEAM AND NOT A DIRECT CALL because it downloads, verifies and replaces binaries — the one
// operation in this package that must be drivable in a test without a network or the right to write
// into /usr/local.
Update func(ctx context.Context, targetVersion string) (bool, error)
// ExitForUpdate ends the process after a successful update, so the service manager starts the new
// binary. Nil is a no-op, which is what a test wants and what a build with no updater has anyway.
ExitForUpdate func()
// SetEngineImageRef moves WHERE this machine fetches an engine it does not hold, and persists it so a
// restart keeps it. Nil = this build has no puller wired, and PALAI_ENGINE_IMAGE_REF reports NOT READ.
//
// ‼️ IT HAS TO BE LIVE, NOT ENROLMENT-ONLY, OR A FLEET DRIFTS THE MOMENT THE ENGINE MOVES. The value
// was read once at enrolment, so every machine already in the pool kept pointing at the engine that
// existed the day it joined: after a rollout the lease pinned a new digest, the machine pulled its old
// reference, and the pull "succeeded" onto bytes that were not the ones pinned. The run then died with
// `inspect engine image after pulling …: No such image`. Measured live 2026-08-13, run run_4b6195e3,
// on the only Mac in the pool — with a hundred, re-enrolling each one is not a repair.
SetEngineImageRef func(ref string) error
// SettingsInterval is how long the runner waits between polls, and it is therefore the WORST-CASE
// LATENCY from an operator pressing save to this machine acting on it. Zero uses
// defaultSettingsInterval.
//
// It is a configuration-freshness knob and NOT a security parameter, which is the reason it is its own
// field rather than derived from the renewal cadence: renewal fires at 80% of certificate lifetime, so
// tying the two would mean an operator who wanted faster configuration had to shorten certificate
// lifetimes to get it.
SettingsInterval time.Duration
}
ServeConfig drives the runner's park -> lease -> supervise loop with certificate renewal.
func (ServeConfig) Serve ¶
func (cfg ServeConfig) Serve(ctx context.Context)
Serve runs the runner's lease loop until ctx is cancelled: it parks for a lease, supervises the leased engine, and repeats, while a background renewer rolls the client certificate forward as it nears expiry. The renewer runs on a separate connection, so a rollover never interrupts a parked or in-flight lease; each fresh dial picks up the renewed identity, so a re-dial after the original certificate would have expired still authenticates — closing the review's "open lease...retrying" 1/s-forever loop on expiry.
type Session ¶
type Session struct {
Identity Identity
URL string
ControllerCAs *x509.CertPool
ControllerDNS string
Now func() time.Time
// Background answers the bg.* triple while this session is PARKED (A.3 T7). Nil is a machine that
// runs no background task; it still answers, with a refusal, because a control plane blocked on a
// probe must learn it will get no result rather than wait forever (BackgroundServer.Handle).
Background *BackgroundServer
// DialHandshakeTimeout bounds the outbound dial + runner.v1 handshake. Zero uses
// dialHandshakeDeadline. It never bounds the lease-offer park or a held lease.
DialHandshakeTimeout time.Duration
// Version is this runner build's version stamp, advertised in the runner.hello so the control-plane
// can enforce the §48.2 support window (OPS-008). Empty leaves the hello version-less (a pre-E15-T2
// runner), which the control-plane treats as unstamped and does not window-check. cmd/runner sets it
// from packages/version.Resolve.
Version string
}
Session is the runner's outbound leasing connection. It dials the control plane with the short-lived enrolled identity over mutually authenticated TLS; it owns no inbound listener. Every field is required.
func (Session) OpenLease ¶
func (s Session) OpenLease(ctx context.Context) (*LeaseSession, error)
OpenLease completes the same handshake as ReceiveLease but keeps the connection open, returning a LeaseSession over which the runner streams engine frames to the controller, receives controller frames, and finally reports the terminal outcome. It is the persistent form ReceiveLease's one-shot close forecloses.
func (Session) ReceiveLease ¶
ReceiveLease opens the outbound session, completes the runner.v1 handshake, and returns the offered lease, closing the connection immediately. It never opens an inbound connection and returns an error (yielding no lease) if the handshake does not complete in ctx. OpenLease is the variant that keeps the connection for the frame relay.
type Settings ¶
type Settings struct {
// Revision MOVES whenever either contributing document does — the pool's or this machine's. It is a
// change detector rather than a citation: a machine compares it with the revision it is running to
// decide whether anything needs applying, and must not read it as "the machine document is at N".
Revision int64
Settings map[string]string
}
Settings is one document as this machine resolved it.
func FetchSettings ¶
func FetchSettings(ctx context.Context, current Identity, report Settings, config SettingsConfig) (Settings, error)
FetchSettings asks the control plane for this machine's current configuration, reporting in the same round trip what it did with the previous one.
ONE ROUND TRIP FOR BOTH DIRECTIONS, deliberately. A separate report endpoint would let the two drift — a machine could be answering polls while its verdicts silently stopped arriving — and the report is only ever interesting alongside the document it is about.
THE MACHINE IS IDENTIFIED BY ITS CERTIFICATE AND NEVER BY A FIELD IN THE BODY. There is no runner id on this wire, which is what makes it impossible for a machine to ask for another machine's configuration: the control plane reads the identity out of the TLS peer certificate's DNS name, exactly as renewal does.
type SettingsConfig ¶
type SettingsConfig struct {
SettingsURL string
ControllerCAs *x509.CertPool
ControllerDNS string
Now func() time.Time
}
SettingsConfig is the input to one settings poll: where to ask, and the trust anchor and exact DNS identity the runner pins on every outbound connection — the same pair renewal and the lease session use, for the same reason.
type StreamSupervisor ¶
type StreamSupervisor struct {
// HandshakeTimeout bounds the wait for engine.ready after the hello is written.
// Zero uses defaultHandshakeTimeout.
HandshakeTimeout time.Duration
// contains filtered or unexported fields
}
StreamSupervisor supervises a live, interactive engine attempt: it writes the §25.6 handshake, injects controller frames into stdin mid-run, reads stdout frames incrementally under the same envelope and bound rules the batch supervisor enforces post-hoc, and classifies the outcome identically. It is the streaming counterpart of Supervisor; both share buildSpec, validateEnvelope, and the FrameLedger.
func NewStreamSupervisor ¶
func NewStreamSupervisor(driver oci.InteractiveDriver) *StreamSupervisor
NewStreamSupervisor returns a streaming supervisor backed by driver.
func (*StreamSupervisor) Stream ¶
func (s *StreamSupervisor) Stream(ctx context.Context, request EngineRequest, inbound <-chan contracts.EngineFrame, sink FrameSink) (EngineResult, error)
Stream supervises one interactive engine attempt to a terminal outcome. It starts a hardened container, writes supervisor.hello, waits for engine.ready inside the startup deadline, forwards every validated stdout frame to sink, injects inbound controller frames into stdin, and applies the batch supervisor's outcome classification. A handshake timeout, a bound violation, a frame-id conflict, a wall-time kill, or a non-zero exit each fails the attempt; a killed engine never yields a false success.
type Supervisor ¶
type Supervisor struct {
// contains filtered or unexported fields
}
Supervisor runs an engine attempt in an OCI sandbox and enforces the engine JSONL protocol on its output. It owns the protocol discipline; the sandbox mechanics live behind the oci.Driver.
func NewSupervisor ¶
func NewSupervisor(driver oci.Driver) *Supervisor
NewSupervisor returns a supervisor backed by driver.
func (*Supervisor) Run ¶
func (s *Supervisor) Run(ctx context.Context, request EngineRequest) (EngineResult, error)
Run supervises one engine attempt: it builds a hardened, allowlisted sandbox spec, runs it, and classifies the outcome. A timeout, oversized stdout, non-zero exit, or malformed frame stream each fails the attempt; only a clean run yields frames.
type SyncConfig ¶
type SyncConfig struct {
// Root is the allocation directory this lease was given.
Root string
// Project scopes the blob namespace.
Project string
// Client is the control-plane side.
Client WorkspaceClient
// Coalesce tunes when events become publishes. Zero takes the defaults.
Coalesce ws.CoalesceConfig
// Poll is how often the coalescer is asked whether a batch is due. It is NOT the debounce — it is
// the resolution at which the debounce is noticed, so it must be well under it.
Poll time.Duration
// Logf is where a sync says what it did. Nil silences it, which is what tests want.
Logf func(string, ...any)
}
SyncConfig is the machine half of the workspace inversion.
type ToolServer ¶
type ToolServer struct {
// contains filtered or unexported fields
}
ToolServer runs an exec.request on the machine's own executor. It is the half of A.3 that makes "this run on the Mac, that one in a container" expressible: the executor here is the one THIS machine was built with, so where a command runs is decided by which machine took the lease.
func NewToolServer ¶
func NewToolServer(exec toolbroker.ShellRunner) *ToolServer
NewToolServer binds the executor this machine runs commands on. A nil executor is a runner that was never wired for tool execution; it still answers every request (see Handle).
func (*ToolServer) Handle ¶
func (s *ToolServer) Handle(ctx context.Context, request contracts.RunnerMessage) contracts.RunnerMessage
Handle runs one exec.request and returns the exec.result to send back. It carries only Type and Data — the lease identity and the runner.v1 envelope are filled by LeaseSession.SendExecResult, so there is one place that knows which lease a message belongs to.
IT NEVER RETURNS AN ERROR, AND THAT IS THE POINT. Every path here — a refusal, a malformed request, an executor that could not start the process — produces a message. A control plane that asked is blocking a tool call on the answer, so silence is not a degraded mode, it is a run that never continues. This tree has already paid for the opposite arrangement once, when every tool Exec error wedged its run forever.
The two answers are distinguishable and neither is fabricated. A command that RAN answers with data.result, including a non-zero exit: a non-zero exit is the shell reporting an outcome, not the executor failing, and the seam's own contract says so (adapters/sandboxes/host/exec.go:107). A command that did NOT run answers with data.error. Collapsing the second into the first — reporting an unwired machine as exit 127, say — would let a misconfiguration read as a command that merely failed, which is the more expensive of the two to diagnose.
type WorkspaceClient ¶
type WorkspaceClient interface {
// Head is the manifest the workspace is currently at, and false when it has never published.
Head(ctx context.Context) (ws.Manifest, bool, error)
// Fetch returns one blob's bytes.
Fetch(ctx context.Context, digest string) ([]byte, error)
// Missing reports which digests the store lacks.
Missing(ctx context.Context, digests []string) ([]string, error)
// Put uploads one blob.
Put(ctx context.Context, digest string, body []byte) error
// Publish records a manifest and moves the head, returning the manifest id.
Publish(ctx context.Context, m ws.Manifest) (string, error)
}
WorkspaceClient is everything the machine needs from the control plane to keep a workspace in step. It is an interface because the transport is not this file's business — today it rides the lease connection, and the presigned-URL path will move the bytes off it without changing a line here.
type WorkspaceServer ¶
type WorkspaceServer struct {
// contains filtered or unexported fields
}
WorkspaceServer answers ws.request on THIS machine's disk. It is the workspace half of what ToolServer is for a command: the filesystem here is the one this machine has, so where a run's bytes live is decided by which machine took the lease.
func NewWorkspaceServer ¶
func NewWorkspaceServer(allocationRoot string) *WorkspaceServer
func (*WorkspaceServer) Handle ¶
func (s *WorkspaceServer) Handle(ctx context.Context, request contracts.RunnerMessage) contracts.RunnerMessage
Handle performs one workspace operation and returns the ws.result to send back.
IT NEVER RETURNS AN ERROR, for the reason ToolServer.Handle does not: a control plane that asked is blocking a tool call on the answer, so silence is not a degraded mode, it is a run that never continues.
A FAILURE CROSSES WITH ITS CAUSE NAMED, NOT JUST ITS TEXT. data.code is workspace.FailureCode's answer and data.error is the message; the control plane rebuilds an error that errors.Is answers the same way (workspace.ErrorForCode). Without the code every refused traversal would arrive as an unclassifiable failure and the file tool would tell the model `failed` where it means `refused` — the control would still work and would stop being legible, which is the more expensive half.
func (*WorkspaceServer) SetStore ¶
func (s *WorkspaceServer) SetStore(store *LeaseWorkspaceClient)
NewWorkspaceServer binds the managed allocation root this machine serves workspaces under. An EMPTY root is a machine that serves none: every request is refused rather than answered against an unbounded filesystem. That is the same reversal workspaceUnderRoot already made for the bind-mount and for the same measured reason — one variable name, two planes, and the plane that guards is the one nobody sets. SetStore gives this server the lease's workspace-store client, which is what a materialise needs and nothing else here does. Nil leaves the operation refusing rather than silently writing an empty tree — a materialise that "succeeded" with no store would hand the engine an empty allocation and look like a repository that had lost its contents.