efema-proto 0.1.0

The wire format of efema: streams, positions, cursors and epochs as a client and a relay exchange them
Documentation

Your devices leave their changes for each other in one place: efema keeps them in order, hands each device what it missed, and cannot read any of it.

Why

A local-first app keeps your data on your device and works without a network. Then you install it on a second device, and the two need somewhere to leave their changes for each other. The usual answers are a sync service that reads everything it carries, or a database server that has to understand your data to store it.

efema is the third answer: a relay that knows nothing about your data. Devices append batches of opaque entries to a stream; the relay gives them positions, links them into a hash chain and hands every device what came after its cursor. It never merges, never decides who is right, and - once the client library seals entries on the device - cannot read what it stores.

A day in the life

A relay on a home server. A laptop writes two changes, a phone catches up and writes one of its own, the laptop reads it - and a photo library, on a newer format, syncs alongside:

$ efema-server serve --data ./relay
2026-10-08T18:36:09.749999Z  INFO efema-server 0.1.0 listening on http://127.0.0.1:2451, data in ./relay
2026-10-08T18:36:11.688941Z  INFO notes: wrote 2 entries (1200 bytes) at 1..2, epoch 1
2026-10-08T18:36:11.736746Z  INFO notes: read 2 entries after 0
2026-10-08T18:36:11.741075Z  INFO notes: wrote 1 entry (512 bytes) at 3, epoch 1
2026-10-08T18:36:11.743149Z  INFO notes: read 1 entry after 2
2026-10-08T18:36:11.747426Z  INFO photos: wrote 3 entries (147456 bytes) at 1..3, epoch 2

What the relay keeps is what its operator can see - positions, epochs, sizes and links, never contents:

$ efema-server streams --data ./relay
STREAM  ID        EPOCH  ENTRIES       SIZE  UPDATED (UTC)
notes   11c3cd7e  1            3    1.7 KiB  2026-10-08 18:36
photos  6964d409  2            3  144.0 KiB  2026-10-08 18:36

$ efema-server streams notes --data ./relay
SEQ  EPOCH   SIZE  HASH
  1  1      640 B  0d72a730
  2  1      560 B  19cdc65a
  3  1      512 B  235f28b7

What you get

  • One order for everyone. Batches land whole, on consecutive positions, and every device reads the same sequence however many wrote at once.
  • Cursors that cannot lie. A cursor carries the hash chain's link at its position: after a restore from an old backup, a device is told its copy and the relay's have parted, instead of silently reading past the gap.
  • Epochs. When an app changes its format, an old version can no longer mix its entries in, and a reader stops before the first entry it cannot read.
  • Waiting, not polling. One request sleeps until any of the streams it watches moves, and carries positions only.
  • A schema that refuses. Gaps, older epochs and edited entries are refused by the database itself, not only by the code above it.

Install

$ cargo install efema-server

Or download the archive for your platform - Linux on x86_64 and ARM64, Windows, macOS on Apple silicon - from the latest release.

Then efema-server serve, and put it behind a reverse proxy before anything outside the machine talks to it: Getting started.

Documentation

The model, the threat model, every command and the protocol: lacodda.github.io/efema.

Status

0.1.0 is the relay and its wire format, efema-proto. It has no access control and no TLS of its own yet: run it on loopback or a network you trust. The Rust client library, efema, comes in 0.2.0 - with sealing built in, so there is no way to send an entry unsealed. Changes: CHANGELOG.

Contributing

The gate, the layout and how commits are written: CONTRIBUTING.md.

License

MIT (c) Kirill Lakhtachev