The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Katra listing page.
A committed, rich-component dev log you write as you build — for a developer working solo or alongside a coding agent — and the memory that makes spec-driven agentic development work. Markdown entries with embedded interactive components, stamped automatically with the commit and diffstat they describe, served as a live, auto-reloading page.
It exists to make one workflow reliable: chronicle the work as it happens — the why, the dead ends, the screenshots and animations — and never lose a draft to a "promote" step that gets skipped.
The same store is what keeps an agent effective across sessions: designs land as committed specs, tasks point at them, and a session starting cold reads the spec instead of re-deriving intent from a conversation that no longer exists. That loop is the agent workflow below.
A draft is an entry with no commit hash. That is the entire state machine.
It appears in the In Progress panel the moment you create it. There is no scratch file, no separate document, no "publish" toggle. Stamping it — adding the hash and the diffstat — is what drops it into the log.
Everything else follows from that. Nothing can get stranded in a buffer you forgot to promote, because there is no buffer. The log ends up in the order you actually worked, including the parts that did not pan out, which is the half a squashed history always loses.

katra serve renders the store as a live page — no build step, no static
site to regenerate while you write. The spine down the middle is the log in
the order it happened: future epics, the entry in flight right now, then the
past. The right rail surfaces what a flat file list buries — decisions that
are still load-bearing, and which tags a stretch of work threads through.
An entry, open — this one shipped mid-draft with a before/after slider
comparing two icon-mark directions, dragged in with katra compare:

The board, grouped by status — specced is its own column between todo and
doing, for the tasks that have a committed design and nobody building them
yet:

The hub, one page across every registered project on the machine — this is the maintainer's own, 14 projects and 25 things in flight at once, none of them re-typed anywhere:

You finish a hard week. The commits say fix streaming, wip, actually fix streaming. Six months later you need to know why the spawn budget is
nearest-first, and the answer is not in the diff — it was in your head, and in a
screenshot you no longer have.
The usual answers are all bad in the same way: they are a second job.
CHANGELOG.md records what shipped, not what you learned, and it is written
at release time from the diff you are trying to explain.The common failure is that all of them are a step after the work, and any step after the work gets skipped when the work runs long — precisely when the log would have been worth most.
katra's bet is that the log has to be a side effect of working rather than a task that follows it. So the draft is created when you start, it accumulates screenshots and reasoning while you go, and the commit you were going to make anyway is what publishes it.
This is what katra is built around, and the reason its shape is odd compared to a static-site generator: it exists to keep two things from being lost between agent sessions — the design an agent is meant to build from, and the record of how the build actually went.
An agent that logs at the end writes a summary of a diff — the one thing the diff already tells you. What is lost is everything before the final state: the approach that failed, the measurement that changed the plan, the picture of the bug. So katra pushes the log into the work. The common contract is the CLI sequence in The Katra workflow: declare the epic/task, attach and read a committed spec when one is warranted, open the draft before implementation, record decisions and evidence as you go, then stamp and close. It works from Codex, Claude Code, MCP, or a plain shell.
That creates the Katra and installs the harness-neutral Git post-commit
auto-stamp. Claude Code users can additionally run katra setup, which
installs its skill and seven session hooks. With that optional adapter:
SessionStart reports the active draft, unresolved memory, or in-flight
changes that need reconciling.PostToolUse records every file the agent edits.Stop blocks the turn from ending if authored code changed and nothing
declared what it was for.PreToolUse blocks a git commit whose staged code has no
reconciliation receipt.The gate is the part people react to, so it is worth being precise about when it fires. It blocks only when the turn authored code that is still present in the working tree, outside the katra directory, and nothing declared its purpose. A conversational turn never blocks. An edit-then-revert nets to nothing and never blocks. Someone else's pre-existing dirt is not your work and never blocks. A blocked turn never blocks twice for the same unchanged work.
To satisfy it — <task> is a slug from katra task list (or create one with
katra task new):
If you are trying katra out, use katra setup --no-gate. You get the
nudges and the auto-stamp without anything blocking a commit, and you can turn
the gate on later by re-running katra setup. A blocking hook in every
repository is a real change to how committing feels, and it should be a choice
made on purpose.
There is also an MCP server (katra-mcp) for clients that would rather call a
tool than shell out. Its fifteen tools cover entries plus tasks, task specs,
epics, decisions, and articles. Full detail: The Katra workflow
and Agents.
When the first release ships, the official MCP Registry will be able to
discover the same stdio server through a narrow OCI package. That image
contains only katra-mcp and git; it exists because the registry needs an
installable package, not because a container is a better way to use Katra. A
registry client still has to expose the repository working tree to it. For
direct use, install the native binaries above.
Current-source feature: the installed v0.1.0 CLI does not yet have
task spec,task new --spec, or thespeccedlist-filter help value. Build current source withmake allfor this phase until the next release; the release checklist now asserts all three against the packaged binary.
A task can carry spec: — a node slug in the katra (a decision, an article, an
entry) or a path relative to the repository root, resolved the same way as a
[[wikilink]]. katra task spec <slug> <ref> attaches it, and moves the task
from todo (or empty) to a new status, specced: a design exists, committed,
and nobody has started building it.
The benefit is implementation efficiency and it compounds across sessions: an
agent starting cold — new context window, different day — reads the spec
instead of re-deriving intent from a conversation that no longer exists.
katra task list --status specced is the worklist. Nothing requires a spec;
todo → doing is still legal, and most tasks don't warrant one. specced is
just a place to stand between "we should do this" and "someone is doing it,"
for the tasks where a design is worth writing down first — and the entries you
write while implementing become the other half of the record: what the spec
proposed against what actually happened, dead ends included.
Claude Code keeps native per-project memory. katra can ingest it into a private
ledger, so the log gets the play-by-play without anyone re-typing it. Only
metadata.type: project memories are admitted by default — not user (who you
are), not feedback (how you like to be worked with), because neither belongs
in a committed log. Anything matching a secret detector, or a term you list in
sensitiveTerms, is quarantined rather than offered.
The ledger itself lives at katra/.state/memory-ledger.json and is
deliberately local and gitignored — raw agent memory is unreviewed text
about you and your machine, and it should never reach a shared branch by
default; only the prose you choose to write from it does. So "review" means the
katra memory scan | status | resolve | ignore queue on the machine that
produced it: a teammate cloning the repo gets your entries, not your ledger.
Read this before adopting. Several tools do part of this, and some of them are a better fit than this one.
| Tool | Use it instead when |
|---|---|
| Keep a Changelog + release-please | You want a release-facing record of what shipped, for users. That is a different document from a working record of why, and most projects should have both. |
| adr-tools / Log4brains | Architecture decisions are the only thing you want to record. ADRs are a tighter, more disciplined format; katra's decide is a deliberately lighter-weight cousin. |
| Obsidian, Logseq, a wiki | The notes are personal and span projects, and being in the repo is not the point. katra is per-repository and committed on purpose. |
| Hugo, Jekyll, Astro | You are writing a public blog. They have themes, feeds, taxonomies and an audience; katra has none of those and does not want them. |
| Notion, Linear, Jira | You need assignees, permissions and a shared team workflow. katra's tasks exist to be linked from entries, not to run a team. |
git log and good commit messages | Honestly, quite often. If your commits already carry the reasoning and you never need a picture, you do not need this. |
What none of them do, and what this tool exists for: keep the log inside the repo, written while the work happens rather than after it, with screenshots and interactive components inline, published by the commit you were making anyway. If you do not need all four, one of the above will serve you better and has years more mileage.
Binaries ship for macOS and Linux, amd64 and arm64. There is no Windows build; Windows is untested and unsupported.
go installThe primary channel, and the only one that works before the first release ships (see Homebrew and Download a binary, below). The right path if you already have Go 1.25 or newer:
Install both. katra-mcp is not optional extra tooling — the skill and every
MCP client wiring assume it sits beside katra on your PATH.
A go install build reports the version you installed —
go install …@v0.1.0 reports v0.1.0, and installing from a working tree
reports a pseudo-version naming the commit. Release binaries and make build
carry a link-time stamp instead, which additionally marks a dirty tree.
make build writes into ./bin/ rather than the repo root, because katra
dogfoods itself and ./katra is the directory its own log lives in.
make snapshot builds the full set of release archives locally (requires
goreleaser) if you want to check what a release would
contain.
No release has shipped yet, so this tap has nothing to install until v0.1.0 does — cutting one is what RELEASING.md walks through, and once it happens this becomes accurate:
That taps craigjmidwinter/homebrew-tap
and installs prebuilt binaries. brew upgrade katra tracks new releases.
Same trigger as Homebrew — these archives are produced by the release
workflow, so they exist starting with v0.1.0, not yet. Every
release, once one
exists, ships archives for macOS and Linux on both amd64 and arm64, each
containing both katra and katra-mcp, plus a checksums.txt and a
signature over it.
The snippet below is for a macOS/Linux shell (bash/zsh) — there is no Windows
build, and the uname mapping it relies on would just produce a garbage URL
on one.
On macOS, a binary you downloaded yourself is quarantined by Gatekeeper. Clear
it with xattr -d com.apple.quarantine /usr/local/bin/katra, or use the
Homebrew install above, which does this for you.
Then the signature over checksums.txt. Releases are signed keylessly with
cosign — there
is no public key to fetch and no private key anyone has to guard. The signing
certificate is issued to the release workflow's own GitHub OIDC identity and
recorded in the public Rekor transparency log, so what you are checking is
"this was built by release.yml in this repo, from a tag":
Re-running the go install ...@latest commands above replaces the binaries in
place — verified: the second run relinks both, same as the first. Once a
release exists, brew upgrade katra does the equivalent for the Homebrew
channel.
katra setup creates five things; each has its own removal.
katra hook uninstall.katra hub install. katra hub uninstall reverses it — launchctl unload plus deleting the plist.rm -rf .claude/skills/katra/ (it holds one
file, SKILL.md)..claude/settings.json. Remove the katra agent-hook entries under the SessionStart, UserPromptSubmit,
PostToolUse, Stop, PreCompact, SessionEnd keys, and PreToolUse too
if you installed the commit gate — then drop any of those keys left empty.
katra setup only ever added entries under those keys; anything else there
is yours.~/.config/katra/registry.yml. There is no
explicit deregister command, and none of the above removes it — it lives as
long as katra/config.yml does, which is usually what you want, since
uninstalling the automation is not the same as abandoning the log. If you
do delete katra/, the entry self-prunes the next time katra hub list or
katra hub serve reads the registry — including a hub daemon already
running.Nothing above touches katra/ itself — the katra/ directory is your data,
and it stays.
About five minutes, from inside a git repository.
Set it up without tying the repository to a coding harness.
Creates katra/, a welcome draft, the portable Git auto-stamp hook, and a
hub registration. Claude Code users can layer on katra setup --no-gate
for session nudges or katra setup for nudges plus its commit gate.
Add to the welcome draft, then drop in a screenshot.
Optional, and worth doing for real once you have something to show: capture
any image on your machine (screencapture -x shot.png grabs one on macOS
if you don't).
Serve it.
Leave that running; open tabs reload as you write.
Commit as usual.
The post-commit hook stamps the draft with that commit's hash and
diffstat. Without the hook: katra stamp (HEAD), or katra stamp --hash a1b2c3,d4e5f6 for a chapter of several commits.
Confirm it landed.
A stamped entry, off the commit you already made — the guaranteed visible success this quickstart ends on.
To publish a static copy:
A self-contained directory — index.html, data.json, media — with no external
requests. Host it anywhere, or open it from a USB stick.
A component is a fenced code block whose language names it. The source stays readable and diffable; the page shows the widget.
Plain  images get a lightbox for free.
An unregistered language renders as an ordinary code block. That is the compatibility rule for the format, not a fallback: an entry written against a newer katra still renders in an older one, just less prettily.
There is no chart component, deliberately. You author a self-contained HTML
figure and capture it — katra capture recognises .html and emits an embed
block — so any chart you can draw is available rather than a fixed set of types.
examples/media/frame-times.html is a worked
one, and examples/entry.md exercises every component in a
single entry (CI renders it on every push, so it cannot rot).
Adding a component is one ComponentFunc in internal/core/render.go — that is
the whole extension surface. Full reference:
docs/components.md.
The files katra writes are its public API. They are markdown in your repository, readable without the tool, and they outlive it.
A directory is a katra if and only if it holds config.yml. The conventional
name is katra/; devlog/ is the pre-rename name and is still discovered, so
older repositories keep working without migration.
The compatibility rules a consumer can rely on: an unknown frontmatter key is
ignored, an unknown fence degrades to a code block, and an absent type means
entry. Full contract: docs/format.md.
| Command | What it does |
|---|---|
katra setup [--no-gate] | Add the Claude Code skill/hooks plus portable Git auto-stamp and hub registration. Idempotent. |
katra init [--title T] [--install-hook] | Scaffold a harness-neutral Katra, with optional Git auto-stamp |
katra new "Title" [--tags a,b] [--featured] | Start a draft entry |
katra append [text] [--entry slug] [--file -] | Append markdown to a draft |
katra capture <file> [--caption C] | Import media into the active draft |
katra compare <before> <after> | Add a before/after slider |
katra stamp [--hash H…] [--closes task] | Stamp the draft with commit + diffstat |
katra list [--drafts] [--json] | List entries |
katra serve [--port N] | Live, auto-reloading page on the LAN |
katra build [--out dir] [--all] | Build a static site |
katra hook install | uninstall | Manage the auto-stamp git hook |
katra doctor | Find dangling media, parse errors, entries with no visual, stale epics |
katra task | epic | decide | article | The node model |
katra task spec <slug> <ref> | Point a task at its committed spec (todo/empty → specced) |
katra reconcile … | Declare what the current work is for |
katra memory scan | status | Claude Code memory ingest |
katra hub serve | list | scan | install | Across every registered katra |
Full reference: docs/cli.md.
katra stamp reads git to resolve the hash and compute the diffstat
(--numstat). The optional post-commit hook removes the "forgot to stamp"
failure mode entirely:
After each commit it stamps the active draft with that commit, skipping its own
bookkeeping commits and commits that only touch the katra. By default the stamp
is left as a working-tree change for you to commit; set autoCommit: true in
config.yml to have the hook commit it itself.
The hook honours core.hooksPath. Under husky it installs to the tracked
.husky/post-commit, not the generated .husky/_/, which husky rewrites on
every npm install — and whose shim exits before anything appended to it would
run.
A katra is per-repository, which is right for the log and wrong for "what have I been doing?".
One project index, a cross-project board and roadmap, a merged chronological
log, and every project's own viewer under /p/<id>/. The registry lives at
~/.config/katra/registry.yml and is pruned as a side effect of reading it, so
a project you deleted stops appearing without a cleanup step.
katra build --all produces the same thing as a static directory. There is no
hosted service, no account, and no sync. docs/hub.md;
contrib/ has the launchd agent and a systemd unit.
PRs are welcome — CONTRIBUTING.md has the setup, the test and lint commands, and the invariants a change should hold. RELEASING.md covers how a release actually ships, for anyone cutting one.
Pre-1.0, and written by one person for their own projects. The concrete version of that, since "battle-tested" is easy to say and easy to check: 11 repositories, 237 entries, over four months. Usage is lumpy rather than daily — one heavy month (24 of July's 31 days had an entry written) against three sparse ones before it.
Nobody else has used it yet, so every rough edge you hit is probably one nobody has hit.
Two things that argue for it and one that argues against, all checkable:
katra/ in this repo is a real sample of
its output rather than a demo.katra doctor is worth running.Command names and flags may still move.
katra-mcp OCI wrapper solely so the official MCP
Registry has a package to index; it is not an alternative to the install
paths above.accent as the knob. The
static build is a directory you can restyle yourself if you must.The hub's launchd agent is macOS-only as a first-class install.
contrib/systemd/ covers Linux, but there is no
katra hub install for it.
katra serve and katra hub serve bind all interfaces, so a headset or a phone on the LAN can
reach it. There is no auth. Do not run it on a network you do not trust.
A slug change breaks inbound [[wikilinks]]. The file path is the
identity; katra doctor reports the resulting missing links, but nothing
rewrites them for you.
Media is committed. A log full of gifs is a repository full of gifs. Git LFS works, and nothing in katra knows or cares.
The stat diffstat counts the whole commit, including files unrelated to
the entry, and for a chapter it is the sum across commits. It is a sense of
scale, not an accounting.
katra build overwrites what it generates, and only that. index.html,
app.js, styles.css and data.json are rewritten on every build; files you
add alongside them are left alone. That combination is the trap. A generator
that clobbered the whole directory would teach you to be careful with it; this
one respects the file you added, which invites the reasonable conclusion that
it respects your edits generally — and then discards the one edit that had to
live inside a generated file.
So anything hand-edited into that page — an analytics snippet, social tags, a favicon link — is silently gone on the next build, and the page still renders perfectly while whatever it powered has stopped. Keep such insertions in a step that runs after the build. Verify by checking that a fresh build plus your step reproduces the file you committed: that the page looks right proves it worked once, not that it is still working.
A built site has one URL, so entries have no individual ones. The viewer
routes on the fragment (#/node/<slug>), which the browser never sends to a
server. Two consequences, both worth knowing before you publish a katra
somewhere that matters:
These are one limitation, not two, and neither half can be fixed on its own: both need a URL per entry. Injecting head tags into the built page addresses neither — it cannot vary a card the server never routes, and it cannot emit a navigation that never happens.
Per-entry pages the server can see are planned, and every published
#/node/<slug> link will keep working when they land. Until then, a katra is
a chronicle you can publish and link to, not one whose individual entries
can be linked, previewed, or counted.
Browsable at https://midwinter.io/katra/, or in this repo:
config.yml key, its
default and its failure mode.Runnable examples: examples/entry.md (every component in
one entry, rendered by CI), examples/config/ (a minimal
config and a fully-commented one), and
examples/media/frame-times.html (a
self-contained chart).
The wordmark is set in Fraunces by Undercase Type (Phaedra Charles and Flavia Zimbardi), used under the SIL Open Font License 1.1. Brand assets, the palette and its measured contrast ratios are in branding/BRAND.md.
MIT. See LICENSE.