kadence
Your team and your AI agents work from the same context — it lives in your repo and remembers what the code cannot.
npm install -g kadence
kadence init
No server. No account. No network. Tasks, their whole history and the time they took are files next to your code, and they move with your branches.
The context your code cannot hold
Your code says what exists. git log says when it changed. Neither says
what was tried and abandoned, why a task is blocked, or what the team agreed on
Tuesday.
That gap costs a human a few minutes. It costs an AI agent the entire session: every new one starts from scratch, re-reads the same files and asks the same questions you answered yesterday.
kadence keeps that missing layer as an append-only journal — one file per event, committed with the code:
$ kadence task show KAD-1 --json
{
"schema": "kadence/v1",
"label": "KAD-1",
"title": "Fix login",
"status": "in_review",
"loggedHours": 4.5,
"blockedBy": ["KAD-7"],
"comments": [
{ "at": "2026-09-02T09:14:00Z", "by": "ana",
"text": "Session cookie is fine — the redirect drops it." }
],
"history": [
{ "at": "2026-09-01T10:02:00Z", "by": "ana", "type": "task.created" },
{ "at": "2026-09-01T14:40:00Z", "by": "ana", "type": "task.moved", "to": "in_progress" },
{ "at": "2026-09-02T09:20:00Z", "by": "agent","type": "task.blocked_by_added" }
]
}
That is the whole state of a piece of work, in one call, with no server to ask
and no context to rebuild. A human reads it in kadence task show. An AI agent
reads the same thing as JSON.
And it stays one call. That answer stays under a kilobyte whether the project holds ten tasks or a thousand — while the journal behind it grows from 5 KB to 528 KB. The cost of asking does not grow with the history that makes the answer worth having. Measured at 948 bytes in 0.2; 982 bytes at 0.4, after claims and acceptance criteria joined every record.
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, and all three are why kadence stores events instead.
It drifts. A spec written on Monday and edited by an agent on Thursday no longer says what actually 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: the journal is append-only, one file per event.
It forgets. Rewriting a task file destroys the previous version. The journal keeps every step, so «how did we get here» has an answer.
State is still there when you want it — it is folded from the journal on read, which is why the board cannot drift from the journal. No one maintains a column by hand, so no column can be stale in the way a task file can.
What that does not buy you. A journal records what was written to it. If a
piece of work stops being touched, the board stays perfectly accurate and
perfectly uninformative about it — the same way git log is honest about a
branch nobody pushed to. kadence removes the drift between what is shown and
what was recorded. It cannot remove the drift between what was recorded and
what is actually happening; that still takes someone looking.
Why the merge claim holds
We measured it before building on it.
Across 8,396 merge commits in 130 public repositories using file-based
trackers, conflicts in task files hit 15% of repositories — and 89% are
CONFLICT (content), the exact type an append-only journal removes.
Then the other direction: three people editing one task on three branches, merged in every order. Zero conflicts, every author preserved, identical final state. That is an integration test, not a claim.
The measurement itself is a working paper and is not published (see
.gitignore); the mechanism it measures is proved here by
test/integration/merge.test.ts.
In practice
The first ten minutes, for the person who owns CLAUDE.md:
kadence init # .kadence/, a short section in AGENTS.md and CLAUDE.md, and two
# Claude Code hooks: prime at session start, a journal search per prompt
kadence task add "Fix login" --type bug --priority high
kadence decision add "Keep sessions in Redis" \
--why "Revocation must be instant" --rejected "JWT: cannot revoke before expiry"
Open a new agent session. The hook runs kadence prime, and the agent starts
knowing what is open, what is ready and DEC-1 with its reason — ask it how
sessions should be stored and it answers from the journal, not from a guess.
git add .kadence AGENTS.md CLAUDE.md .claude/settings.json .gitignore
git commit -m "Keep the team's work next to the code"
kadence never commits for you. Once that commit is pushed, the journal is the team's, not yours.
Adding a teammate
- They install it:
npm install -g kadence(Node 20 or newer). Nothing is configured per person. - They pull. Their agent reads the same section in
CLAUDE.mdorAGENTS.mdand, through the hook, runskadence primein its first session. On a machine where kadence is not installed yet, the hook prints one line asking for the install instead of failing. - One identity per person. Authorship is
git config user.email. The same person on two machines with two addresses reads as two people — set the same address everywhere. - Agents say so. Put
KADENCE_SOURCE=agentin the agent's environment; its writes then carry[agent]next to the same email, so a person and their agent stay distinguishable.
What you should see afterwards: kadence task show KAD-1 lists their comment,
note or move with their address. That second author is the moment the journal
starts doing its job.
When several people — or several agents — work the same board:
kadence ready # open, unblocked, nobody else's
kadence task claim # take the top of that list, in one step
kadence note "Redirect drops the cookie, not the session" --task KAD-1
kadence task release KAD-1
There is no lock, and the message says so. Two machines can each claim before
either pushes; the merge keeps both claims and the task reads contested with
both names. Refusing the second one would make the owner depend on which branch
merged first, and that is the property the whole design rests on.
The board, when you want to look at it:
$ kadence ui
kadence 6 open · 2 ready · 1 decision in force
+- backlog (2) -------++- in_progress (1) --++- in_review (1) ----++- done (3) ---------+
| ^# KAD-1 Auth epic || . KAD-4 Tokens @dev||!! KAD-7 Crash [] || v KAD-2 Export |
| * KAD-3 Login form || || || v KAD-5 Docs |
+---------------------++--------------------++--------------------++--------------------+
arrows move enter details m status a assign e edit s sprint R ready b branch q quit
Keyboard, mouse, drag between columns, every field editable in place. It calls the same commands the CLI does, so the two can never disagree.
And what the branch you are on is actually about:
kadence task list --branch
Nothing is stored for that: which tasks belong to a branch lives in git's history and is read when you ask. It narrows the answer between three and twenty times, measured on real board sizes.
Also in the box
Sprints (sprint create/close, with points and hours derived from moves),
milestones, acceptance criteria and a definition of done, templates, custom
columns, reports folded from the same journal (report flow, cfd,
attention; burndown, velocity and workload are in the repository and not yet
on npm), a self-contained HTML or Markdown export, compact for long journals,
and shell completion. None of it is required, and none of it is the point —
it is what the journal happens to know. --help on each command is the
reference, and kadence doc list is what the journal knows about itself.
Removing kadence
Everything init touched, so you can undo it by hand:
| What | Where |
|---|---|
| The journal | .kadence/ — yours to keep or delete; kadence never deletes it |
| The agent section | between <!-- kadence:begin --> and <!-- kadence:end --> in AGENTS.md and CLAUDE.md |
The hooks (unless --no-hooks) |
the SessionStart entry that runs kadence prime and the UserPromptSubmit entry that runs kadence hook prompt, in .claude/settings.json |
| The cache entry | the .kadence/state.json line in .gitignore |
Then npm uninstall -g kadence. Nothing lives outside the repository and your
global node_modules.
For agents
Files first. Every command speaks --json, every response carries
schema: "kadence/v1", stdout is JSON and nothing else, warnings go to stderr.
Start a session with one command:
kadence prime # active sprint, your work, what is ready, decisions in force, recent notes
kadence ready --json # seven fields per task, not the whole record
prime is held to forty lines and three kilobytes by a test, because it is paid
for on every turn that follows. kadence init adds it as a SessionStart hook,
and adds a UserPromptSubmit hook that searches the journal for each prompt
and puts up to three records next to it. Both are on by default because agents
do not reach for search on their own (1 in 10 measured); --no-hooks leaves
.claude/settings.json alone. The journal is searched in the words it is
written in: a prompt in another language finds nothing.
kadence board --json
kadence task show KAD-1 --json # full history and comments
KADENCE_SOURCE=agent kadence task move KAD-1 in_progress
init writes that guide into both AGENTS.md and CLAUDE.md — the first is the
cross-tool convention, the second is what Claude Code actually reads. Whatever a
human wrote in either is left alone. No MCP server to run, no token to issue, no
network call to make.
The contract is not a promise you have to take on trust:
kadence schema --json # every command, every field, every error code
A failure carries error.code and, where the valid set is knowable, allowed —
which matters most for statuses, because they are configured per project and no
documentation can tell an agent what yours are.
There is no MCP wrapper, and one gets built only as an optional package, when someone who cannot run a CLI asks for it: it costs about 700 tokens a session over the CLI path — we measured it, and it is not the saving the industry benchmarks suggest — it would be a second way to say the same thing, and it would not work for agents that have no MCP client at all.
Ask for only what you need — a board of a thousand tasks is 855 KB in full,
275 KB with --summary, and 104 KB with the three fields an agent actually
reads:
kadence board --json --summary
kadence board --json --fields label,status,assignee
And the part the code cannot hold — why:
kadence decision add "Use ULIDs" --why "Clocks disagree between machines" \
--rejected "Auto-increment: collides across branches" --task KAD-1
When a decision stops being true you supersede it rather than edit it, and that
is one event — the backward link is derived, so the two directions cannot
fall out of step. In a file-based tool it is two edits, and teams reliably make
one; that is how a reversed decision keeps looking authoritative.
decision list returns what is still in force, --all adds the history, and
task show --json carries the decisions made about that task.
Something learned that was never a choice is a note, not a decision — no --why,
no number, and prime shows the latest:
kadence note "The staging clock runs 40 s behind" --task KAD-1
Documentation lives in the journal too. How something works now, revised as
it changes — an entity, DOC-N, not a filename an agent has to guess:
kadence doc add "How auth works" --file notes/auth.md --task KAD-1
kadence doc show DOC-1 --json
A revision names the revisions it was written on top of, so two people editing
the same version leave a conflict that is reported and settled by writing text,
never a silent overwrite. Agents are handed it without searching: task show
carries documentation[] and prime names the documentation of the work you
hold — both without bodies, so the body arrives only when asked for.
A markdown file that already exists is still linked, not swallowed:
kadence task doc add KAD-1 docs/design.md records the link, which is the part
git cannot express.
Bulk works everywhere and is all or nothing: kadence task move KAD-1,KAD-2 done
either moves both or changes nothing. A typo does not leave half a board.
What it costs you
| Install | 114 KB packed — 344 KB of kadence, plus 1.8 MB of blessed |
| Startup | 70 ms (--version, 0.8.0, one laptop) |
| 10,000 events | 21 ms cold with a compacted archive, 12 ms warm — 199 ms cold if every event is still a separate file |
| Journal on disk | under 5 MB |
| One task, as an agent reads it | 982 bytes, or 304 with --summary — the same however long the task has been worked on |
These are tests. They fail the build on regression, which is why they are still true.
Honest status
Verified. The merge thesis, on real git branches. Performance and size, by tests that fail if they regress. That the conflict problem exists in the wild — measured, not assumed. 1,188 tests in the repository today, including an end-to-end run through the installed binary.
Not verified. That teams and their AI agents actually lose enough context to want
this. The bet rests on reasoning and on the industry naming the problem out
loud — not on our own users. That research, Probe B, is
designed and not yet run: as of
2026-09-30, zero conversations and no external users. What happens next, and
on which dates, is recorded in this repository's own journal — kadence decision list and kadence note list.
Not built, on purpose. An MCP package — only if someone who cannot run a CLI asks for it, not as an inevitability.
kadence context <task> was dropped: we measured what task show --json already
returns and it is the whole history of one piece of work, under a kilobyte,
constant.
The only thing left to add was a different format, and nobody has asked for one.
Known limits. Conflicts are real but rare: roughly one merge in two hundred.
Terminal interaction is covered by manual testing; only the key router is
unit-tested. Deleting several tasks by KAD-N in one loop removes the wrong
ones, because labels are derived and renumber as earlier tasks go — delete by
ULID, or one at a time. There is no task ac remove, so a board-wide Definition
of Done lands on every new task unless it is added with --no-dod.
How it works
.kadence/
|- state.json derived cache - gitignored, safe to delete
`- events/
|- archive/ compacted history, one file per month
`- 2026-09/ recent events, one file each
Every command appends one event. State is folded from the journal on read, so the board cannot drift from the journal — nothing is maintained by hand. Two branches writing at once produce two different files, and git merges them without a conflict by construction.
Design decisions, each recording what was measured and what would make us
revisit it, are in the journal that ships with this repository — kadence decision list, or kadence decision show DEC-3 for one in full. The longer
write-ups behind them are working papers and are kept out of git on purpose;
the decision, its reason and the alternatives that lost are in the events.
Contributing
npm install
npm test # 1,188 tests; builds dist/cli.js first
npm run typecheck
npm run build # dist/cli.js 135 KB, blessed kept external (82 KB at 0.4.1)
CLAUDE.md documents the invariants, the boundaries, and the decisions that
look arbitrary without their reasoning. Read it before changing the core.
Licence
MIT