Fabric Architecture
Physical layout, transport contract, and session flow as of 2026-05-05. This page summarizes; full topology is in fabric-hetzner-handoff-2026-05-04.md §1.
Physical topology
INTERNET
│
┌──────────────┼──────────────┐
│ │
fabric.optimal.miami optimal.miami
(CF orange-cloud) (CF orange-cloud)
│ │
▼ ▼
CF Tunnel: fabric-prod CF Tunnel: a6f4487b-…
UUID 00633812-46c8-4e70-… (Pi's existing tunnel)
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ HETZNER │ │ PI 5 │
│ CX32 │ ◀── pair ──▶ │ 16 GB │
│ Debian12 │ │ Bookworm │
│ 178.156. │ │ 100.94. │
│ 203.234 │ │ 77.114 │
└────┬─────┘ └────┬─────┘
│ │
cloudflared cloudflared (separate tunnel)
optimalos.service optimalos.service (Pi-side, classic)
caddy.service n8n / strapi / openclaw-gateway
(Caddy unused for optimal-discord / optimal-docs
tunnel; bypassed) Phoenix (docker)
[SAME bundle on both — gated by hostname (client) +
DEPLOYMENT env (server). See "Deployment modes" below.]The Pi tunnel is untouched. No DNS cutover happened. fabric.optimal.miami is a separate hostname on a separate tunnel. Since 2026-05-05 (commit bf69427), both origins serve the same bundle — the legacy and fabric experiences are decided at runtime, not at build time. See Deployment modes for the full primer.
Deployment modes
Two modes, one bundle:
- Legacy mode —
optimal.miami(Pi). Home / Board / Loom / Settings + tmux attach +[XFER]. No Sessions tab, no fuel meter, no vault. Fabric-only API routes return 410 GONE. - Fabric mode —
fabric.optimal.miami(Hetzner). Everything legacy mode has, plus Sessions tab + fuel meter +/vault/*+ Cockpit chrome.
The gate runs at request-time:
- Server —
process.env.DEPLOYMENT === "hetzner-cloud"insrc/server.tscontrols whether/api/vault,/api/auth/setup-init,/api/auth/devices, and/api/fuelare mounted. Pi has noDEPLOYMENTenv → those return 410. - Client —
client/fabric-mode.tsisFabricMode()checkswindow.location.hostnameagainst theFABRIC_HOSTNAMESset (withfabric.*wildcard +window.__FABRIC_MODE__override seam).client/main.tsshowApp()reads it to conditionally render Sessions tab, fuel meter, and vault routes.
Why this exists, what's gated where, how to add a new fabric origin, and how to debug a leak: Deployment modes.
Hetzner box
| Property | Value |
|---|---|
| Type | CX32 (€5.83/mo, 2 vCPU, 7.6 GB RAM) |
| IPv4 | 178.156.203.234 |
| IPv6 | 2a01:4ff:f4:699d::/64 |
| OS | Debian 12 bookworm, kernel 6.1.0-41-amd64 |
| Disk | 75 GB (1.3 GB used) |
| SSH | ssh -i ~/.ssh/id_ed25519 root@178.156.203.234 (key auth, no password) |
| UFW | public 22 + Tailscale 22 (drop public after Tailscale joins) |
Filesystem layout:
/opt/optimalos/app/— bundled Bunserver.js+ Viteclient/assets (~3.5 MB total)/opt/optimalos/workflows/— Loom workflow modules/opt/optimalos/operations/— Loom operation modules/opt/optimalos/data/— runtime data (SQLite)/opt/optimalos/secrets.env— mode 0600optimal:optimal, holdsJWT_SIGNING_KEY,INVITE_PASSWORD, Supabase keys/etc/cloudflared/00633812-…json— per-tunnel credentials/etc/cloudflared/config.yml—fabric.optimal.miami→http://localhost:3000/etc/systemd/system/optimalos.service—User=optimal, runsbun run server.js
Services: caddy.service (port 80, unused for tunnel), optimalos.service (port 3000), cloudflared.service (4 redundant CF edges).
Transport contract
Wire-locked in commit 7f55c3b (Phase 10c-1.1). The contract is intentionally minimal so we can swap NATS or QUIC later without rewriting the application layer.
- Channel:
wss://fabric.optimal.miami/ws/device(or/ws/browser) - Encoding: NDJSON, one envelope per line
- Auth: JWT in
Sec-WebSocket-Protocolheader (HS256,JWT_SIGNING_KEYrotates server-side) - Direction: device dials out (Anthropic / Cursor pattern) — cloud has no inbound device port
- Heartbeat: device → cloud every 20s, includes capabilities digest
- Envelope shape:
{corrId, type, direction, payload, ts}
corrId reuse rule — child envelopes (session.stdout, session.exit) reuse parent's corrId so the cloud router can pipe them back to the right browser SSE stream. Documented at src/daemon/handlers/session-start.ts:18-25.
JWT TTLs:
| Token type | TTL |
|---|---|
| Browser session | 12 h |
| Device JWT | 30 d |
| Pairing token | 10 min |
| Recovery token | 1 h |
⚠ Open issue (T4, P1): the device daemon does not currently cross-check vault_recipients.revoked_at before honoring its 30-day JWT. Cloud soft-revoke happens, but a device with a stale JWT can still serve sessions until rotation. See Phase Status → "Left to test".
Session flow
Browser Cloud (Hetzner) Device (Pi/laptop)
│ │ │
│ POST /api/sessions │ │
│ { harness, prompt, ... } │ │
├─────────────────────────▶ │ │
│ │ envelope { type: │
│ │ "session.start", │
│ │ corrId: $X } │
│ ├──────────────────────────────▶ │
│ │ │ spawn harness
│ │ stdout chunks │ pipe stdout
│ │ ◀──────────────────────────────┤
│ SSE: data lines │ │
│ ◀────────────────────── ─┤ │
│ │ envelope { type: │
│ │ "session.exit", │
│ │ code: 0 } │
│ │ ◀──────────────────────────────┤
│ SSE: close │ │
│ ◀────────────────────── ─┤ │Capability routing (Phase 12-1, pending): today, session.start routes to the single available device for the requested harness. Phase 12 promotes that to a RAM-aware scheduler that considers capability score, current load, and command allowlist scope. Stub at src/server/device-router.ts:24.
Storage layout
| Where | What | Persistence |
|---|---|---|
Cloud Supabase (hbfalrpswysryltysonm) | vault_entries, vault_recipients, vault_access_log, devices, pairing_tokens | Persistent |
Cloud SQLite (/opt/optimalos/data/) | fabric_sessions, Loom run state | Persistent (in-memory v1, promoted in Phase 14) |
| Device disk | ~/.config/optimalos/keys/device.key (mode 0600) | Persistent |
| Device RAM | Decrypted credentials (15-min TTL, best-effort zero) | Ephemeral |
| Browser localStorage | Trusted-device fingerprint marker | Per-fingerprint (30-day reset) |
⚠ Open issue (T7, P1): device → cloud fetch calls use system TLS trust store. No origin pubkey pinning yet. CA compromise + MITM still possible. Remediation persists cloud's TLS pubkey SHA-256 at ~/.config/optimalos/keys/cloud-pin.txt during pairing; checked on every fetch.
Source links
- Charter:
~/.openclaw/workspace/optimalOS/docs/superpowers/specs/2026-05-03-fabric-charter.md(501 lines) - Plan:
~/.openclaw/workspace/optimalOS/docs/superpowers/plans/2026-05-03-fabric-implementation.md(880 lines) - Decision Ledger:
~/.optimalos/transfers/fabric-design/03-decision-ledger.md(all 25 architectural decisions) - Handoff:
~/.optimalos/transfers/fabric-hetzner-handoff-2026-05-04.md(load-bearing on session start)