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
s9on itsPATHand 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
| Step | From | To | Route |
|---|---|---|---|
| 1 | Device | Operator/fleet | Enroll over mTLS (network, once) |
| 2a | Device | Removable media | Presence request written out |
| 2b–2c | Operator workstation | Removable media | Mint a signed presence bundle |
| 2d | Removable media | Device | Apply the bundle offline |
| 3–5 | Device | Device | Mission → 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
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.