Architecture overview
Zoen is one Rust core shared by every client, a relay that orders and fans out signed (and usually encrypted) events, and an agent runtime that lets agents join conversations as real members.
flowchart TB
subgraph Clients["Clients (one shared Rust core)"]
direction LR
iOS["iPhone & iPad<br/>SwiftUI"]
Mac["Mac<br/>SwiftUI"]
Android["Android<br/>Kotlin · Jetpack Compose"]
Web["Web<br/>React + WASM core"]
CLI["zoen CLI<br/>(tests & journeys)"]
end
Core["Rust core<br/>types · signed log · store · MLS · pages · grants · sync"]
iOS & Mac -- "UniFFI (Swift)" --> Core
Android -- "UniFFI (Kotlin)" --> Core
Web -- "wasm-bindgen" --> WebCore["zoen-web-core"]
CLI --> Core
subgraph Server["Relay cell (staging: relay.tryzoen.com on Fly.io)"]
Relay["zoen-relay<br/>axum · WebSocket · protocol 4"]
FDB[("FoundationDB<br/>per-Space logs, heads,<br/>key packages, leases")]
PG[("Postgres<br/>directory, accounts,<br/>agents, ledger")]
NATS{{"NATS<br/>fan-out bus"}}
Blobs[("Object store<br/>encrypted blobs")]
Relay --- FDB
Relay --- PG
Relay --- NATS
Relay --- Blobs
end
Core <-- "Zoen Sync (WebSocket, protobuf)" --> Relay
WebCore <-- "Zoen Sync" --> Relay
subgraph Agents["Agent runtime"]
Agentd["zoen-agentd<br/>agents as citizens"]
Models["zoen-models<br/>model gateway (Rig)"]
Sandboxes["Sandbox tiers<br/>WASM · Firecracker · browser"]
Egress["zoen-egress<br/>only way out"]
Agentd --> Models
Agentd --> Sandboxes --> Egress
end
Agentd <-- "its own MLS device" --> Relay
The three ideas that shape everything
Section titled “The three ideas that shape everything”1. Every conversation is a signed log
Section titled “1. Every conversation is a signed log”A conversation — a Space — is an append-only log of events. Authors sign their own bytes (Ed25519); the relay chains them by hash and assigns order, but never rewrites them (ADR 0010). Messages, edits, reactions, page versions, mini-app actions and approvals are all events. Every client rebuilds the same state by replaying the same log, which is why iPhone, Android, web and the CLI behave identically.
2. The relay is deliberately small
Section titled “2. The relay is deliberately small”zoen-relay does three things: orders each Space’s log in FoundationDB, stores signed envelopes verbatim (ciphertext in end-to-end Spaces), and fans out events and ephemeral signals to online members. Offline devices catch up by cursor. Membership is the only thing it must understand, because it decides who may write and who receives. See Relay & server.
3. Agents follow the same rules as people
Section titled “3. Agents follow the same rules as people”An agent has its own identity and device key, joins a Space through the same membership flow, and reads end-to-end content only as a declared member. Sensitive actions go through durable approval cards signed by the owner. See Agents runtime & sandboxes.
Where code lives
Section titled “Where code lives”| Layer | Path | Read more |
|---|---|---|
| Shared core | crates/roda-* (types, log, store, mls, docs, grants, gate, proto, ffi) |
Rust core |
| Browser core | crates/zoen-web-core, packages/zoen-ui/web |
Clients |
| Relay | crates/zoen-relay |
Relay & server |
| Agents | crates/zoen-agentd, zoen-models, zoen-runtime, zoen-sandboxd, zoen-guestd, zoen-egress, zoen-liveview |
Agents |
| Apple | apple/ (XcodeGen project.yml) |
Clients |
| Android | android/ |
Clients |
| Mini-apps | miniapps/, packages/zoen-miniapp-sdk, tools/miniapp-build |
Mini-apps |
| Infrastructure | infra/ (Fly, Kubernetes, OpenTofu, FDB, k3d) |
Relay & server |
Some crates still carry a legacy prefix in their directory and package names. The product name is Zoen everywhere; the rename is a tracked, atomic cutover (naming).
A message, end to end
Section titled “A message, end to end”sequenceDiagram
participant A as Ana's phone (core)
participant R as zoen-relay
participant F as FoundationDB
participant N as NATS
participant B as Bruno's phone (core)
A->>A: Seal with MLS, sign envelope, save to durable outbox
A->>R: Publish{envelope}
R->>R: Check membership & limits
R->>F: Append to Space log (seq + hash chain), batched per Space
R-->>A: Accepted{seq}
R->>N: Publish on the Space subject
N-->>R: Edge with Bruno connected
R-->>B: Event{envelope}
B->>B: Verify signature, open MLS, project timeline
Note over B: Offline? Bruno catches up later with Sync{cursors}
Design records
Section titled “Design records”Every significant choice has an Architecture Decision Record in docs/adr/. Start with:
- 0008 FoundationDB as system of record · 0009 Fan-out logs, not inboxes · 0010 Binary protocol
- 0026 End-to-end Spaces on OpenMLS · 0027 End-to-end by default
- 0028 Agent sandboxes · 0042 Dynamic UI
The longer-form system design, security model and scale review go deeper.
