For AI agents
The published JSON contract — every command, every field, every error code — and what one answer costs.
The contract
Every command speaks --json. Every response carries schema: "kadence/v1".
stdout is JSON and nothing else; warnings go to stderr, so a pipe stays clean.
kadence board --json
kadence task show KAD-1 --json
KADENCE_SOURCE=agent kadence task move KAD-1 in_progressKADENCE_SOURCE=agent records who acted, so the history distinguishes a person
from an AI agent afterwards. Without it an event counts as human — kadence does
not guess.
Ask the tool what it does
The contract is not prose you have to trust:
kadence schema --jsonIt returns every command, the fields you can rely on, every error code, the exit codes and the environment variables — about 4 KB, and it works outside a repository, because an agent asks what a tool does before it has a project to ask about.
Three tests keep it honest: the published error codes must equal the ones the code can emit, every field it promises must appear in real command output, and every command it names must actually run. Renaming a field fails the build instead of breaking consumers quietly.
What the promise covers
The contract is deliberately narrower than the output. Fields kadence emits
but does not list may still change; what is listed is a promise. Within
kadence/v1, fields and codes may be added — never renamed, never removed.
What one call returns
{"schema":"kadence/v1","ok":true,"task":{
"label":"KAD-1","status":"in_review","estimate":3,
"loggedHours":4.5,"blockedBy":["01M1PN2NVGQFD…"],
"comments":[{"author":"ana","ts":"2026-09-02T09:14:00Z",
"text":"The redirect drops the cookie, not the session."}],
"history":[
{"type":"task.created","actor":"ana","ts":"2026-09-01T10:02:00Z"},
{"type":"task.moved","actor":"ana","ts":"2026-09-01T14:40:00Z",
"data":{"from":"backlog","to":"in_progress"}},
{"type":"task.blocked_by_added","actor":"agent","ts":"2026-09-02T09:20:00Z"}
]}}That is the whole state of a piece of work in one call — no server to ask, no context to rebuild.
And it stays one call. That answer is 948 bytes 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 in Probe C.
The reasoning, not just the state
task show --json carries decisions and docs; both are always arrays, so
an agent never has to branch on their absence.
kadence decision list --json # what still holds
kadence decision list --all --jsondecision list returns decisions in force. A reversed one is only in the
--all history, because an agent quoting a superseded reason as current is worse
than one with no memory at all. See Recording why.
Each decision also says who wrote it: source is "human" or "agent", from
KADENCE_SOURCE when the event was recorded. An agent reading the reasoning can
tell whether a person made the call or a previous session of an agent did.
Ask for the fields you read
A board of a thousand tasks is 803 KB in full, because every task is emitted in full in every column. Narrow it:
kadence board --json --fields label,status,assignee
kadence task list --json --fields label,status,estimateOn a 200-task board that is 130,799 bytes down to 11,015. An unknown name fails
with unknown_field and the list of what exists — the selectable set is also in
kadence schema --json.
When a call fails
A failed --json call is machine-readable too:
{"schema":"kadence/v1","ok":false,"error":{
"code":"unknown_status",
"message":"Unknown status \"nonsense\".",
"received":"nonsense",
"allowed":["backlog","todo","in_progress","blocked","in_review","done","cancelled"]}}code comes from a closed list: not_a_repository, not_initialised,
no_git_identity, task_not_found, sprint_not_found, unknown_status,
unknown_type, unknown_priority, unknown_field, invalid_argument.
A hint naming a next command is added where one helps. allowed is there whenever the valid set is knowable, and it matters most for
statuses: they are configured per project, so no documentation can tell an
agent what yours are. The agent fixes its own input in one step.
There is deliberately no retryable field. With no network and no lock, the
same input always fails the same way — the field would be a constant dressed up
as information. A test asserts its absence so it does not get added absently.
Exit codes: 0 success, 1 the command understood you and could not comply,
2 bad arguments and nothing was attempted.
No bridge required
init writes the kadence section into both AGENTS.md and CLAUDE.md.
AGENTS.md is the cross-tool convention that 30+ agents read; Claude Code loads
CLAUDE.md and does not read AGENTS.md, so a repository carrying only the
first is invisible to the largest agent audience. Whatever a human wrote in
either file is left untouched, and a repeat init does not duplicate the
section. The reasoning is in ADR-009.
There is no server to run, no token to issue, and no network call to make.
An MCP wrapper stays on the roadmap as an optional package. We measured it: about 700 tokens a session over the instruction-file path — not the saving the industry benchmarks suggest, because kadence is thirteen short commands and not a server exposing dozens of tools. The reasons to keep it optional are that it works for agents with no MCP client at all, and that the core keeps zero runtime dependencies.
Why the CLI is the only bridge
An agent knows a task as KAD-3, because that is what humans write in commits
and pull requests. Searching the journal for it returns nothing:
$ grep -rl "KAD-3" .kadence/events/
$ (no matches)Identity is the ULID; KAD-N is a label derived while folding and never stored
in an event. So an agent holding a human's reference has two options: fold the
whole journal itself, or make one call. That is not an optimisation — it falls
out of a decision made for correctness.
Bulk is all or nothing
kadence task move KAD-1,KAD-2 doneEither both move or nothing changes. A typo does not leave half a board.