← All work

Personal project

kantan · A Plan-Gated Runner for Coding Agents

A local tool that drives the real claude CLI through one coding task at a time — it plans, waits for one human decision, implements on its own branch, and lets your test suite, not the agent, say whether it worked.

75 modules · 2,591 tests · the exit code decides, never the agent

  • TypeScript
  • Claude Code CLI
  • Git
  • Markdown
task plan approve run exit ≠ 0 fix Backlog docs/features/*.md Plan agent · no code Gate you approve Refine effort ↑ on repeat Implement own branch Your tests exit 0 → passed

The problem

Coding agents write good code. Then they mark their own homework. An agent that reports “all tests pass, I’m confident the importer is done” is describing its own belief, and belief is exactly the thing you can’t put in a release. The other failure is the opposite one: an agent that runs unsupervised for an hour on a plan nobody read, so the first time a human looks at the work is after the expensive part is over.

I wanted the opposite shape. One task at a time, one human decision at the point where a decision is cheapest — before any code exists — and a verdict that comes from something that can’t be talked into anything.

What I built

kantan is a single local process on one port. It spawns the real claude binary, so your CLAUDE.md, skills, hooks and authentication load exactly as they do in your terminal, and it talks to no service of its own.

It adds one noun to things already on your disk. A project is a git repo, a feature is a markdown file under docs/features/, and a task is a - [ ] line in it. The backlog stays in your own repository and your own git history; kantan renders it and drives it, and never owns it. What it adds is the run, which moves through five stages:

  1. Plan. An agent reads the repo and writes a plan to a markdown file. It touches no code, so a bad plan costs a minute of reading and nothing else.
  2. Gate. The only stage that’s yours. You read the plan the way you’d read a teammate’s draft, then approve it or send it back with a note that reaches the planning session byte for byte.
  3. Implement. A fresh conversation on the task’s own branch — fresh on purpose, so planning and implementing can run on different models without cold-starting the prompt cache.
  4. Test. The server runs your own test command and branches on the exit code. What the agent says about its own work is never used.
  5. Refine. A non-zero exit goes back for a fix. The same failure repeating raises the effort and keeps the context; three identical failures at top effort is stalled — calm, not red, because nothing got worse.

No tick it didn’t earn. If a project has no test command, a finished task says self-checked, never passed. A box someone ticked by hand shows as checked off, in grey, because kantan never ran it. A log older than the field reads unknown. The state system has ten states, and each one is carried by its glyph’s shape as well as its colour, so it still reads with the colour drained out.

What claude -p taught me

Driving the CLI headlessly turned up behaviour you only find by paying for it, and each finding became a probe script in the repo:

  • A question looks exactly like “done”. In -p mode the agent asks in prose and exits with subtype: success, so a question and a completion are indistinguishable by message type. kantan never infers completion from exit status: it watches for a missing sentinel, parks the run, and sends your answer back to the session that asked.
  • --max-budget-usd bounds one process, not your run. Cost resets on every --resume, so a per-process ceiling silently restarts at each gate. kantan tracks the total itself and passes ceiling − spent.
  • Switching models mid-conversation is a trap. It cold-starts the cache and drops thinking blocks the new model can’t read — which is why implement starts fresh.

The result

kantan runs one task per project at a time, so two agents never share a working tree, and a task that can’t start yet says why in a sentence instead of greying out a button. It is 75 modules and 2,591 tests, with one deliberately skipped — the live smoke test, so CI can’t spend money.

It works, and it’s rough in places, which the project says out loud: src/run.ts is a single 2,817-line state machine, the web bundle is one 741 KB file with no code splitting, and it has so far run on exactly one machine. Naming the rough edges is the same principle as the exit code: don’t claim a verification you didn’t observe.