Skip to content

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

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.

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.

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.

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).

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}

Every significant choice has an Architecture Decision Record in docs/adr/. Start with:

The longer-form system design, security model and scale review go deeper.