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 eachstate.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.