Connect an Edge Agent
The Edge Agent is one small,
statically-linked, signed binary (s9) that runs the platform harness on a
host a full cell cannot always reach: a workstation, a gateway, an IoT device, a
tactical kit. This guide connects one to a cell, shows the two ways to run it,
and moves work between the device and a cell, online or by offline courier.
For the concept, see Core and edge; for every command in isolation, see the Edge command reference.
Prerequisites
- The
s9binary on the device'sPATH(s9 --versionprints its version). - To join a fleet: a one-time enrollment token from a backend admin and network reachability to a cell's edge listeners. A device can also run fully sovereign with no cell at all, skip enrollment for that.
- For local reasoning: a signed model bundle to import (see Model Serving & Provisioning).
Step 1: Enroll the device (or run sovereign)
Enrolling gives the device a delegated identity and a place in a fleet while its private key never leaves the device:
s9 setup # read-only readiness checklist
s9 enroll --token "$S9_EDGE_ENROLL_TOKEN" --device-name gateway-07
s9 identity --json # confirm the enrolled device id
The device generates its own keypair, sends a certificate request to the
node's enrollment door (TLS, no client cert, because the device has none
yet), and receives a short-lived certificate together with a trust bundle. A
second enrollment over an already-enrolled state directory is refused unless
you pass --reenroll. To run sovereign instead, skip this step; s9 setup
reports the sovereign posture and everything below still applies offline.
The node service is the only device path. A device dials the node's own fully-qualified name, and the node forwards the call to the right cell over the internal fabric. The cell's own listeners refuse device calls that did not come through it.
What comes back from enrollment matters as much as the certificate:
- The endpoints the device will use next, published as node addresses, gRPC first and HTTPS second. If the node has no name on record the list is empty rather than naming a cell, so a misconfigured node fails visibly.
- A narrow trust bundle: the organization's device and server certificate authorities, not its root.
The device prefers a flag, then the list it was given at enrollment, then its own configuration. With no configuration at all it defaults to loopback, which is the right answer for a single-box deployment and an obvious failure anywhere else.
The first dial, and how a device trusts a node it has never met
A device enrolling for the first time has no trust bundle yet, so something has to tell it which node is the real one. The enrollment token does.
The token carries the fingerprints of the organization's active server certificate authorities alongside its secret, which means an air-gapped or sovereign deployment needs nothing but the token: no bundle copied onto the device, and no reliance on public web roots that an isolated site does not have.
It is integrity-checked from both ends, which is what makes carrying the pins in the token safe rather than convenient:
| Where | What it catches |
|---|---|
| On the device, before it dials | The device recomputes an authentication tag from the secret it holds. Pins edited by anyone without the secret fail here, and the device never offers its token to whatever the edited pin would have trusted |
| On the node | The stored token hash covers the whole token, pins included. Pins edited by someone who does hold the secret no longer match a minted token, and enrollment is refused |
The device accepts the node only if the chain it presents verifies up to a pinned key, with the signature, validity, server-authentication usage and the node's name all checked. After enrollment it switches to the returned bundle as before.
Two operational details:
- A certificate-authority rotation does not invalidate tokens. Both the current and the next authority are pinned while a rotation is in flight, and a token minted mid-rotation enrolls against either.
- Trust order is configuration, then token pins, then public roots. An operator-supplied bundle still wins if you set one. Public roots are reached only when neither exists, which is the cloud case where the node certificate is publicly trusted anyway.
The fleet installer-script token is a different credential and does not carry pins. A device bootstrapped that way still needs an operator-supplied authority for its first dial, so an air-gapped rollout using the installer script should plan for that step. Enrolling with a normal enrollment token does not.
Prove reachability once enrolled:
s9 heartbeat # one mutually-authenticated round trip to the cell
s9 status # enrolled device + known endpoints
s9 check-revocation # confirm the delegation is not revoked
Step 2. Pick a mode: chat or daemon
One binary, two operating modes.
Figure: the two modes. Chat/one-shot commands (outlined) do one thing and exit;
the resident daemon (outlined) runs the steady-state loop and is supervised by an
OS service (green). Both reach a cell (filled) when a link exists; the daemon also
drains spooled work home when a link returns. Ceiling: local reasoning needs an
imported signed model, without one, s9 chat falls back to a telemetry-only stub
on a minimal build.
Table equivalent
| Mode | Command | What it does | Runs when |
|---|---|---|---|
| Chat (interactive) | s9 chat | REPL with the on-device agent | An operator is driving the device |
| Chat (one-shot) | s9 chat "…" / s9 do "…" | One question / one autonomous mission, then exit | Scripts, CI, single tasks |
| Daemon | s9 run | Heartbeats, serves models, watches inboxes, forwards spooled work | A permanently-deployed device |
| Supervised daemon | s9 fleet-releases service install | The daemon under the host's init system | Production devices |
Chat mode
s9 chat "which models are loaded on this device?" # one-shot
s9 chat --relay --workspace ws-acme --audit --session incident-42 # interactive, grounded, audited
--relay relays reasoning to the fabric when connected and degrades to local when
it is not (requires enrollment). --workspace / --cleanroom scope a session so
each turn is grounded there and audit rows are attributable to it. --audit
records every step to a durable, hash-chained journal.
The default build ships the inference engine compiled in but no model. Import
one before s9 chat can reason locally:
s9 model-serving import /path/to/model-bundle/ # verify + register
s9 model-serving list-models --json
A minimal build (--no-default-features) links no engine and returns a
telemetry-only stub. See Model Serving & Provisioning.
Daemon mode
For a smoke test, run a bounded loop; for production, install the OS service that supervises it:
s9 run --max-heartbeats 5 # bounded loop, ideal for CI/smoke test
s9 fleet-releases service render # preview the unit/plist/SCM entry first
s9 fleet-releases service install
s9 fleet-releases service status
Step 3: Move work between the device and a cell
An Edge Agent is one device, so it does not run cross-cell placement, it holds leased work, survives a blackout, and seals results a cell can verify. There are two directions.
Consume leased work from a cell
Work arrives as a signed job envelope sealed by a pinned issuer. The device leases it, runs it on-device, and seals a result the cell verifies with the device key. The pinned issuer key is mandatory, a request sealed by any other key is refused:
s9 scheduled-jobs consume --issuer-key ./cell-alpha.pub # one pass, online
s9 scheduled-jobs consume --issuer-key ./cell-alpha.pub --offline --max-passes 20 # air-gapped
s9 scheduled-jobs status # inbox / outbound / outbox depths
A device that cannot serve a job (no local model, or a job that needs a peer while air-gapped) fails the job rather than fabricating a result.
Discover a source and admit catalog objects
To bring what a device sees back to a cell, scan sources at the edge and admit catalog objects, offline-verifiable both ways:
s9 discovery-mapping scan ... # scan a source at the edge (see the reference)
s9 catalog apply-object <bundle> # admit a signed catalog object, verified offline
See Discovery, catalog & field manuals and the tutorial Discover a data estate offline for the full flow.
Step 4: Run many instances on one host (profiles)
A profile gives an instance its own isolated state root, so several Edge Agents each bound to a different workspace or clean room, coexist on one host without sharing identity, certificates, or model sockets:
s9 --profile edge-b enroll --token "$TOKEN_B" --device-name gateway-07-b
s9 profile list
--profile <name> forces state to ~/.s9/profiles/<name>; an explicit
--config wins over it.
Verify it worked
| # | Check | How |
|---|---|---|
| 1 | The device enrolled | It appears in the fleet with a device identity, not just a reachable host |
| 2 | Its key never left it | The private key was generated on the device; only a signing request crossed the wire |
| 3 | It is really connected | Presence is streamed, so a stale row is distinguishable from a live one |
| 4 | Work it did offline arrived | Journalled entries drained to the cell once the link returned |
| 5 | Its delegation is bounded | The grant it holds is narrower than its parent's, never equal by accident |
When it does not work
| Symptom | Most likely cause | What to do |
|---|---|---|
| Enrolment is refused | The install token is spent or expired | Tokens are use-bounded and expire. Issue another; you cannot read an old one back |
| The device connects then drops | Certificate or schema-version mismatch | A peer on a different wire version is rejected with a typed error rather than corrupting data |
| It shows connected but does nothing | It is waiting on a cell it cannot reach | Presence is to the gateway. Check which node it was pointed at |
| Work is not arriving | The link never came back, or the spool is holding | Journalled work stays spooled and drains on a good heartbeat. Absent is not lost |
| A command is refused on-device | Posture or permission mode | Reading classified data can drop the network-class tools. That is the taint rule working |
Across the deployment matrix
- Single-cell, the device enrolls against and relays to the one cell's edge listeners.
- Multi-cell, the device recognizes node identity, not cell identity, so
a peer cell can serve it; a
*-terminated dispatch scope on the presence delegation makes elastic scale-out reachable without re-couriering every device. - Sovereign / air-gapped, enroll (once, if at all) then operate with no network: reasoning, journaling, and the job spool all run locally, and signed bundles cross on removable media. See Operate a device air-gapped.
- Cloud-enabled, the same enroll/relay path reaches a cloud-hosted cell.
Related
- Quickstart: your first device, the fastest path to a running Edge Agent.
- Operate a device air-gapped, the full sovereign lifecycle with couriered spawn authority and a signed audit trail.
- The on-device agent:
chat,do,hitl,inbox,mcp,a2a,presence,scheduled-jobs. - Daemon & fleet lifecycle:
run,heartbeat,fleet-releases. - Edge operator runbook, the recurring day-to-day discipline.