Skip to main content

Enroll a device and operate it air-gapped

This tutorial walks a device through a full sovereign lifecycle: enroll it, give it spawn authority via the three-step courier flow, run a governed mission with human-in-the-loop approvals while it is disconnected, and then prove what it did with a signed, verifiable audit trail. Every command is a real s9 command, and every step past enrollment runs with no network.

What you will prove​

By the end, the device will have run a mission, paused a consequential action for a human decision, recorded every reasoning step to a hash-chained journal, and produced a run-record whose signature verifies against the device's enrolled public key, all offline.

Prerequisites​

  • A device with s9 on its PATH and the Quickstart: your first device working.
  • For the courier steps: an operator workstation that holds the fleet-release signing key and the agent key. Signing never happens on the device.
  • A one-time enrollment token, if you are joining a fleet (Step 1). A device that runs fully sovereign can skip enrollment.
  • Removable media to carry bundles between the operator workstation and the (air-gapped) device.

The flow​

Figure: the air-gapped operate flow. Enrollment (solid) is a one-time network act; the presence delegation crosses on removable media (amber, dashed) because the signing key stays on the operator workstation; the mission, the human decision, and the signed audit trail all run on the device with no network.

Table equivalent

StepFromToRoute
1DeviceOperator/fleetEnroll over mTLS (network, once)
2aDeviceRemovable mediaPresence request written out
2b–2cOperator workstationRemovable mediaMint a signed presence bundle
2dRemovable mediaDeviceApply the bundle offline
3–5DeviceDeviceMission → human decision → signed audit trail (offline)

Step 1: Enroll the device (or run sovereign)​

If the device joins a fleet, enroll it once over the network:

s9 enroll --token "$S9_EDGE_ENROLL_TOKEN" --device-name gateway-07
s9 identity --json # confirm the enrolled device id and fingerprint

The device generates its own keypair and receives a short-lived certificate; the private key never leaves the device. Note the device id. You will type it back at decommission time, and the operator needs it to mint the presence bundle.

If the device runs fully sovereign with no fleet, skip enrollment; s9 setup will report the sovereign posture and the rest of this tutorial still applies.

Step 2: Give the device spawn authority (courier flow)​

Subagent tools are never advertised to the model without a presence delegation, the device's subagent spawn authority. A device can never mint its own, so this is a deliberate three-step courier flow that keeps the signing key on the operator workstation.

2a. On the device: request​

s9 presence request --out /media/usb/dev-1.presence-request.json

This generates the device's presence keypair and writes the request for an operator to carry. Move the removable media to the operator workstation.

2b. On the operator workstation: mint​

s9 presence mint --request /media/usb/dev-1.presence-request.json \
--agent-id agent:7f3a --agent-key ./agent.key \
--entity-id entity:acme --device-entity-id entity:dev-1 \
--cell-id cell:alpha --key ./fleet-release.key \
--key-id fleet-release-2026Q3 --ttl-days 30 \
--clearance CONFIDENTIAL \
--out /media/usb/dev-1.pdbundle

mint binds the delegation to the agent, entity, device entity, and cell, and can attach a classification --clearance from the ladder (PUBLIC, INTERNAL, CONFIDENTIAL, SECRET, TOP_SECRET). For a fleet, add --dispatch-scope with a *-terminated scope so elastic scale-out is reachable without re-couriering every device, and --mldsa-key to add a hybrid post-quantum release signature. Carry the media back to the device.

2c. On the device: apply (offline)​

s9 presence apply --bundle-path /media/usb/dev-1.pdbundle --dry-run   # verify first
s9 presence apply --bundle-path /media/usb/dev-1.pdbundle
s9 presence status # confirm

apply verifies the signed bundle against the trust roots the device already holds, no network. --dry-run verifies without writing. From here the model may be offered subagent tools within the delegation's scope.

Step 3: Set the air-gapped telemetry posture​

Tell the device to transmit nothing off-box. Under the off posture, s9 run has no heartbeats to send and bounds its local ticks instead.

s9 telemetry set off --yes
s9 telemetry status

Step 4: Run a governed mission with human-in-the-loop​

Run an autonomous mission, but pause consequential tool calls for a human instead of running them. Because s9 do is headless, --hitl writes each paused call to a hash-chained, device-signed approval queue that survives a restart.

s9 do "triage the failing sensor on this gateway and draft a report" --hitl
Reasoning needs a model bundle imported

s9 do reasons with the on-device model. The shipped build links the inference engine by default, but it ships no model, so import a signed bundle with s9 model-serving import before expecting genuine reasoning (see Quickstart: your first device). A build made with --no-default-features links no engine at all and falls back to a telemetry-only stub. The governance flow below, the approval queue and the audit trail, is real regardless of the runtime.

Step 5: Resolve the pending approvals​

The mission's consequential calls are now waiting. List and resolve them; the decisions are durable.

s9 hitl list                 # tool calls paused awaiting a human decision
s9 hitl approve 01J8... # approve a paused call (or: s9 hitl deny <ID>)
s9 hitl history # replay the full pause/resolve history

An approved call may proceed when the mission resumes; a denied call is refused.

Step 6. Drain the journal: export and verify the audit trail​

Missions record a durable reasoning trail by default. Export it as a signed run-record and verify it, the proof of what the device did.

s9 audit export --sign --out /media/usb/run-record.json
s9 audit verify /media/usb/run-record.json --device-key ./device.pub.pem

--sign signs the bundle head with the device key. On verify, passing --device-key checks the signature against the device's enrolled public half, turning "self-attested" into "attributed". Add --format otel on export to emit OpenTelemetry-span-shaped JSON that carries the same signature and chain, so an observability stack can ingest the trace and still verify it.

Step 7 (optional): Unwind, with proof​

If the mission mutated device state you want to roll back, the rewind is itself verifiable:

s9 rewind status            # what is checkpointed, and what a rewind would cost
s9 rewind plan --to 42 # dry-run the unwind (writes nothing)
s9 rewind execute --to 42 --yes
s9 rewind prove # verify the chain and prove every unwind on it

What you proved​

  • A device came online with an identity whose private key never left it.
  • Its spawn authority was granted by a courier flow that keeps the signing key off the device, verified offline against trust roots the device already held.
  • A governed mission ran while disconnected, and a consequential action paused for a human rather than running unsupervised.
  • The device produced a hash-chained, device-signed audit trail whose signature verifies against its enrolled public key, evidence that holds up with no network and no trust in the device's own word.