The harness I built around Claude Code
I use AI to write a lot of my code now. The model isn’t the interesting part though, the harness around it is. On its own a model guesses. Wired into the tools you already use, your test suite, and a few checks it can’t argue with, it becomes something I actually rely on every day, and this site is one of the things it built.
This is the overview: the shape of the thing, and why each piece is there. I’ll go deeper on the parts in later posts.
The four layers
Only the bottom one comes out of the box. The rest I put there.
Each skill teaches the agent one tool I already use and the kind of question that tool answers. The swaps are for the places where the default was too heavy to keep in the loop.
Finding things in the code
This is the layer I lean on most, and it’s where I part ways with how a lot of setups do it. The common move is a RAG index: embed the whole repo, search the vectors. I don’t. An index is stale the moment someone pushes, it costs resources to build and host and keep current, and it still answers a precise question with a fuzzy guess.
So the routing is boring and exact instead. One kind of question, one tool, and a rule that the agent loads the skill before it runs anything.
| Question | Goes to | Why that one |
|---|---|---|
| A literal string, an error message, a key | ripgrep | Fast, respects gitignore, and there’s no index to keep fresh |
| A shape of code: a decorator, a call signature | ast-grep | Matches structure, so formatting variants don’t slip past |
| Who actually calls this function | the language server | Follows re-exports and injected wiring that a text search misses |
| When this line showed up, and who touched it | git history (log -S, log -G, blame) | Searches time, not just the current checkout |
| Whether anything still imports this export | knip | Sweeps the whole project for exports and files nothing reaches |
| A diff drowning in reformatting | difftastic | Compares syntax, so moved code reads as moved instead of rewritten |
None of those guess, and that’s the whole point. It’s also the instinct behind the swap to tsgo, the native rewrite of the TypeScript compiler: I want the agent reading the types as they are right now, not a snapshot from an hour ago.
The gates
I don’t trust an agent that tells me it’s done. I trust an exit code.
So the checks that matter are deterministic and git-tracked, not left to the model’s judgment. When I want a rule enforced it becomes a script that returns zero or non-zero, not a line in a prompt asking the agent to please remember. Same thing I’d want from a person: show me, don’t tell me.
What I don’t hand over
The bot that orders my groceries at home sits behind a Telegram approval step, because I don’t fully trust it with my money yet, and the same instinct carries into how I code. The agent can change whatever it wants. It just doesn’t get to decide the change was good. The tests and the gates decide that, and then I do.
Each of these is worth its own post, and I’ll write them as the pieces settle. The short version is that the model was the easy part to get, and most of my time now goes into the rig around it.