Skip to main content

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 s9 binary on the device's PATH (s9 --version prints 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.

You point a device at a node, not at a cell

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:

WhereWhat it catches
On the device, before it dialsThe 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 nodeThe 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.
One path still needs a pinned bundle

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

ModeCommandWhat it doesRuns when
Chat (interactive)s9 chatREPL with the on-device agentAn operator is driving the device
Chat (one-shot)s9 chat "…" / s9 do "…"One question / one autonomous mission, then exitScripts, CI, single tasks
Daemons9 runHeartbeats, serves models, watches inboxes, forwards spooled workA permanently-deployed device
Supervised daemons9 fleet-releases service installThe daemon under the host's init systemProduction 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.

Local reasoning needs a signed model bundle

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​

#CheckHow
1The device enrolledIt appears in the fleet with a device identity, not just a reachable host
2Its key never left itThe private key was generated on the device; only a signing request crossed the wire
3It is really connectedPresence is streamed, so a stale row is distinguishable from a live one
4Work it did offline arrivedJournalled entries drained to the cell once the link returned
5Its delegation is boundedThe grant it holds is narrower than its parent's, never equal by accident

When it does not work​

SymptomMost likely causeWhat to do
Enrolment is refusedThe install token is spent or expiredTokens are use-bounded and expire. Issue another; you cannot read an old one back
The device connects then dropsCertificate or schema-version mismatchA peer on a different wire version is rejected with a typed error rather than corrupting data
It shows connected but does nothingIt is waiting on a cell it cannot reachPresence is to the gateway. Check which node it was pointed at
Work is not arrivingThe link never came back, or the spool is holdingJournalled work stays spooled and drains on a good heartbeat. Absent is not lost
A command is refused on-devicePosture or permission modeReading 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.