Pairing
How a paired device joins the Fabric and what it tells the cloud about itself. Two flows are supported: the historical token-paste pairing (v1) and the OAuth Device Authorization Grant flow per RFC 8628 — shipped 2026-05-06 in commit e1ef78d (feat(fabric/auth): RFC 8628 OAuth device-grant pairing flow — Phase 13a-1; merged as 7d4f203). Phase 13a-1 is the recommended path for new pairings; token-paste stays in place for back-compat with already-paired devices.
What "paired" actually means
A paired device is a long-lived devices row plus a vault_recipients row of kind=device. The cloud holds:
- the device's
agepublic key (for vault re-wrap) - a 30-day device JWT issued at pair-complete (for WebSocket auth)
- a heartbeat-published capability digest (for routing)
The device holds:
- its
ageprivate key at~/.config/optimalos/keys/device.key(mode 0600; TPM/Secure-Enclave sealing in Phase 12-2) - the cloud's origin URL
- the device JWT in
~/.config/optimalos/credentials.json(mode 0600)
The cloud never sees plaintext credentials, never sees the device's private key, and never opens an inbound port to the device.
Token-paste flow (v1)
Three actors: the issuing browser (already unlocked at https://fabric.optimal.miami/), the cloud, and the device (running optimal pair).
Browser Cloud (Hetzner) Device
│ │ │
│ POST /api/auth/pair-init │ │
├────────────────────────────▶ │ │
│ { ttl: 600 } │ │
│ ◀──────────────────────────┤ │
│ { token, expiresAt } │ │
│ │ │
│ user copies token, │ │
│ reads it on the device │ │
│ │ │
│ │ `optimal pair --token …`│
│ │ ◀──────────────────────── ┤
│ │ POST /api/auth/pair-complete
│ │ { token, devicePubkey, │
│ │ capabilities } │
│ ├──────────────────────────▶ │
│ │ { deviceId, deviceJWT } │
│ │ │
│ │ eager re-wrap of all │
│ │ vault_entries to include │
│ │ the new device recipient │Concrete steps:
- Issue a pairing token from the browser. Open the dashboard, hit Add device, copy the resulting 10-minute token. Under the hood:
POST /api/auth/pair-initreturns a JWT signed withJWT_SIGNING_KEY,kind=pairing,ttl=600, and aninviteclaim that records the issuing browser's pubkey. - Run
optimal pairon the device. The CLI generates a freshagekeypair if one is not already at~/.config/optimalos/keys/device.key, collects local capabilities (RAM, installed harnesses, OS), and posts to/api/auth/pair-completealong with the token. - Cloud validates. The token must be live and unconsumed (
pairing_tokenstable tracks single-use). Cloud writes adevicesrow and avault_recipientsrow ofkind=device, then issues a 30-day device JWT. - Eager re-wrap. Cloud (and the issuing browser, on next dashboard load) re-wraps every
vault_entriesrow so the new device recipient can decrypt them. Atomicity is best-effort today; T8 in the threat audit tracks the Postgres RPC promotion. - Device opens its WebSocket.
optimalos-agent.service(orbun run devin development) dialswss://fabric.optimal.miami/ws/device, presents the JWT inSec-WebSocket-Protocol, and starts heart-beating every 20 seconds.
After step 5 the device shows up in the Sessions tab as an eligible target for the harnesses it has installed.
Capability declaration
Each heartbeat envelope publishes a capabilities block. The cloud upserts it into openclaw_instances and the (Phase 12-1) scheduler reads from there.
{
type: "heartbeat",
payload: {
deviceId: "uuid",
capabilities: ["has-gpu", "harness:claude-code", "ram:16g", "always-on", "has-returnpro-creds"],
installedHarnesses: ["claude-code", "openclaw"],
ramFreeMb: 11_240,
loadAvg: [0.32, 0.41, 0.39],
diskFreeGb: 612,
uptimeSec: 184_223,
optimalosAgentVersion: "0.10c-1.1"
}
}Capability tag conventions:
| Tag prefix | Meaning |
|---|---|
harness:<id> | The named harness adapter is installed and detect.sh exits 0. |
ram:<n>g | Total physical RAM, rounded down. Used by the (pending) capability scheduler. |
has-gpu | Any GPU detected; finer-grained tags TBD when the Phase 11-2 adapters surface real GPU needs. |
has-<creds> | Vault has at least one entry whose label matches a known credential bundle (e.g. has-returnpro-creds). |
always-on | The device is mains-powered and does not sleep (used as a tie-breaker). |
Custom tags are fine. The scheduler's requires list uses set membership against the union of all tags a device publishes.
Inspecting paired devices
From any signed-in browser at https://fabric.optimal.miami/:
- Sessions tab (Phase 14-1 thin slice): shows online devices and the harnesses each can run.
- Vault dashboard (Phase 10a-5): per-recipient revoke. Revoking a
devicerecipient soft-revokes thevault_recipientsrow and triggers eager re-wrap.
From the CLI (auth via the same OPTIMAL_FABRIC_TOKEN env var as the rest of optimal vault):
optimal vault recipients
# kind pubkey device_id revoked_at
# browser age1… - -
# device age1… <uuid> -
# recovery age1… - -Revoking a device
The flow is symmetric to pair:
- Click Revoke on the device row in the dashboard. This sets
vault_recipients.revoked_at = now()and triggers eager re-wrap excluding that recipient. - New ciphertext written from this point forward is no longer decryptable by the revoked device.
- T4 cross-check + 13b-1 cascade (both shipped): the device's 30-day JWT is still cryptographically valid, but
authMiddlewarecross-checksvault_recipients.revoked_atwith a 60s cache (T4 closeout, commit5ff9ba4), andWSMultiplex.closeRevokedConnectionByPubkey(commitcbd6bbd, Phase 13b-1) tears down any live WS session for that device within the same request that flippedrevoked_at. Stale JWT no longer survives until natural expiry. - If you suspect actual key compromise (laptop stolen, daemon binary tampered with), rotate
JWT_SIGNING_KEYin/opt/optimalos/secrets.envon Hetzner and restartoptimalos.service. That invalidates every JWT, browser and device alike, forcing a full re-pair.
Failure modes
| Symptom | Likely cause | Fix |
|---|---|---|
pair-complete returns 410 Gone | Token expired (10 minute TTL exceeded) or already redeemed | Issue a fresh token from the browser. |
pair-complete returns 401 | Token signature invalid; usually means JWT_SIGNING_KEY was rotated between issuance and redemption | Re-issue. |
pair-complete returns 500 | Migration drift or DB unreachable | Check Hetzner journalctl -u optimalos; usually a Supabase connectivity blip. |
| Device JWT works for a minute, then 401s | JWT_SIGNING_KEY was rotated (intentionally or via redeploy that wiped secrets.env) | Re-pair. |
| Heartbeat does not show up in browser | Cloudflared or optimalos.service restarted while the device was mid-WebSocket | Daemon will reconnect with exponential backoff (1s to 30s cap). Wait, then check journalctl -u optimalos-agent on the device. |
pair-init returns 401 | The issuing browser's session expired | Sign in again at /, then retry. |
What's coming
- Phase 12-2: TPM / Secure-Enclave device key sealing. Replaces mode 0600 disk storage of
device.key.
Shipped since this doc was last revised
- Phase 12-1: capability-aware routing scheduler. Shipped 2026-05-05 (
35c7f70); patched 2026-05-08 (c125cfa). RAM-aware scoring + harness/capability hard filter + lexicographic tie-break. Seemultiplex.md. - Phase 13a-1: OAuth Device Authorization Grant (RFC 8628). Shipped 2026-05-06 in commit
e1ef78d(merged7d4f203). Replaces token-paste with a phone-friendly user-code flow. Better for non-CLI devices and removes the "type a long token" UX papercut. Token-paste remains the fallback for already-paired devices and CLI-only pairings. - Phase 13b-1: Full revocation cascade + WebAuthn-gated destructive ops. Shipped 2026-05-07 (commit
cbd6bbd, mergeb100b03). Adds the daemon-sidecloseRevokedConnectionByPubkeycascade (src/server/ws-multiplex.ts:334), cockpit revoke UI, and last-of-kind guards so the operator can't accidentally revoke their last recovery recipient.
Source
- Charter §5 (session schema), §7 (transport), §8 (device daemon), §9 (capability routing):
~/.openclaw/workspace/optimalOS/docs/superpowers/specs/2026-05-03-fabric-charter.md - Vault design §5.3 (pair-a-new-device flow):
~/.optimalos/transfers/fabric-design/02-vault-design.md - Auth routes:
~/.openclaw/workspace/optimalOS/src/routes/auth.ts - Device daemon:
~/.openclaw/workspace/optimalOS/src/daemon/ - Threat audit (T4 device-JWT cross-check, §3-D, §3-E, §3-O):
~/.optimalos/transfers/fabric-design/06-vault-auth-threat-rerun.md