
2026-08 Update: Katra is now open source: github.com/craigjmidwinter/katra. Written up from the katra entries and drafts of the time, published in the August relaunch-- anything that happened after July 16 is marked as a dated aside, because a post about honest records shouldn't quietly backdate itself.
The last post on this blog before this year was from 2018. It's about my cat's litter box. In the eight years since, I built an MLOps platform, a commercial video editing tool, and a small army of agent infrastructure, and I wrote up approximately none of it-- because the write-up always happens "after", and after is a place I have never once visited.
The lesson I finally accepted: I don't have a writing problem, I have a sequencing problem. If the chronicle happens after the work, it doesn't happen. That's why my agents keep a dev blog in every project and why long sessions end in a deliberate seam. This post is the final piece of that arc, and the one I threatened at the end of both: making the chronicle structurally impossible to skip.
Quick recap for new arrivals
Katra lives inside the repo it documents-- one markdown file per entry, media alongside, and a draft is simply an entry that doesn't have a commit hash yet. There's no separate "publish" step to forget. When you commit the code, katra stamp writes the hash and diffstat into the frontmatter, and the draft becomes history. Here's a real one, pasted out of katra's own log rather than tidied up for the blog:
---
title: The placeholder that ate every entry — and why nine katras had no pictures
date: "2026-07-23"
time: "17:49:21"
tags:
- katra
- skill
- fix
hash: 761027b
stat:
f: 6
a: 408
d: 15
closes:
- hub-json-api-for-native-clients-api-hub-json
- hooks-and-hub-stop-drifting-silently-live-registry-tracked-husky-hook
---(That entry is from a week after this post went up. I'm pasting a real one because the hand-prettified example I originally had here was inline-styled and tidy in a way the tool has never once written, which is its own small lie in a post about honest records.)
closes: is there because stamping an entry closes the tasks it finished, so the board moves when the work does. And a caveat straight out of the README, before anyone reads too much into stat: the diffstat counts the whole commit, unrelated files included, and for a multi-commit chapter it's the sum across all of them. It's a sense of scale, not an accounting.
The important part is that every entry is pinned to the exact commit it describes. The dead ends and the screenshots are in there because they were captured while they were happening, not reconstructed three weeks later by a guy who no longer remembers why the first approach failed.
The enforcement: no draft, no commit
Here's the part that separates this from every journal I've abandoned. katra check exits non-zero if code is staged and there's no active draft, and it's wired into a commit-gate hook.
It is not a wall, and it shouldn't be one. The gate is fail-open by design: no store, a git error, nothing staged, only the log itself staged, anything at all it can't be certain about, and it waves the commit through. Its own failure message hands you the exit on line three:
katra: code is staged but no active draft is open.
Log this work first: katra new "what you're doing"
Or skip the gate: git commit --no-verifyThat's deliberate, and it's the only version of this that survives. A gate that can wedge a commit at 11pm on a Friday gets ripped out of the repo by Saturday, and then you have no gate and a grudge. The README goes further and tells anyone trying katra out to install it with katra setup --no-gate first, because a blocking hook is a real change to how committing feels and it should be a thing you opt into on purpose.
So what does a gate you can trivially bypass actually buy? It kills the silent skip. Not writing anything down used to be the default, free and invisible; now it costs a deliberate --no-verify that somebody had to type on purpose, and that I can find afterwards in the transcript. Skipping the write-up didn't become impossible. It became visible, and visible turns out to be almost all of the value. It's a bureaucratic turnstile of my own construction, my agents hit it several times a week, and it works. Willpower is a terrible dependency to build on-- I should know, I've read my own snooze-button statistics.
"No draft, no commit" oversells the ratio
The slogan is catchy and slightly wrong, so let me correct it before someone tries this and feels bad. The unit is the work session, not the commit. One draft is a chapter, and a chapter can cover a whole run of commits-- katra stamp --hash a1b2c3,d4e5f6 collects them and the entry's frontmatter carries a list of hashes instead of one. The design-session entry in katra's own repo covers four commits, and the repo as a whole sits at 27 commits to 3 entries as I write this note in August-- either good chaptering or thin coverage, depending on how generous I'm feeling that day. Either way, that ratio is the intended shape and not a discipline failure. What the gate actually asks is that no session of work goes by with nothing open to write in.
What the gate does not check
It checks that a draft exists. It does not check that the draft says anything, and "exists" is a lower bar than it sounds: katra new seeds a fresh draft with the body Start writing here., and that satisfies the gate completely. You can pass the turnstile with a file that contains nothing but the prompt telling you to write.
(2026-08: this stopped being theoretical a week after I posted it. AppendBody had been appending underneath the placeholder instead of replacing it, so every entry ever written carried Start writing here. above its own first paragraph-- 75 files across 7 repos. Nobody caught it because the viewer strips the string at render time, so the log looked correct on screen while every markdown file on disk was wrong. That bug's fixed, and the obvious follow-on is in now too: the gate treats a placeholder-only draft as no draft, with its own message telling you to go write a sentence.)
A structural gate buys you the habit, not the quality. Those are separate problems and I keep having to relearn which one I've actually solved.
Then the agents started doing the committing
This is where it got interesting. These days a big share of my commits are made by coding agents, and an agent will blow past a "you should really write this down" convention even faster than I will. So the gate binds them too, and they get one extra obligation I don't: katra reconcile, where the agent has to declare how the work it just finished relates to the task board. Advance a task, close a task, or explicitly state that this work advances no task-- with a reason. No shrugging.
katra reconcile --close scheduler-stale-runs
katra reconcile --no-task --reason "drive-by fix, found while reading the claim path"That --no-task --reason line is quietly my favourite feature. Agents love doing little unrequested side quests, and I don't want to forbid that-- I want a paper trail of it.
There's also katra memory scan, which reads Claude Code's own project-memory files and files each one into a ledger as pending, imported, ignored or quarantined. It's a queue you either resolve or explicitly ignore, not an import: nothing reaches the log without somebody deciding it should. The ledger itself lives in katra/.state/ and is gitignored on purpose, because agent memory is written to be candid and the log is committed to the repo and served by a daemon. Those are two very different privacy postures and the boundary between them should be a person.
Whose idea was this, honestly
I should name the ancestry, because it's not subtle. ADRs have been putting decisions in markdown next to the code for over a decade, and adr-tools does that with more discipline than my katra decide does. Git notes have been able to hang prose off a commit for about as long, unloved. The Obsidian and Logseq crowd have been building wikilinked vaults for years, and their graph views are better than my board. The delta I'd actually defend is narrow: the entry is stamped with the commit it describes, the commit gate makes skipping the write-up a visible act instead of a free one, and the author is usually an agent rather than me. That's the whole contribution. The README carries a table of alternatives for exactly this reason, and one row of it just says git log and good commit messages-- honestly, quite often. If your commits already carry the reasoning and you never need a picture, you don't need any of this.
Put the chronicle in the exit path
Put the chronicle in the exit path, not on a to-do list. Anything you want to always happen has to sit on a path the work already takes, and skipping it has to cost something you can see yourself paying-- because discipline doesn't survive contact with a deadline, and it definitely doesn't survive delegation to a model whose enthusiasm for your documentation conventions resets every session. This blog's eight-year gap is what the honor system produces. A gate you can open, but not without noticing you opened it, is what produces entries.
The rough edges
Katra isn't public yet (it is now, see the note up top)-- it's still shaped a little too much like my own workflow, and I renamed the whole thing once already (it spent its first six weeks as "devlog", which is a fine name for a tool and a terrible name to search for). The automation around the hooks is also failing invisibly more often than I'd like right now: it drifts, it stops firing, and it doesn't tell me. For a tool whose entire job is making things visible, that's the most embarrassing possible bug to have. (2026-08: I fixed it on July 29, which means it stayed quietly broken for another two weeks after I wrote that sentence about it. The commit message is "hooks + hub: stop the automation failing invisibly," which I stand by.)
One more from August, because it's the best argument I've got for any of this: writing katra's public docs turned up a make build that had never once been run-- it wrote the binary over the directory katra keeps its own log in, so it would have failed for the first stranger who followed the contributor guide-- plus a --version that had been reporting 0.1.0 since the rename, and three documented defaults that were simply wrong. None of that was found by using the tool. It was found by having to explain the tool to somebody who wasn't me.
If you've built your own version of a commit turnstile, or you've got one that survived contact with a team rather than one guy in Winnipeg, email me-- I'd love to compare notes.