Agents
katra was built to be written by an agent while it works, so the log records what happened rather than what someone reconstructed afterwards. The complete, harness-neutral contract is The Katra workflow. There are three ways into the same core, and they compose.
| Surface | Use it when |
|---|---|
| The CLI | The common surface for any agent that can run a shell command: planning, specs, entries, decisions, closure, and stamps. |
| MCP | A client that speaks the Model Context Protocol and would rather call a tool than shell out. |
| The skill + hooks | Optional Claude Code reminders, memory ingest, and a commit gate installed by katra setup. |
The problem this solves
An agent that logs its work at the end writes a summary of a diff. That is the one thing the diff already tells you. What is lost is everything that happened before the final state: the approach that failed, the measurement that changed the plan, the screenshot of the bug.
So katra’s workflow is built around a single rule — the draft exists while the work does. The CLI makes that portable; hooks can make it harder to forget.
Portable setup
katra init --install-hook
That creates the store and installs Git auto-stamp without writing harness
configuration. An existing Katra can use katra hook install.
Claude Code setup
katra setup # with the commit gate
katra setup --no-gate # nudges only
Idempotent, and safe to re-run — which you should do after upgrading katra,
because it is how a project picks up new hook wiring. It installs the skill into
.claude/skills/katra/, merges the hooks into .claude/settings.json (leaving
any hooks it does not own alone), installs the git auto-stamp hook, and
registers the project with the hub.
The hooks
Seven, all routed through one adapter, katra agent-hook <event>. Every one is
fail-open: a hook that errors, or a directory that is not a katra, allows
the operation.
| Event | Hook | What it does |
|---|---|---|
SessionStart |
agent-hook session-start |
Rescans memory and prints a one-line status: unresolved memory, in-flight changes needing reconciliation, or the active draft’s slug. |
UserPromptSubmit |
agent-hook turn-start |
Records a turn boundary, so a unit of work can be scoped to one turn. |
PostToolUse (Edit\|Write) |
agent-hook post-tool |
Records the touched path and incrementally rescans memory. Cheap. |
Stop |
agent-hook stop |
The gate. Blocks the turn ending if authored code changed and nothing declared what it was for. |
PreToolUse (Bash) |
agent-hook pre-commit |
The commit gate. Blocks a git commit whose staged code has no reconciliation receipt. |
PreCompact |
agent-hook snapshot --event pre-compact |
Snapshots state before the context is compacted. |
SessionEnd |
agent-hook snapshot --event session-end |
The same at teardown. Async, so it never delays exit. |
agent-hook pre-commit blocks by exiting 2 — the Claude Code PreToolUse
convention for stopping a tool call — not the plain 0/1 every other katra
command uses. See CLI reference: Exit codes.
What the commit gate covers
Coverage is answered per path, not per set. A receipt declaring
{a.go, b.go} covers a commit of {a.go} — committing part of declared work is
still declared work, so an ordinary split commit is not blocked. Content is
still checked: edit a file after declaring it and the gate asks again, because
the per-path key is that path’s change record (op, mode, HEAD blob, new blob),
not merely its name.
The gate judges exactly the set reconcile treats as work — the staged paths
outside the katra store and outside .claude/. Asking both sides the same
question is what keeps them from disagreeing about what the work is.
If the gate ever blocks over a path katra reconcile cannot see, it says so and
names the path:
katra: staged code isn't covered by a reconciliation receipt, and
`katra reconcile` cannot see it either — declaring will not help.
invisible to reconcile: y.go
This is a katra bug, not something you did wrong.
That state should be unreachable. The message exists because it was reachable,
in three different ways, and each one presented as an ordinary “declare it”
prompt that no amount of declaring would satisfy — see
the design note. If you see it, --no-verify is
the correct response and the report is welcome.
What the Stop gate actually blocks
This is the part worth understanding, because a gate that fires wrongly is worse than no gate. It blocks only when all of these hold:
- The turn authored code changes that are still present in the working tree. An edit-then-revert nets to nothing and does not block.
- Those changes are outside the katra directory. Writing an entry is not work that needs its own entry.
- Nothing in this turn declared what the work was for.
A purely conversational turn never blocks. Neither does a turn that only touched files someone else had already dirtied. And a blocked turn never blocks twice for the same unchanged work — the block records a watermark.
To satisfy it, declare the work — <task-slug> comes from katra task list
(or create one with katra task new):
katra reconcile --advance <task-slug> # this moves a task forward
katra reconcile --close <task-slug> # this finishes one
katra reconcile --no-task --reason "…" # this advances no task
katra reconcile --skip --reason "…" # bypass just this unit
katra reconcile status prints what the gate currently wants, which is the
first thing to run when you are blocked and unsure why.
The commit gate changes your commit flow in every repo you install it in.
That is the intent — it is what stops work landing unlogged — but if you are
trying katra out, katra setup --no-gate gives you the nudges without the
block, and you can turn the gate on later by re-running katra setup.
Working from a spec
The installed v0.1.0 CLI predates task spec, task new --spec, and the
specced help value. Build current source with make all to use this phase
until the next release. The release checklist tests these commands against the
packaged binary.
A task can point at a committed spec. katra task spec <slug> <ref> sets
spec: on it and moves it from todo (or empty) to specced — a status
meaning a design exists, committed, and nobody has started building it.
<ref> is a node slug in the katra (a decision, an article, an entry) or a
path relative to the repository root; either resolves the same way in every
session, because it is committed rather than remembered.
That matters more for an agent than for a person. A fresh session has no transcript from the one that wrote the spec — what it has is whatever is in the repo. The pickup sequence for a specced task:
katra task list --status specced # what's designed and ready to build
Read the task’s spec: reference — the file it names, in katra/decisions/,
katra/articles/ or wherever the path points — before writing any code.
Then:
katra task start <slug> # -> doing
Implement from the spec, not from a re-guess at what the title meant. Log entries as you go the same as any other work, and let them record where the build diverged from the plan and why — that comparison is the thing neither the spec nor the diff can hold on its own.
katra task spec writes even when <ref> does not yet resolve to a node or a
file — the spec may be authored in the same change — but katra doctor
reports a task whose reference never ends up resolving. See
Design: the spec phase on tasks for the full
reasoning behind the status and the resolution rule.
Memory ingest
Claude Code keeps its own project memory. katra can read it, classify each generation, and fold the admitted ones into a private ledger so the log gets the play-by-play without anyone re-typing it.
katra memory status # pending / imported / ignored / quarantined
katra memory scan # pick up anything written since the last scan
katra memory resolve <file> # fold a generation into the log
katra memory ignore <file> # mark it not worth logging
Only metadata.type: project memories are admitted by default — not user
(who you are) and not feedback (how you like to be worked with), because
neither belongs in a committed log. Anything matching a secret detector or a
configured sensitiveTerms entry is quarantined rather than offered. See
Configuration.
The ledger is machine state in katra/.state/ and is not itself the log. Ingest
is the first of three stages — ingest, author, publish — and only publishing
puts anything in an entry.
Writing a good entry
The failure mode is not an agent that forgets to log. It is an agent that logs a flat technical document — a wall of prose that reads like a handoff note to the next agent — and a log of those is one nobody, including you in a month, ever reads.
The public Katra workflow is the full guidance. Its core:
- Show the thing. Treat a draft with zero visuals as unfinished. A screenshot, a before/after, a chart of the numbers you just measured.
- Write the reasoning, not the diff. The decision and the why, the alternative you rejected, what broke first. That is what memory paraphrases away and what a diff cannot hold.
- Open with the stakes, not the filename. “The HUD was eating the city” beats “Implemented WorldHUD.cs changes in-place.”
- Land it. End on what is now true and what is still shaky — a
warningblock for the parts that need a device, a person, or a rerun.
Anti-patterns, all observed in real katras: addressing the next agent (“Integrator notes: wire X after Y”), closing on tooling receipts (“Validated clean: 0 diagnostics”), spec-dumps as the body, and an entry with no visual at all.
MCP
katra-mcp is a stdio MCP server over the same core operations the CLI drives.
{
"mcpServers": {
"katra": { "command": "katra-mcp" }
}
}
It resolves the katra from $KATRA_DIR (or the legacy $DEVLOG_DIR), falling
back to discovery from the working directory.
The native binary is the supported path because it can use the working tree
directly. From the first release onward, the official MCP Registry will list a
minimal OCI wrapper containing only katra-mcp and git. That wrapper is
distribution metadata for registry clients, not a general Katra container: a
client running it still has to expose the repository working tree. It does not
contain katra setup, the viewer, the hub, or the CLI.
| Tool | What it does |
|---|---|
katra_list |
List entries. |
katra_get |
Fetch one node’s frontmatter and body. |
katra_new |
Start a draft. |
katra_append |
Append markdown to a draft. |
katra_capture |
Import media and append the right block. |
katra_compare |
Import two images and append a compare slider. |
katra_stamp |
Stamp a draft with commit hashes and diffstat. |
katra_nodes |
List nodes of any type. |
katra_task_new |
Create a task. |
katra_task_list |
List tasks. |
katra_task_set_status |
Move a task between todo/specced/doing/done/cut. |
katra_task_spec |
Attach a spec and advance a todo task to specced without moving later statuses backwards. |
katra_epic_new |
Create an epic. |
katra_decide |
Record a decision. |
katra_article_new |
Create an article. |
The operations live in internal/core, so the MCP server and the CLI cannot
drift apart in behaviour — only in surface. If a tool is missing here, the
corresponding core operation exists and wiring it up is a small change.
Without Claude Code
Claude integration is not required. Any agent that can run a shell command can drive the full workflow, including epics, specced tasks, spec pointers, decisions, closure, and rollup. The smallest entry loop is four commands:
katra new "What you are about to do" --tags area,kind
katra capture screenshot.png --caption "after the fix"
katra append "Why X over Y, and what broke first."
katra stamp
Install the git hook (katra hook install) and the last one happens on its own.
For the complete plan-to-proof sequence, use The Katra workflow.