A replicated, append-only store in Zig you embed like SQLite - from one process to a fleet, with no quorum, no sidecar, and optional TTL for rolling-window state.
The only prerequisite is a Zig 0.16 toolchain - its minimum version is pinned
in build.zig.zon; nothing else needs installing.
Run the node. One process is a complete journal; init writes the member
key and the genesis into a data directory, then append, read and head:
rm -rf ./data
zig build run -- init --dir ./data --journal main
zig-out/bin/coppiz append --dir ./data --journal main --payload "hello"
zig-out/bin/coppiz read --dir ./data --journal main
zig-out/bin/coppiz head --dir ./data --journal mainOr embed the library. The API is pre-1.0 - PRD
0005 pins the shape,
names will move. The SQLite case: a data directory, an allocator, an std.Io,
and a node:
const std = @import("std");
const coppiz = @import("coppiz");
const gpa = std.heap.page_allocator;
var io_state = std.Io.Threaded.init(gpa, .{});
const io = io_state.io();
const data_dir = try std.Io.Dir.cwd().openDir(io, "data/coppiz", .{ .iterate = true });
try coppiz.journal.init(gpa, io, data_dir, &.{}, "events", coppiz.journal.wallClock);
const node = try coppiz.journal.Node.open(gpa, io, data_dir, .{});
defer node.deinit();
const events = node.journalIdByName("events").?;
_ = try node.append(events, "hello coppiz", 0);
var found = false;
try node.readRange(events, null, null, false, false, &found, struct {
fn on(s: *bool, _: *const coppiz.journal.slot.Slot, _: ?*const coppiz.journal.entry.Entry) anyerror!void {
s.* = true;
}
}.on);In a cluster, a host writes through the loop of its own member -
cluster.ClusterNode.localAppend queues, forwards to the leader and blocks
until the slot folds back - and reads the same way
(cluster.ClusterNode.localReadRange runs the read through the loop), or
over the wire as a client to the local member, while the loop runs. The
four host shapes are examples/, built and tested by zig build examples.
zig build docs regenerates docs/configuration.md from the settings schema.
Read the design, in order, with cat docs/README.md. Everything below is
detail.
Working today: a single process is a complete journal (append, read, follow, restart), and a cluster replicates for real - the pure membership fold, election and epoch/merge rules, the node loop over the wire, and settings, TTL and checkpoint cleanup all in the chain. A host embeds the library and writes through its member's loop. Not yet: federation (PRD 0006), the first host integration, and the service API. The full narrative is in Status and where things are below, and what ships in what order in docs/ROADMAP.md.
What it is. coppiz is a replicated, append-only store for Zig programs: a library you link and give a data directory, with storage, replication, election and cleanup inside it - nothing to install or run beside it. Entries are signed, hash-chained, and replicated in full to every member of the group. One process is a complete, working store (the SQLite shape); a fleet needs no quorum, no odd member count, and no redeploy; and past one group the same code composes groups instead of growing one (ADR 0003, PRD 0006).
How it works, at a glance. The design rests on one separation and one rule (PRD 0001):
- Entry versus slot. An entry is what an author writes - immutable,
author-signed, identified by
(author, author_seq). A slot is where the journal put it -(epoch, seq), assigned and signed by the leader of that epoch, hash-chained to the previous slot. Content never changes; order can heal after a partition by re-slotting unchanged entries. - Everything the journal knows about itself is in the chain. Membership, leadership terms, settings, and cleanup are control entries in the same chain as data, validated by every member with the same pure rule and folded deterministically into state. That is what makes join order unspoofable (RFC 0002), settings impossible to disagree on (PRD 0004), and expiry deterministic across N clocks (PRD 0002).
- Writes go through the leader; reads are always local. Every member holds the group's journals in full, so a read never leaves the process:
client ─append─▶ local member ──forward──▶ leader ──(slot, entry)──▶ every member
│ unslotted queue │ assigns (epoch, seq), │ validates chain + signatures,
│ (durable) │ stamps slot_ts_ms, signs │ appends, folds
◀──────────────── read / follow: always local ─────────┘
- Leadership without quorum. There is no vote:
leader(mode, settings, members, liveness)is a pure function every member evaluates over its fold.seniority(earliest join - unspoofable, because join order is chain position), an operator'sconfiguredauthority list (name one of two members, or an odd subset of six), orcombined- all well-defined at 1, 2, and any even count, and switchable at runtime when the cluster allows it. A partition underseniorityyields a leader per side and a deterministic merge on heal (PRD 0003). - Scaling by recursion. A cluster is a group. Past
max_membersthe system grows by more groups, not a bigger one: groups elect with the same function, a journal is owned by one group and routed from the others, and cold segments can be stored k-of-m across groups (PRD 0006).
- Append-only entries, author-signed and hash-chained, replicated in full to every member of its group. No update, no delete; the only mutations are TTL expiry and an author marking its own entries stale, both opt-in per journal and off by default (ADR 0002, PRD 0002).
- TTL with policy: enforcement off, per entry, or for every entry in the journal; expiry marks stale or deletes; deletion is deterministic across members because it is itself a journal event (PRD 0002).
- Settings live in the journal, not in per-member config files, so two members cannot run different rules on one journal (PRD 0004).
- Batteries included: storage, replication, failure detection, election and cleanup are inside the library; no coordinator, daemon or broker beside it, at any size (ADR 0003).
- Embeddable first: a Zig library any host links, with the standalone
coppiznode built from the same tree for hosts that would rather talk to a process (PRD 0005, RFC 0001).
Any program that needs a durable, replicated, append-only record shared across processes or machines, and finds the existing answers don't fully cut it: the server-shaped stores (etcd, Postgres, NATS) want a cluster stood up first; the Raft stores (rqlite, dqlite) want a quorum and an odd member count, so two nodes can't elect; the gossip/CRDT stores converge but don't order. coppiz is for the gap between "write files and hope" and "run infrastructure" - slim at size 1, expandable by settings as members are added (ADR 0003). It is general-purpose: not built for any one host.
zig build test is today's merge gate (OQ 45): it
builds and runs the unit tests plus the analysis gates - formatting (zig fmt --check --ast-check), the 100-column cap, test registration and forced
declaration analysis, and gate-coverage completeness:
zig build testRun the analysis gates alone:
zig build lintShipped (2026-08-27): the single-member core, the pure cluster core, the cluster node loop, TTL cleanup end to end, and the embedded-host write path. One process is a complete journal - append, read, follow, restart - with settings, TTL and checkpoint cleanup in the chain.
On top of that sits
the cluster core of PRD 0003:
the pure membership fold, election and epoch/merge rules (driven by the
deterministic simulator, OQ 27), and the node
loop over the wire - failure detection, election → epoch, admission
(allowlist/open/prompt), and forward/broadcast/backfill, so real processes
replicate: coppiz serve runs a node, members join live, appends through a
follower replicate to the leader and back, and a healed partition merges
deterministically.
The leader's checkpoint cadence (PRD 0002 phases 4–5) keeps every member's payloads dropping at the same chain position.
A host embeds the library and
writes through its member's loop (cluster.ClusterNode.localAppend), and
examples/ - embed-single, embed-cluster, sidecar, short-process -
builds and tests each host shape (PRD 0005).
coppiz append/read/head talk to a serving node over the wire when the
data directory is locked (OQ 47); coppiz status,
coppiz members, coppiz doctor, coppiz settings set and coppiz admit
round out the CLI. The product is named coppiz
(ADR 0004).
The full design lives in docs/ - this table is the map; the detail is in
the records, not here.
| Path | What |
|---|---|
| docs/README.md | architecture overview and the documentation taxonomy |
| docs/prds/ | what each part is meant to be |
| docs/rfcs/ | decisions still open, with recommendations |
| docs/adrs/ | decisions made |
| docs/research/ | evidence, with read dates; open measurement questions live here too |
| docs/glossary.md | every term, defined once |
| docs/ROADMAP.md | what ships in what order |
| CHANGELOG.md, RELEASES.md | consumer-visible changes; version and release policy |
src/journal/, src/settings/, src/config/ |
the single-member core: codecs, chain, segments, store, schema, local config |
src/cluster/, src/sim/ |
membership, election, epochs, merge and the node loop (pure core + node.zig); the deterministic simulator |
examples/ |
one host per shape (embed-single, embed-cluster, sidecar, short-process) - built by zig build examples, each a test |
src/root.zig, src/main.zig |
the library; the node binary |
coppiz was founded from clanker's RFC 0019 ("the spine"), which surveyed seventeen candidates (stores and log/CRDT libraries alike), found no general-purpose Zig-native replicated store, and recorded the decision to build one as a standalone public project for anyone with the same problem. What that survey established, and what coppiz inherits from it, is in research 0001; one worked example of embedding the library is spelled out in PRD 0005.