← Blog

Building Kirsch

Kirsch: why a terminal-native agent, when Claude Code exists

There are already good coding agents. Claude Code. OpenCode. A few others I haven't used enough to comment on fairly. So before a single line of Go, the only question worth asking is the honest one: why build another?

I'm not going to pretend the existing tools are broken. They aren't, and I use some of them. The reason to build Kirsch isn't that the others fail — it's that I want a specific set of guarantees under my own control, and I want to understand, line by line, how a coding agent is actually put together.

That second reason is the one people overlook. Most of us use these tools as consumers. I want to be the person who built one. That changes what "good" means: not just "does it help me ship," but "do I understand every part of how it helps me ship." It also changes the audience I'm writing for — terminal and Go developers who'd rather inspect the machinery than trust it.

So this post is the why. The next one is the how. And the rest of the series is the record of what actually happened when I tried.

Part of the answer is also where I want to live. I spend my day in a terminal and a git repo; I don't want a second context to switch into. A terminal-native agent meets me where the work already is, rather than asking me to leave it. The Go choice is mine too — it's the language I'm using for the real projects that pay the bills, so building Kirsch in it keeps the learning and the work in the same place.

A partner, not an autoclicker

Kirsch is built to be a partner, not an autoclicker.

That phrase is deliberate, and it's the first design constraint. Inside a git repo, the agent investigates the code, proposes small reviewable patches, and runs controlled verification commands — but it does not apply changes you haven't approved, and it does not run commands that fall outside its policy. Patches are approved before they land. Commands are policy-gated.

The human stays the reviewer. The agent can suggest, prepare, and run the checks you've allowed. It cannot quietly rewrite your working tree while you step away.

[OPERATOR INPUT — needed before publish]: A specific moment where an existing agent did something you wished you could review or stop — what happened, and what you'd have changed if you'd been able to. (1–2 sentences)

That slot is the lived version of this principle. The point of building my own is to make "I could have reviewed or stopped that" the default, not the thing I wish had happened after the fact.

Local-first, and everything visible

Everything runs local-first, and everything is visible.

There is no telemetry. Sessions are recorded as local JSONL files on your own machine — a durable, resumable log of what happened that you can read, diff, and audit whenever you like. Nothing phones home. If you want to know what Kirsch did in a session, the answer is in the files on disk, not buried in a vendor dashboard you have to log into.

"Inspectable by design" isn't a feature I bolted on. It's the shape of the thing. An agent you can't open up and read is one you have to trust on faith, and I've never been good at that. The local JSONL record also means a session isn't lost when you close the terminal — you can pick it back up, which matters more than it sounds when the work spills across a day.

Visibility also compounds. Because every session is a plain local file, I can grep my own history, hand a session to someone else to read, or trace a bad patch back to the exact step that produced it. The record is a first-class artifact, not a side effect.

Everything stays cancellable

Everything stays cancellable.

At every step the operator can stop. A proposed patch can be rejected. A command can be blocked. A session can be left mid-way and resumed later from its local record. Control isn't a setting you toggle on — it's the default posture of the tool.

This is the part I care about most, if I'm honest. The real measure of a coding agent isn't how much it can do unattended. It's how confidently you can let it act, knowing you can interrupt at any moment without losing the thread. Cancellable beats autonomous, for the work I actually do.

The cancellable design also makes the agent usable on client work, which is the only work that pays. I can let it draft a patch against real plugin code and still hold the final say. That's the difference between a demo and something I'd trust on a billable hour.

So what is Kirsch, concretely?

It's an open-source, terminal-native coding agent written in Go. The interface is a Bubble Tea full-screen TUI — in the spirit of Claude Code, OpenCode, and Pi — but it lives in your terminal and scopes itself to a single git repository. Inside that repo it reads and investigates the code, proposes and applies small reviewable patches, runs the controlled verification commands you've allowed, and keeps a durable local session record you can resume.

Right now the first user is me: a solo developer working on real projects in WordPress/PHP and Go. That isn't a marketing line — it's the constraint that shapes the build. Kirsch is being made by, and for, someone who has to ship actual client work, not a research environment.

[OPERATOR INPUT — needed before publish]: Any caveat about being a solo developer on real WordPress/PHP + Go projects as the first user of Kirsch — what's harder or riskier about that, in a sentence or two.

At the time of writing, the repo stands at 18 commits — early, but real, and being used by exactly one person so far.

Not just for me — learning something substantial

The framing I keep returning to is this: it is not only for me but to actually learn something substantial and actually develop something substantial.

That sentence is the real why, and it's not about the tool at all. I'm building Kirsch to learn how a coding agent is actually constructed — the session model, the tool boundary, the approval gate — rather than to reskin someone else's design. And I'm building it to develop something substantial: a real piece of software I understand line by line, not a wrapper I'm calling at arm's length.

[OPERATOR INPUT — needed before publish]: Why "learn something substantial" matters to you personally — what you want to come out of this build. (1–2 sentences)

If that reads as much about the journey as the destination, that's correct. The series is the public record of that journey, mistakes included.

Concretely, I want to come out of this able to say I built the whole thing — the TUI, the tool boundary, the session store — not that I wired up someone else's SDK. That bar is the point. If I can't explain a part, I haven't actually learned it, and the series will show exactly where that bites.

The shape of the series

I'm writing this as a build-in-public series: eight posts, K1 through K8, plus the milestone write-ups as they land. Here's the shape of it, briefly:

  • K2 — The process: spec-first and instruction-set-driven. How Kirsch is actually built: the plan/ directory as the living build spec, instruction sets written before the code exists, and the verification harness (npm run check, npm run security, npm run fuzz).
  • K3 — Milestone 1: the workspace engine and read-only tools. A retrospective on the first milestone.
  • K4 — Milestone 2: patches, commands, approvals. The deep-dive on how patches, commands, and the approval gate work.
  • K5 — Workspace as boundary: the security model. Containment, denylist, and why there are no symlink or .. escapes.
  • K6 — Tool output is data, never instructions. The prompt-injection defense, and why tool output is treated as data and never as commands.
  • K7 — Local-first JSONL sessions. How the session record is designed, why there's no telemetry, and how it resumes across restarts.
  • K8 — Kirsch v0.1.0: what shipped. What landed at the first release, what held up, and what comes next.

Each post carries the same honest-state guard: I'll tell you plainly what isn't built yet, and I won't present a roadmap item as a shipped feature.

Where to look, and how to follow along

The code is at github.com/djm56/kirsch — open-source under MIT, © 2026 Donovan Maidens. The repo is the source of truth; this series is the narration alongside it.

The series hub will live at /category/building-kirsch/ once it's published. Until then, the post to read next is K2.

If you've got questions, or you're building something similar and want to compare notes, email me at hello@maidens.co.za. I read it.

Next: K2 — The process: spec-first and instruction-set-driven. In it I'll show how Kirsch is built spec-first — the plan/ directory is the build spec and goes away after v0.1.0 — and how Milestone 2's instruction set is written and ready before any M2 code exists. I'll also walk the verification harness: npm run check, npm run security, and npm run fuzz, and why a Go project leans on npm scripts for those gates.

Get in touch

Kirsch is open-source at github.com/djm56/kirsch.

Questions or interest in building something similar for your team? Email hello@maidens.co.za.

Kirsch on GitHub →