MDK is the Rust implementation of Marmot, an end-to-end encrypted group
messaging protocol built on MLS (RFC 9420) with Nostr identity. It powers
White Noise: the protocol engine, encrypted storage, Nostr transport, the app runtime behind the mobile bindings, the
wn command-line app, and the wn-agent connector that brings AI agents into encrypted chats.
The protocol specification lives in marmot-protocol/marmot. This repository holds the implementation, its architecture notes, conformance tests, formal models, and release tooling.
- Find your way
- Chat with an AI agent
- The
wncommand-line app - Build an app on MDK
- How MDK is built
- How convergence works
- Crate map
- Development
- Releases
- License
| I want to… | Start here |
|---|---|
| Talk to Hermes, OpenClaw, Claude Code, Codex, OpenCode, Pi, or Goose from White Noise | White Noise + Agents quickstart |
| Use White Noise from a terminal (CLI, daemon, TUI) | wn guide |
| Integrate MDK into an iOS, macOS, or Android app | MarmotKit integration guide |
| Integrate from C or another raw-FFI host | C ABI guide |
| Upgrade an app to a new MDK release | Version upgrade guides |
| Understand the Marmot protocol | Marmot specification |
| Understand MDK's architecture | Architecture index |
| Understand how clients agree on group state | How convergence works |
| Contribute code | Development |
| Cut a release | Release guide |
wn-agent gives an agent runtime its own White Noise identity and bridges encrypted Marmot chats to it over a local
control socket (same-user by default, optional bearer token). You invite the agent from White Noise on your phone like any other contact; only accounts
you allowlist can invite or message it.
| Runtime | Connector | Docs |
|---|---|---|
| Hermes | Gateway plugin | integrations/hermes |
| OpenClaw | Channel plugin | integrations/openclaw |
| Claude Code | Terminal harness | integrations/claude |
| Codex | Terminal harness | integrations/codex |
| OpenCode | Terminal harness | integrations/opencode |
| Pi | Terminal harness | integrations/pi |
| Goose | Terminal harness | integrations/goose |
The quickstart installs a checksum-verified release on macOS or Linux and gets you to a first
encrypted chat. For how connectors share one machine, identities, and allowlists, see
How the connectors fit together. The connector daemon itself is documented in
crates/agent-connector.
wn manages accounts, relays, KeyPackages, chats, groups, and messages, and includes a terminal UI (wn tui). wnd
is the background daemon that keeps accounts synced and serves the CLI and TUI. Install both from a checkout:
cargo install --path crates/cli --locked --binsThe wn guide covers configuration, a two-account quick start against local relays, the full
command map, the daemon, the TUI, and JSON output for scripting.
Apps embed MDK through the multi-account app runtime (marmot-app), exposed as:
- MarmotKit — UniFFI bindings for Swift (iOS, macOS) and Kotlin (Android). Start with the integration guide, then the complete method reference and distribution notes.
- Marmot C — a C ABI over the same runtime, with ownership and blocking-call rules in the C guide and every symbol in its reference.
Each release ships a concise release note and a detailed upgrade guide
with required changes, new defaults, and optional features. The latest is 0.12.0 → 0.13.0;
read every intervening guide when skipping releases. Read docs at the tag matching your binaries — master may describe
unreleased APIs.
MDK separates the protocol engine from storage, transport, and application concerns so each can be tested, replaced, and reasoned about on its own:
apps (MarmotKit / C) wn CLI · wnd · TUI wn-agent ──► agent runtimes
└────────────────┬─────┴──────────────┘
marmot-app multi-account runtime, projections
marmot-account account homes, keys, transport orchestration
cgka-session one encrypted account-device lifecycle
cgka-engine OpenMLS group state machine + commit convergence
storage-sqlite SQLCipher persistence (one DB per account-device)
transport-nostr the relay plane: shared relay sessions, delivery, directory fetches
transport-nostr-peeler / -adapter Nostr events ⇄ engine messages, relay I/O
transport-quic-stream / -broker transient live previews for agent replies
- The engine is generic over a storage trait and knows nothing about Nostr; transport code lives in the peeler and adapter.
- Nostr plays three roles — identity, app message format, and (pluggable) transport. See Nostr's role.
- For the full picture, start with the executive summary, target architecture, and protocol boundary, all linked from the architecture index.
In Marmot, commits are the consensus log. Clients fetch from several relays, see messages in different orders, and may come back online holding commits from abandoned branches. Every honest client that receives the same valid messages must still land on the same epoch and group state. The engine — not relay order or timestamps — picks the canonical branch.
| To understand… | Read |
|---|---|
| The convergence model | Distributed convergence |
| The engine's canonicalization contract | Canonicalization contract |
| How it is tested: scenarios, vectors, generated chaos, property tests | Conformance simulator |
| Multi-process and container campaigns | Campaign runner |
| Formal proofs and how they map to Rust tests | Tamarin models |
| What the engine owns and leaves out | cgka-engine |
| Area | Crate | What it is |
|---|---|---|
| Engine | cgka-engine |
OpenMLS-backed group state machine and commit convergence |
traits (cgka-traits) |
Shared traits and cross-boundary types | |
cgka-session |
Encrypted account-device session over the engine | |
| Storage | storage-sqlite |
SQLCipher persistence for engine, session, and simulator |
fs-private |
Owner-only file, directory, and socket creation | |
media-store |
Encrypted per-file media store: file format and file lifecycle | |
| Transport | transport-nostr-peeler |
Nostr event ⇄ engine message boundary |
transport-nostr-adapter |
Relay I/O behind an injectable client | |
transport-nostr |
The relay plane: per-account relay sessions, delivery and directory fetches | |
transport-quic-stream |
QUIC transport for live agent text previews | |
transport-quic-broker |
Memory-only preview broker (marmot-quic-broker); deployment |
|
| App | marmot-account |
Account homes, key storage, transport orchestration |
marmot-app |
Multi-account app runtime used by every front end | |
marmot-markdown |
CommonMark + Nostr display parser for messages | |
marmot-forensics |
Append-only JSONL forensic audit schema | |
| Bindings | marmot-uniffi |
MarmotKit: Swift and Kotlin bindings |
marmot-uniffi-macros |
Runs MarmotKit's async exports on its own Tokio runtime | |
marmot-c |
C ABI bindings | |
| Front ends | cli (wn-cli) |
wn, wnd, and wn tui |
| Agents | agent-connector |
The wn-agent connector daemon |
agent-control |
Agent control protocol messages and framing | |
agent-stream-compose |
Live-preview stream composition | |
integrations/ |
Per-runtime plugins and terminal harnesses | |
| Verification | cgka-conformance-simulator |
Multi-client scenarios, vectors, chaos, property tests |
convergence-campaign-runner |
Distributed container/VM campaigns | |
incident-replay |
Triages forensic exports of real incidents into verdicts and simulator vectors | |
formal/tamarin |
Tamarin models for convergence and lifecycle |
The toolchain is pinned in rust-toolchain.toml; most tasks run through just.
Cargo.toml patches HPKE 0.7 with the local vendor/hpke-rs-libcrux
provider to use libcrux-kem 0.0.10, fixing RUSTSEC-2026-0330 and RUSTSEC-2026-0331.
Its backport notes record provenance, regression commands and the removal condition.
just fast-ci # formatting, static gates, CI's two clippy passes; skips the `just test` matrix
cargo test -p <crate> # targeted tests for the crate you changed
just ci # full local parity with GitHub CI (slow)Pull requests run a pre-check of a few minutes: formatting, the static and
contract gates, dependency audit, workspace clippy and the WASM build. They run
no tests. The merge queue runs the full matrix on the exact commit it merges:
every workspace test with the ci nextest profile, the CLI and relay end-to-end
suites, simulator lanes, C bindings, iOS clippy, Windows filesystem contracts,
Tamarin, Hermes and installer checks. Required CI must pass there before a
change lands. Master pushes run CI only to refresh the Rust dependency caches
that pull requests and merge groups restore, and only when Cargo.lock, a
Cargo.toml, the toolchain pin, Cargo config or ci.yml changes.
Add ci:full and re-run all jobs to run the full matrix on a pull request,
along with complete Android/Apple candidate packaging. ci:apple adds the iOS
clippy lane and Apple packaging, and ci:android adds Android packaging; for
packaging, re-run all candidate-workflow jobs or push a new commit. Ordinary PRs
skip complete native candidate packaging, including Android target compilation
and linking; use the opt-in when a change needs that signal before merging.
Candidate packaging also runs on master when its inputs change, and weekly.
Existing nightly and weekly campaigns retain their wider coverage. Broker image
publishing stays serialized per ref so an older build cannot overwrite a newer
latest image.
Common targeted runs:
cargo test -p cgka-engine
cargo test -p cgka-conformance-simulator # scenarios, vectors, property tests
cargo test -p cgka-conformance-simulator --features conformance-slow # wider property-test run
just tamarin # formal modelsThe CLI's real-relay end-to-end tests need the local relay stack:
just relay-up
just e2e-test
just relay-downNotes:
- Stack size.
.cargo/config.tomlsetsRUST_MIN_STACK=4194304for Cargo-launched tests and debug binaries; unoptimized OpenMLS can overflow the default 2 MiB thread stack. Export the same value if you run a debug binary directly. - WASM.
just wasm-checkis a compile-only gate forcgka-traits,cgka-engine, andtransport-nostr-peeleronwasm32-unknown-unknown(rustup target add wasm32-unknown-unknown; setCC_wasm32_unknown_unknownto a Clang with a wasm32 backend if needed). It does not claim browser support for storage, the runtime, bindings, or the CLI. - Commits must be cryptographically signed.
MDK versions the whole workspace as one compatibility cohort and publishes separate artifact tracks from the same
commit: the source snapshot (v<version>), the wn-agent connector (wn-agent-v<version>), MarmotKit bindings
(marmotkit-v<version>), and the C bindings (marmotc-v<version>). The release guide is the checklist;
per-release notes live in docs/release/.
Nightly pre-releases such as marmotkit-v0.14.0-nightly.20261020 are built every day from the newest master commit that
passed CI. They are for testing only: pin a formal release for anything you ship, expect each nightly to be deleted
after 30 days, and don't count on going back to an older nightly. See nightly builds.
MIT.