ADR-0003: vibeD executor contract
Status
Draft. Contract only. No code ships against this ADR in M0; the vibeD
Executor implementation is targeted for M2. This ADR exists now so the M0
proto (Run.spec.executorHint) doesn't need a breaking change later.
Context
hiveD stays independent of vibeD through the Executor interface (see
docs/PROJECT.md §7), but vibeD is the default, best-supported Executor,
the one that provisions a Cell as a Kata + Firecracker microVM (general
lane) or a fast-start sandbox (fast lane). Pinning down what the vibeD
Executor actually hands to a Cell, and what it expects back, needs to
happen before M2's implementation so the Drone (M1) and the vibeD
Executor (M2) are built against the same assumptions from the start.
Decision
- Artifact layout delivered into the Cell:
/drone: thehived-dronebinary./app: the agent bundle, containing rendered instructions, tool manifests, and (forruntime.loop: custom) the user's entrypoint code/package./run.json: the frozen Run spec plus the resolved AgentVersion snapshot plus a reference to the identity token (never the raw signing material; see below).
- Lane hint, not a lane guarantee.
Run.spec.executorHint(and the AgentVersion'sruntime.lanehint) map to vibeD's classifier hint (fast→ workerd/static lane,general→ Kata + Firecracker), but the Keeper may override the hint based on policy, and the Executor may reject a hint it cannot satisfy. Callers must not assume the hint is binding. - Env / secret injection. Secrets are injected by reference
(a
Credentialname the Executor resolves at Cell start against its own secret store), never written into/run.jsonin the clear. Environment variable naming:HIVED_RUN_ID,HIVED_KEEPER_ADDR,HIVED_IDENTITY_TOKEN_REF,HIVED_MODEL_GATEWAY_ADDR,HIVED_TOOL_BROKER_ADDR,HIVED_MIND_ADDR. The Drone reads these to bootstrap; user code never sees them directly (it talks to the Drone's local IPC endpoint instead, perdocs/PROJECT.md§8). - Log and status retrieval. The Executor exposes
Logs(streaming) andStatus(poll) per theExecutorinterface in §7. The Keeper pollsStatusfor terminal-state detection; it does not require the Executor to push status changes in M2's first cut, though push (e.g. webhook) is not precluded later. - Egress policy. The Cell is deny-by-default. The allowlist the
Executor enforces is derived from the effective Colony + AgentVersion
spec.toolspolicy, resolved by the Keeper and passed toProvision. Enforcement is entirely the Executor's/vibeD's responsibility, never the Drone's (restates the Keeper/Drone split from ADR-0001; the Drone has no egress-enforcement code path, by construction). - Timeout semantics. Three distinct timers: Run-level wall clock
(
AgentVersion.spec.limits), per-step timeout (Drone-local), and Cell idle timeout (Executor-local, protects against a wedged Drone). Any of the three firing kills the Cell and marksRun.status.phase = FailedorTimedOutwith a message identifying which timer fired. - Warm pool expectations. The Executor may reuse a warm Cell shell
(network setup, base filesystem) across Runs for latency, but the
identity token,
/run.json, and/appmust always be freshly injected per Run. Nothing Run-specific may be warm-pooled.
Consequences
Run.spec.executor_hintmust exist as a proto field in M0 even though no Executor consumes it yet, so M2 doesn't require aRunschema break.- Because egress enforcement lives entirely in the Executor, a
local-dockerorprocessExecutor (M1) that doesn't implement egress control is explicitly not a security boundary. This must be documented wherever those Executors are used for anything beyond local dev. - The env-var contract above becomes the first thing M1's Drone
bootstrap code and M2's vibeD Executor
Provisionimplementation both have to agree on; changing it later is a coordinated cross-repo change.
Alternatives considered
- Push-based status (Executor calls back into the Keeper on state
change) instead of poll. Deferred, not rejected: push is lower
latency but adds a callback endpoint the Keeper must expose and secure
before any Executor is even implemented. Polling is simpler to get
right first; push can be added as an optimization without changing the
Executorinterface'sStatussemantics. - Bundling secrets directly into
/run.json. Rejected: arun.jsonis exactly the kind of artifact likely to end up in logs, warm-pool caches, or debugging output; by-reference injection keeps raw secret material out of anything that isn't the Executor's own secret store.