01The insight: double-entry is already reactive
In a Quilt sheet, every computed cell has a list of parents — the cells it reads from. When a parent changes, the engine recomputes the child. The child pulls from its parents. From the parent's perspective, the same event is a push — it just published a new value to whoever depends on it.
The engine doesn't care which direction the arrow points. The propagation is symmetric. This is double-entry bookkeeping, and it is already the reactive model. A change in cell A flows to cell B; A and B don't agree on who initiated, they only agree that a propagation happened.
cell B ◀─── pull ───── cell A
// the engine does not know or care which is which
Why federation needs no new abstraction
When cell B is local, propagation is in-memory. When cell B is on a Jetson 3 meters away, propagation is over LAN. When cell B is in a GitHub Codespace on the other side of the planet, propagation is over HTTPS. The subscription shape is identical in all three cases. The cell declares its derived_from; the engine resolves the parents; if a parent is remote, the wire format is the same as the in-memory one.
Everything else on this page is the consequence of that one observation. The URI scheme, the two new SDK functions, the FederatedQuilt class, the Codespaces-native orchestrator — they are all the same reactive engine with a wider reach.
- Local cell → in-process subscriber, push via in-memory event
- LAN cell → WebSocket subscriber, push via JSON-over-LAN
- Codespace cell → HTTPS subscriber, push via JSON-over-MCP
- Fleet cell → wildcard subscriber, push to every matching instance
One engine, one subscription model, one audit log. The network is just a slow bus.
02The URI scheme
The URI is the only thing that changes between local and remote. It is deliberately similar to other well-known schemes you already trust — and the grammar is small enough to implement in 10 lines.
The instance-id is a logical name for a running engine — local, jetson-lab, codespace-7c3, or an ESP32 serial number. The sheet-id is the name of the manifest loaded in that engine. The cell-path is the dotted path to a cell within the sheet, and supports the + wildcard for fleet-wide subscriptions.
Five shapes you will use
up{job="node"} — every matching instance receives the push.Resolution algorithm (10 lines)
// resolve a quilt:// URI to a live subscriber handle function resolveCell(uri, ctx) { const m = uri.match(/^quilt:\/\/([^/]+)\/([^#]+)#(.+)$/); if (!m) throw new Error(`bad URI: ${uri}`); const [_, instance, sheet, cell] = m; if (instance === 'local') return localEngine.cell(`${sheet}#${cell}`); if (instance === '*') return router.fanout(sheet, cell, ctx); return transport.connect(instance).subscribe(sheet, cell, ctx); // LAN / HTTPS / MCP }
That's it. The rest of the federation story is what happens around that handle: how the subscription is set up, how pushes are routed, how the run trace is recorded. None of that changes the cell's evaluate() — it just calls derive() on its parents and the parents happen to be on a different quilt.
Why quilt:// and not https://
quilt:// is the application's identifier; https:// is the transport. Cells refer to other cells by their quilt URI, not their network address. The transport is resolved by the engine's transport layer (mDNS, MCP, WebSocket) and is invisible to the cell. A fleet can swap LAN for LoRa, or HTTPS for a satellite modem, without touching a single cell definition.
03The federation primitive
Two new SDK functions and one new class. They are deliberately minimal: resolve a cell, subscribe to a cell, and stitch multiple engines into one addressable graph. Everything else is composition.
① resolveCell(uri, ctx)
Given a quilt:// URI, return a live CellHandle. The handle is a subscriber — call handle.get() to read the current value, or pass it as a derived_from entry to a cell. Local URIs resolve to in-process handles (no network). Remote URIs open a transport and return a live-streaming handle that yields values as they change on the source.
import { resolveCell } from '@quilt/federation'; // local — instant, in-process const rudder = await resolveCell('quilt://local/boat-autopilot#rudder.angle'); // remote — opens MCP/HTTPS, streams values const anomalies = await resolveCell('quilt://codespace-7c3/prod#anomaly.detector', { run_id: '01HXY' }); console.log(rudder.value, anomalies.value); // current values
② subscribeCell(uri, callback)
Get a callback fired every time the cell's value changes — locally, remotely, by any cell, by any user, by any code path. The callback receives the new value and a trace object describing who changed it, when, and what the previous value was. The returned Unsubscribe cleans up the subscription (closes the socket, removes the listener).
import { subscribeCell } from '@quilt/federation'; // "tell me whenever the cloud's PID tuning changes" const off = subscribeCell('quilt://codespace-7c3/prod#tune.pid_kp', (kp, trace) => { console.log(`kp → ${kp.value} by ${trace.actor} at ${trace.t}`); algoPid.derived_from = ['kp']; // rewire locally }); // later off(); // close the subscription, release the socket
The FederatedQuilt class
The class that stitches multiple engines into one addressable graph. It owns the routing table, the transport adapters, and the cross-engine propagation log. You can think of it as a superset of an engine — it has cells, and its cells can be on other engines.
// FederatedQuilt — a quilt of quilts. Each "node" is a real Engine (local) // or a remote URI handle (remote). One trace log for the whole fleet. import { Engine } from '@quilt/core'; import { resolveCell } from '@quilt/federation'; import { transport } from '@quilt/transport'; export class FederatedQuilt { constructor(name) { this.nodes = new Map(); // instance-id → Engine | Remote this.edges = []; // { from, to, kind } this.trace = []; // every cross-tier change } addLocal(id, engine) { this.nodes.set(id, engine); engine.on('change', (cell, _p, next, actor) => this._propagate(id, cell, next, actor)); return this; } async addRemote(id, ep) { const conn = await transport.connect(ep); this.nodes.set(id, conn); conn.on('push', (cell, value, actor) => this._propagate(id, cell, value, actor)); return this; } wire(from, to, kind = 'mirror') { this.edges.push({ from, to, kind }); } async resolve(uri, ctx = {}) { return resolveCell(uri, ctx); } private async _propagate(fromId, cell, value, actor) { this.trace.push({ t: new Date().toISOString(), from: fromId, cell, value, actor }); for (const e of this.edges.filter(x => x.from.startsWith(fromId) && x.from.endsWith(cell))) { const [tier, path] = e.to.split(/#/); await this.nodes.get(tier.split(/\//)[0])._setCell(path, value, actor); } } } // 3 lines to federate a fleet const fleet = new FederatedQuilt('boat-fleet'); fleet.addLocal('local', esp32Engine); fleet.wire('local#mirror.rudder', 'jetson-lab/perception#mirror.telem'); fleet.wire('jetson-lab#perception.fused', 'codespace-7c3/prod#anomaly.input');
That is the entire federation surface. The class is ~90 lines; the rest is transport adapters and routing. Notice what is not there: no message broker, no service mesh, no API gateway, no protocol negotiation. The cells push and pull; the engine routes; the trace is one log.
The same shape, three different transports
Local propagation is a function call. LAN propagation is a WebSocket frame. Cloud propagation is an HTTPS POST (or MCP call). The wire format is identical: { uri, value, prev, actor, t, trace_id }. The cell receiving the push does not know, and does not need to know, which transport delivered it.
04The killer example: Fed-Autopilot
A 3-tier Quilt stack for an autonomous boat. ESP32 at the bottom, Jetson in the middle, GitHub Codespace at the top. Each tier runs an engine; each engine has its own cells; the cells are linked by quilt URIs that cross the network boundaries. There is no separate message bus. The reactive graph is the bus.
What runs where
quilt://codespace-7c3/prodquilt://jetson-lab/perceptionquilt://esp32-fleet/+/autopilotWhat pushes up, what pulls down
| Edge | From → To | What happens |
|---|---|---|
| ESP32 → Jetson | rudder.angle → mirror.telem | Every cycle (20 ms), the ESP32 publishes its current rudder angle. The Jetson subscribes and feeds the perception engine. No polling — push only. |
| ESP32 ← Jetson | algo.pid.intent ← autopilot.intent | The Jetson publishes a desired heading correction based on perception. The ESP32 subscribes to its local mirror and adjusts motor PWM. |
| Jetson → Codespace | perception.fused → anomaly.input | The Codespace runs an anomaly detector (a model cell) over the full telemetry stream. It doesn't poll; the stream arrives via the federation. |
| Codespace → ESP32 | tune.pid_kp → algo.pid | A human (or a nightly job) updates the PID gain in the Codespace. The new value is pushed to every boat via the wildcard quilt://esp32-fleet/+/algo.pid. |
| Codespace → human | anomaly.detector → alerts.notify | When the detector fires, it pushes to alerts.notify, which in turn calls Slack, PagerDuty, or Twilio. Audit is one engine.serialize() away. |
The thing to notice
There is no message broker. No Kafka, no NATS, no MQTT. The cells push to their parents; the parents push to their consumers. Three engines, one reactive graph, one audit log. The network is invisible to the cells — and the cells don't care.
05Codespaces-native Quilt
GitHub Codespaces is the canonical orchestrator target. It gives you a real Linux box in the cloud, a browser-based terminal, a public HTTPS endpoint on a stable subdomain, and an env-var-based secret store — all without managing a server. For a fleet of boats (or any small fleet of edge devices), this is exactly the right shape of orchestrator: cheap, ephemeral, fully scriptable, and reachable from anywhere on the internet.
Six steps to a Quilt in a Codespace
Add a devcontainer
One file. Pre-built Quilt dev env. Every Codespace that opens your repo has Quilt installed, with all the federation transports compiled in.
Run quilt serve
The TUI starts in the browser-based terminal. Watch the live cell graph, push values, inspect the trace. Same as the local Quilt Live — just running in the cloud.
Expose the TUI and MCP
Port 7681 runs the ttyd-served TUI. The MCP server runs on stdio, so any agent (Claude Code, Codex, custom) can mount the whole cloud engine as tools.
Set the token in env
Every Codespace has a secrets store. Set QUILT_TOKEN to a long-lived random string; the engine requires it on every connect.
Point the IoT devices at the URL
The Codespace has a stable HTTPS preview URL. Configure your devices once. They push; the Codespace receives, routes, logs, alerts.
The Codespace IS the orchestrator
The orchestrator is just another Quilt engine, with cells for anomaly.detector, alerts.notify, audit.run_trace. The same reactive graph, the same run trace, the same MCP surface.
Why a Codespace, not a VM, not a serverless function
Codespaces give you a long-lived Linux process (not a 15-minute timeout), a free HTTPS endpoint (the githubpreview.dev URL), and a shell for debugging. A serverless function can host a cell; it cannot host an engine that maintains a federation over time. A VM can; a Codespace is a VM you didn't have to provision.
06Live 3-tier demo
Three simulated Quilt engines, one reactive graph, real propagation. Click the buttons; watch the values flow; read the run trace. This is the same code path that runs on real hardware — just with fake sensors.
Fed-Autopilot · 3 tiers · live
Two scripted flows — click to run
① Upstream device → cloud
mirror.rudder reflects it; the edge ━━━▶ carries the value to Jetson's mirror.telem.
Jetson recomputes perception.fused and ━━━▶ forwards to Codespace's anomaly.input.
Codespace re-runs the anomaly model and lights up anomaly.detector if the pattern matches.
② Downstream cloud → device
The edge ◀━━━ carries it down to ESP32's algo.pid (via the wildcard
esp32-fleet/+/algo.pid).The PID recomputes, drives actuator.motor_pwm, the rudder adjusts to the new gain.
The buttons above trigger real propagation through the simulated federation. Every cell that receives a value pulses green (upstream) or blue (downstream). Every change appends to the run trace. The SVG edges between tiers are dashed at rest and light up as the value flows through them.
07Cross-Quilt observability
Every cross-tier change is a run trace. The trace is the same shape as the local trace — one log, append-only, hash-chained — but with an extra field: the origin_tier and the trace_id that lets you correlate the same value across every quilt it touched.
Example trace JSON
// one cross-tier change, 3 tiers, 6 cells { "trace_id": "01HXY2K9-7c3", "t": "2026-01-15T14:22:01.482Z", "origin_tier": "esp32-7c3-a1b2", "origin_actor": "sensor:rudder-pot", "hops": [ { "tier":"esp32-7c3-a1b2", "cell":"sensor.rudder", "value":0.42, "ms":0 }, { "tier":"esp32-7c3-a1b2", "cell":"mirror.rudder", "value":0.42, "ms":0, "kind":"mirror" }, { "tier":"jetson-lab", "cell":"mirror.telem", "value":0.42, "ms":11, "transport":"websocket/lan" }, { "tier":"jetson-lab", "cell":"perception.fused", "value":0.44, "ms":28, "kind":"formula" }, { "tier":"codespace-7c3", "cell":"anomaly.input", "value":0.44, "ms":187, "transport":"https/mcp" }, { "tier":"codespace-7c3", "cell":"anomaly.detector", "value":true, "ms":203, "kind":"ai" } ], "totals":{ "ms":429,"cells":6,"tiers":3 }, "hash":"sha256:f1b4...7e" }
The ledger view
Each Quilt is its own "book" in the double-entry sense. The local trace is one ledger; the federated trace is a merge of ledgers with a common hash chain. To audit "what did the fleet do on Tuesday", you concatenate every per-quilt trace for that day and verify the chain.
| Quilt | Role | What the ledger records |
|---|---|---|
| esp32-7c3-a1b2 | edge | Every sensor read, every motor write, every PID cycle. 50 Hz × N cells. Sampled and shipped to Jetson once per second. |
| jetson-lab | mid | Every perception inference, every fused output, every push to/from ESP32 and Codespace. 5 Hz per upstream feed. |
| codespace-7c3 | cloud | Every anomaly detection, every alert sent, every PID tuning published. Plus the immutable log of every cross-tier change it has witnessed. |
One trace, three books
The local ledger is the engine's serialize(). The federated ledger is a deterministic merge of the per-quilt ledgers, with a trace_id that lets you reconstruct the order of cross-tier changes. There is no separate "distributed tracing" library; the engine already records everything, and the merge is one function call.
08Security
A federated engine is a network service. It needs auth, transport security, and per-cell authorization. The cell types that touch the physical world (motors, brakes, money) need extra guarantees: an irreversible flag that makes a cell write-once, and a rollback path the federation can revert across.
Token auth · required
Every cell write from a device must carry the QUILT_TOKEN. Tokens are scoped to a namespace; a token for esp32-fleet/* cannot write to codespace-7c3/prod.
mTLS · for production
Each device gets a client cert at provisioning time. The Codespace mints a server cert from Let's Encrypt. Mutual TLS prevents token-leak replay and gives you a real CA-anchored audit chain.
Per-cell ACLs
Read-only cells, write-once cells, role-gated cells. The ACL is declared in the manifest and enforced by the engine before the value propagates.
irreversible: true
For high-stakes cells (motors, brakes, money, anything that mutates the physical or financial world). The cell rejects any value that is not strictly greater / more conservative than the previous one. The federation can never accidentally drive the motor to an unsafe value.
rollback · across the federation
One snapshot per tier. One rollback call restores the whole fleet to a known-good state. The same reactive history that powers replay powers rollback.
Audit · signed traces
Every trace is signed by the engine that produced it. The Codespace collects the per-tier traces, verifies the signatures, and writes a hash-chained summary to durable storage. The "who did what when" question has a one-call answer.
The shape of trust
Devices trust the cloud (mTLS, signed manifests). The cloud trusts devices (token + cert + ACL). Cells trust their parents (typed schema, validated preconditions). Humans trust the audit (hash-chained, signed, append-only). Each link is a different mechanism; together they form a chain where breaking any one of them is detectable.
09What ships today, what's next
Three things are in the repo and working. Three more are in design. The first three prove the primitive; the second three prove the orchestrator.
resolveCell · subscribeCell primitivesquilt:// URI to a live handle; subscribes any cell to push events from any tier. ~80 lines each.FederatedQuilt class.devcontainer/devcontainer.jsonquilt serve in the browser terminal, MCP on stdio. The hello-world of a federated Quilt.FederatedArtifactStore R2-backedquilt-fleet repo multi-device orchestrationquilt-jetson repo onboard tierIf you build on the federation, your work goes here. The primitives are open, the class is open, the Codespace config is open.
Companion reads
- Neural Cells — the deeper theory: every cell is a generalized message-passing node.
- Agent Substrate — the registry / resolver / substrate: how the engine already implements 5 agent-substrate primitives.
- Engines — the cell kinds that wrap external state (paper trading, backtest, simulator).
- Universe Model — a Quilt sheet as a self-contained universe, wrapping any engine (robotics sim, game engine, notebook).
Where to go from here
Three doors, depending on what brought you here.
FederatedQuilt class. The Codespace devcontainer. MIT licensed.