kadence

How it works

Events on disk, state folded on read, and why nothing is ever rewritten.

One event per command

Every command appends one file. Move a task, log an hour, close a sprint — each becomes an event, and no event is ever edited or deleted.

.kadence/
|- state.json          derived cache — gitignored, safe to delete
`- events/
   |- archive/         compacted history, one file per month
   `- 2026-09/         recent events, one file each

state.json is a cache. Deleting it changes nothing: the state is folded from the journal on the next read.

Why events and not files

Every other tool that keeps work in a repository keeps state — a task file, a row in a database, the current spec. State has three failure modes.

It drifts. A spec written on Monday and edited by an AI agent on Thursday no longer says what happened. An event cannot drift: it records that something occurred, not what is currently true.

It conflicts. Two people editing one task on two branches is a merge conflict in every file-based tracker. Here it is not, by construction: append-only, one file per event, so two branches write two different files.

It forgets. Rewriting a task file destroys the previous version. The journal keeps every step, which is why "how did we get here" has an answer at all.

Ordering

Event order comes from the ULID in the filename, never from a timestamp field. Clocks disagree between machines; ids do not. The same events fold to the same state whatever order the files are read in.

What identity buys

The id of a task is its ULID. KAD-4 is a label, derived while folding by ULID order, and never stored in an event — which is why two branches can each create a task without colliding on a number, and why grep -r "KAD-3" over the journal finds nothing. The tool is the bridge from the name a human uses to the data, not a convenience layer over the files.

Conflicts are surfaced, not rejected

A dependency cycle, a task in a removed status column, an event for a task that is not merged yet — all are kept and reported. Rejecting the later event would make the state depend on merge order, which would break the guarantee above.

Deletion

kadence task delete writes a task.deleted event, and the message says so plainly. Promising erasure in an append-only journal would be a lie.

On this page