14 min read essay philosophy protocol

Tree Rings

A tree never edits a ring: growth accretes on the outside, and the whole history stays legible in the grain. Rich Hickey has spent two decades arguing software should be built the same way — values that accrete, not places that overwrite. On Effective Programs and its pyramid of what actually costs money, Simple Made Easy, the epochal time model — what taking the middle of that pyramid seriously looks like in a working protocol, and the three places we deliberately depart from the hammock.

Cut a tree and you can read its whole life. Every year the trunk lays a new ring on the outside — wide in the wet years, tight in the droughts, scarred where the fire came through — and the years already laid down do not move. A tree never goes back to fix 1987. There is a science built on exactly this property: dendrochronologists have cross-dated overlapping ring patterns from living trees back through beams in medieval barns to sub-fossil oaks in peat bogs, and assembled an unbroken year-by-year record reaching thousands of years into the past. The record is trustworthy for one reason. The medium only ever adds. Growth on the outside; history, legible, within.

Software, mostly, is not built like this. Software is built like a whiteboard: to record something new, we erase something old. A database row is a place; an update walks to the place and destroys what it finds there. Rich Hickey — the creator of the Clojure programming language, and the closest thing our field has to a working philosopher — has spent two decades arguing that this is not a detail. It is the load-bearing mistake. He gave it a deliberately deflating name, place-oriented programming, and pointed out that its only real justification was that 1970s memory and disk were desperately small. The constraint died; the habit survived. New information does not need to replace old information. That is just what tiny computers forced us to do, and we kept doing it after the computers stopped being tiny.

This essay is about Hickey's argument and one attempt — ours — to take it literally in a protocol. The occasion is a talk he gave in 2017, at the conference marking Clojure's tenth birthday, called Effective Programs. It is the closest thing the corpus has to a summing-up, and near its centre there is a slide we have never stopped thinking about: a ranked list of everything that goes wrong in software, ordered by what it actually costs.

The pyramid of what actually hurts

The talk opens with a question most language designers skip: what are programs for? Hickey's answer is that programming is about making computers effective in the world — and that most of the leverage comes not from computation but from information: from accumulating facts about what happened and extracting predictive power from them. The programs that do this — the ones most of us are paid to build and run — he calls situated. A situated program runs for years, not milliseconds. It consumes and produces information whose meaning it does not control. It talks to other systems that will not upgrade on its schedule, absorbs requirements of genuinely dumbfounding arbitrariness (his example, from a radio-scheduling system he built early in his career: the station plays two songs by the same artist back-to-back on Tuesdays), and is expected to keep working while the world changes around it. A compiler gets to define its whole universe. A situated program lives in ours.

Then comes the slide. Hickey lists the problems of programming and prices them, most expensive at the top. At the summit sit domain complexity and misconceptions — the problem being genuinely hard, and you misunderstanding it. These dominate everything, and no language feature reaches them; by his estimate there is roughly an order-of-magnitude drop in cost before the first problem a language could even address. At the bottom sit the trivia: typos, silly inconsistencies, passing the wrong type — real, but cheap, caught earliest, closest to the keyboard. And in between lies the band where design actually pays: mutable places. Tangled couplings. Information forced through rigid class shapes. Names that only mean something inside one program's walls. Concurrency. Distribution. His charge, delivered mildly and landing heavily, is that our industry's most celebrated tooling concentrates on the cheap bottom of the pyramid — while the middle, where systems actually die, goes comparatively unserved. Clojure was his attempt to serve the middle.

misconceptions · domain complexity

$$$$$ — no language can help

the leverage band — and what a protocol can do about it

  • place-oriented programming a signed, append-only change log — nothing is ever overwritten
  • information handling one open-map node shape; schemas are lenses, not cages
  • brittleness & coupling one merge rule, derived everywhere, pinned by conformance vectors
  • parochial names & types namespaced schema IRIs, authority-scoped extension keys, DIDs
  • concurrency & distribution immutable facts + a pure fold; devices converge without a referee

typos · inconsistencies · type mismatches

$ — cheapest, caught earliest, where most tooling energy goes

The severity ranking from Effective Programs, drawn as the pyramid it implies — dearest at the top, cheapest at the bottom, and the middle band where design leverage actually lives. The right-hand column is our homework, marked in the sections below.

Clojure serves that middle band at the level of a language. xNet is a bet that the same band can be served at the level of a system — a protocol for keeping, merging, and sharing a workspace full of other people's information, which is about as situated as programs get: always on, many devices, many authors, nobody in charge of the whole world. So let us descend the middle of the pyramid, problem by problem, and show receipts.

Facts do not take editing

Start with place-oriented programming, because everything else in xNet follows from refusing it. Hickey's alternative to the mutable place is the value — immutable, and meaning what it says without needing anyone's permission or interface documentation to read it. His favourite examples are disarming: your source control system never edits a commit; your logs are never rewritten; your accountant does not erase the ledger when the balance changes, and it is mildly alarming how many industries consider that basic hygiene while software considers it exotic. A fact is something that happened. You cannot update the past; you can only learn more of it.

In xNet, the primitive of all writing is built exactly on this refusal. Every edit — a keystroke batch, a task ticked, a cell changed — becomes a change: a small record naming the node it touches and the properties it sets. Before it goes anywhere, it is hashed and signed:

packages/sync/src/change.ts
// packages/sync/src/change.ts — an edit becomes a fact: hash, then sign.
export function signChange<T>(unsigned: UnsignedChange<T>, signingKey: Uint8Array): Change<T> {
  const hash = computeChangeHash(unsigned)  // BLAKE3 over canonical JSON
  const signature = sign(new TextEncoder().encode(hash), signingKey)
  return { ...unsigned, hash, signature }
}
An edit becomes a fact. The hash is computed over canonical JSON with BLAKE3; the author's Ed25519 key signs the hash; the change carries its parent's hash, so history is a chain. There is no other write path in the protocol.

Note what this object cannot do. It cannot be altered — the hash would break. It cannot be disowned — the signature names its author. And it cannot be overwritten, because nothing in the system overwrites: the log of these changes is append-only, and the current state of your workspace is not a place being serviced by updates but a view derived from the log, the way the shape of a trunk is derived from its rings. We have walked through this log line-by-line before; what we want to say here is simply where the idea sits. It is the top of Hickey's middle band, implemented as a wire format.

Just use maps

Next: information. The strangest-sounding claim in Effective Programs, until it clicks, is that mainstream languages are bad at information specifically. The class — the proud unit of object-oriented design — makes you declare, in advance, in one program's vocabulary, what a person or an invoice is; the names get compiled away, and two systems with slightly different declarations cannot use each other's data without a diplomatic incident. Hickey's word for this is parochialism: types and names that only work inside their own parish. His prescription is older than it sounds — represent information as plain associative data, maps of self-describing keys, the way RDF represents everything as subject-predicate-object so that facts from strangers can merge at all. Clojure programs are famously maps all the way down. So is xNet:

packages/data/src/schema/node.ts
// packages/data/src/schema/node.ts — the universal container type.
export interface Node {
  id: string
  schemaId: SchemaIRI   // e.g. xnet://xnet.fyi/Task@2.0.0
  createdAt: number
  createdBy: DID
  /** All other fields are schema-defined */
  [key: string]: unknown
}
Every piece of data in xNet — a page, a task, a contact, a message — is this one shape: four universal fields, then an open map. Schemas validate and describe; they do not own.

Four fields are universal; everything else is keys. A schema in xNet is a lens over this open map — it validates, describes, and gives your editor something to autocomplete — but it does not seal the map shut. Anyone can lay extra keys on any node under an extension prefix that names their authority, and the protocol will sync and merge them like any other property; a later owner can promote a well-loved extension into the schema proper. And the names are anti-parochial by construction: a schema is not Task, a word that means something different in every codebase on earth, but xnet://xnet.fyi/Task@2.0.0 — a namespaced, versioned identifier anyone can mint under their own domain or DID, and no one can collide with. Authors, likewise, are not row IDs in somebody's users table; they are DIDs, names that keep working when the data walks out of the building. This is what it looks like to let information leave the parish: the map is open, and every key knows where it came from.

One braid at a time

Six years before Effective Programs, Hickey gave the talk most working programmers know him for, Simple Made Easy — the one that rescued the word simple from meaning "whatever I already know". Simple, by etymology, is one fold: a thing is simple when each concern stands alone, and complex when concerns are braided together — for which he exhumed the perfect archaic verb, complect. Easy, by contrast, just means near at hand — and the talk's lasting warning is that these are different axes. Tools that make things easy to write can braid horrors into what you have written. State complects value with time. Objects complect state with identity. And the cost lands later, in the artefact you run and maintain, not in the construct you enjoyed typing. His verdict on our profession's due diligence still stings: "programmers know the benefits of everything and the tradeoffs of nothing."

We can offer a confession-shaped receipt for this one. The heart of any multi-device protocol is its merge rule — ours is per-property last-writer-wins over the change log. For a while, that rule lived in three places: once in the store that applies changes, once in the SQLite layer's conflict guards, once in the hub's ordering. Three implementations of one idea, each drifting on its own schedule, on the invariant the whole system's convergence depends on. That is complexity in Hickey's exact sense — not a hard algorithm, but one fold braided through three modules. The fix was not cleverness. It was un-braiding:

packages/core/src/lww.ts
// packages/core/src/lww.ts — "The ONE Last-Write-Wins ordering for xNet".
// Previously re-implemented in three places; every implementation now
// derives from this module, and a conformance suite pins them equal.
export function compareLwwStamps(a: LwwStamp, b: LwwStamp): number {
  if (a.lamport !== b.lamport) return a.lamport - b.lamport
  if (a.wallTime !== b.wallTime) return a.wallTime - b.wallTime
  // … then a grinding-resistant tiebreak key, then the author DID
}
The merge rule, un-braided: one module owns the ordering — Lamport time, then wall time, then a grinding-resistant tiebreak — and the store, the SQLite guards, and the hub all derive from it. Golden-vector conformance tests pin every implementation to the same answers.

The rule itself stayed three lines. The work was making it one — a single fold, projected into JavaScript and SQL from one source, with a conformance suite standing guard against re-braiding. Simplicity, Hickey keeps insisting, is a choice, and mostly an unglamorous one; nothing about that diff would demo well. But it is the difference between a castle of bricks and a castle of knitting, and you find out which one you built on the day you need to change it.

The whole workspace is a value

The deepest idea in the corpus is the one Hickey built a database out of. If facts are immutable and accrete, then time stops being an enemy and becomes an index. He calls it the epochal time model: an identity — you, a document, a company — is not a mutable thing but a succession of values, each one complete, each one permanent, like — his image, and this essay's title — the rings of a tree. Datomic, his database, made this concrete in a way that still feels like a magic trick: you do not send queries to the database; you ask for the database, as a value, as of any moment, and query it locally at your leisure. The past is not a backup tape. It is an argument you can pass.

xNet's version of the trick is called a frontier: a map from node to change-hash that pins a moment in the log — by content, never by clock, so it either means exactly what it says or fails honestly. Materialising a workspace at a frontier is a pure fold: start empty, apply changes in order, out comes the state as of that moment. Everything the app calls history is this one primitive wearing three hats. A read-only frontier is the timeline scrubber. A named, pinned frontier is a checkpoint. A frontier you fork and keep writing to is a draft — and because your draft and the shared head are just two successions of the same values, comparing or merging them needs no new machinery at all. Undo, blame, diff, "what did this look like in March?" — none of these are features in xNet. They are queries against rings the tree was growing anyway.

c₁ c₂ c₃ c₄ c₅ the log — signed facts, hash-chained, append-only

↓  fold(state, change) → state′  — one pure function, no coordination

value as of c₂

a pinned frontier — a checkpoint, or the scrubber paused in March

value as of c₄, forked

a writable frontier — a draft, growing rings of its own

value as of c₅

the head — what your screen shows, kept hot in SQLite

The epochal time model, as shipped: one append-only log of facts, one pure fold, and every "feature" of history — checkpoints, drafts, the current state — is just the same fold stopped at a different ring.

Once you see the pattern you find it everywhere in the design, and the repetition is the point. A change is a value — hashed, signed, done. A moment is a value — a frontier of hashes. A query is a value — a serialisable expression any storage engine can compile. An error is a value — a tagged record you can match on, not a string you grep. Even a schema is a value, versioned in its own name, with lenses to carry data between versions. Hickey's pitch for values — that they can be shared, cached, compared, sent across a wire, and reproduced without anyone coordinating — is not an aesthetic in a sync protocol. It is the entire mechanism. Two devices converge because they are folding the same immutable facts through the same pure function; there is nothing else to agree on.

Three departures

If the essay ended there it would be fan mail, and Hickey's own method forbids that — the benefits of everything, remember, and the tradeoffs of nothing. So here is where xNet leaves the hammock, on purpose, three times.

We use static types. Everywhere. xNet is TypeScript from kernel to UI, and its author would not give up the tooling — the renames, the exhaustive switches, the autocomplete — for any philosophy. What we kept from Hickey's type-scepticism is the boundary discipline: his real charge is that elaborate types mostly polish the cheap bottom of the pyramid, and become actively parochial at system boundaries, where your beautiful Invoice<Paid> is a private language nobody else speaks. So in xNet, types never cross the wire. The protocol's contract is canonical JSON, open maps, and namespaced names; the TypeScript is scaffolding inside our walls, and a peer written in Rust or Clojure owes it nothing. Types as tooling, data as truth — we think of it as agreeing with the talk's conclusion while declining its temperament.

There is one black box in the tree. Prose is the one domain where per-property last-writer-wins is the wrong physics — two people typing in the same paragraph need character-level merging — so document bodies and canvas scenes use Yjs, a CRDT whose binary updates are decidedly not semantically transparent values. We drew a hard lane around it: the log for facts, the CRDT for prose, and the CRDT's blobs ride inside signed log changes like freight — addressed, attributed, and ordered by the same machinery, opaque only within. A purist would note the interior of that box has no rings. Correct. It is the tradeoff we can name.

We prune. Hickey's soaring line about the new economics of storage is that we can finally afford to remember everything — and servers can. Phones, whose owners also deserve their whole workspace, cannot always. So xNet ships history-horizon policies that can compact the deep past on constrained devices, which is a real departure from the accrete-forever ethos. What we refused to give up is honesty at the horizon: because frontiers address history by hash, a scrub past what a device retains cannot silently show you a wrong answer — it fails, loudly, and checkpoints pin what they reference so the moments someone chose to name survive compaction. A tree with a hollow core, which still never lies about a ring it has.

The hammock at the top

Which leaves the summit of the pyramid — misconceptions, the problems that cost the most and that no data model, ours included, can touch. Hickey's remedy for those is not in any repository. In the most beloved talk of the corpus he recommends, with a completely straight face, the hammock: state the problem in writing, feed your head with everything known about it, and then stop typing — because the waking mind is a good critic and a mediocre designer, and the background mind, which does the real synthesis, only takes an agenda from what you loaded before you stepped away. Solve problems, he keeps saying, not features. The cheapest bug is the one designed out before the first keystroke.

The closest thing xNet has to an engineering method is an attempt to practise this. Nothing sizeable lands in the codebase without a numbered exploration first — a written statement of the problem, the prior art, the options, the tradeoffs, a recommendation, and a checklist that later gets marked against reality. At the time of writing there are three hundred and forty-eight of them, and the one that specified this essay spent a while in the hammock deciding what this essay should refuse to claim. Some explorations conclude build nothing; those are the best value in the folder. This, we have come to believe, is the real substance of Hickey-style design — not parentheses, not any particular database, but the unfashionable conviction that understanding is a deliverable, and that the pyramid is climbed from the top.

A tree does not hurry, and it does not revise. It commits, once a year, in public, and keeps every commitment where anyone with a saw and some patience can audit the whole series. That is the standard the talks set for software that intends to be around in forty years and legible when it gets there: growth on the outside; history, legible, within. If you want to hold a workspace built on that bet: the app is free, offline, and private. Read the commitments, or build on the protocol — the wire format is open maps and signed facts, so you do not need our permission, which is rather the point. And watch the talk below, ideally twice. It is ten years of thinking, compressed to seventy minutes, about the only questions in software that compound.


Sources

This is an independent essay about Rich Hickey's public talks. He has no affiliation with xNet and has not reviewed or endorsed this essay or this project. Ideas are paraphrased from the talks linked above; the one direct quotation is brief, attributed, and used as commentary. All artwork here is original, and this page loads nothing third-party.