Reference

Architecture

Yttrium is a small stack with a hard seam: protocol and engine know nothing about hosts, browsers, or the playground. Everything else is composition around apply.

Layer map

LayerOwns
src/protocolGeneric IDs, JSON values, shape descriptors, operation and frame envelopes, codecs.
src/engineDeterministic apply, trees, overlays, snapshots, handles, stream lifecycle, artifact coverage.
src/clientSessions, hubs, persistence, optimistic mutation queues.
src/accessApplication authorization hooks, policy replicas, private views.
src/hostAuthentication, Durable Object persistence, policy enforcement, delivery, observability.
src/batteries/textText CRDT state and operations, text stream folding.
src/exampleDemo schema, point extension, raster artifact, demo identity and roles.
src/simSeeded lossy network for tests. No sockets.
playgroundDemo UI and this docs site.

Core modules must not import batteries or example code. Batteries may depend on core contracts. The example installs batteries and its own adapters into a runtime and hands that runtime to server, client, and simulator.

The write path

A durable write travels this path on the host:

  1. Gateway authenticates the upgrade and binds a principal to the socket.
  2. The Durable Object verifies the routed domain and restores SQL state if it just woke.
  3. Policy replicas must be ready. Missing policy fails closed.
  4. Application authorize runs on the inbound frame.
  5. DomainServer.effect / apply produce a persist record and outbound frames.
  6. The host writes the log (and state) first.
  7. Memory updates, then fanout. Each recipient is authorized again on the outbound frame.

Live lanes — presence and stream chunks — can persist a live-state marker, but they do not allocate a commit. Artifacts rebuild only after a commit changes the covered-prefix dependency.

On the client, Session.mutate assigns a UUID, pushes an overlay entry, and returns a mutate frame. The playground's Outbox writes that frame to IndexedDB before send. onFrame applies inbound commits to auth, retires overlay entries on ack/nack, and view() folds the rest.

Runtime, not plugins on the wire

EngineRuntime is a local registry. Functions never appear in frames, snapshots, or shape descriptors. A shape names an extension by id and version. Both peers must have installed that adapter. An unknown adapter fails closed.

An ExtensionAdapter supplies initial JSON, validation, a deterministic reducer, and optionally stream operations. A slice has at most one extension. SchemaAdapter owns registries, migrations, and historical-operation upcasts so core can version shapes without knowing demo names.

Mounts

Sharing is mounting, not containment. The parent stores { type: "mounted", domain_id }. The child is another Durable Object with its own clock, policy, and persistence. Child commits do not propagate to the parent. subtree_at rolls up inside a domain and stops at the mount.

The parent commits the mount itself — added, removed, renamed, reordered. Per-mounter view state (pin, last-read, nickname) belongs on the parent. Access is enforced by the child on connect. Holding the id is not a capability.

A client therefore holds a vector of positions, one per open domain. Hub.positions() reports that vector. There is no merge semantics between components.

Cross-domain moves are two mutations and can be observed independently. If a move must be atomic, keep it inside one domain or accept a visible intermediate state.

Client composition

Hub is one connection identity and a map of domain → Session. Each running hub gets a fresh client id, including duplicated tabs and user switches. Storage is scoped by the application's account key, not by that connection id.

ClientStore keeps an authoritative snapshot cache separate from the outbox. Snapshot writes cannot replace the queue or rewind the cached commit. Confirmed state can be cached while optimistic edits remain; the overlay is kept separately in the outbox. Selected private-view display hints may be persisted alongside confirmed content, never as fresh subscription frames. Mutation records store the original sender, frame, and predecessor id so any tab of that account can replay queued work.

Host lifecycle

Incoming sockets use hibernation attachments: principal, client id, subscription id, and a private-view revision high-water mark. Heap state — including the in-memory peer map and live stream prefixes — can disappear when the isolate is evicted. SQLite is the source of truth.

After wake, the host reloads the log and snapshot, then resynchronizes policy caches before authorizing protected work. Active outgoing policy WebSockets keep a content DO awake and close when the last consumer leaves. Compacted snapshots discard unfinished live prefixes.

See Host for SQL tables, routes, and telemetry.

What core refuses to know

Those omissions are the design. Applications install adapters and hooks. The playground is one such application.