No evidence, no done: a governance framework for coding agents
In my checklist for reviewing AI-generated code, item 8 was "Is every claim backed by something that ran?" It's the one I have to check most often. An agent says the tests pass, the bug is fixed, the change is safe, and it sounds just as sure when it hasn't run anything.
A checklist only works if I remember to use it. So I turned that item into something the agent has to go through. The result is agentic-os: a set of rules in an AGENTS.md file, plus Claude Code hooks that enforce them. You can drop it into any repository. No dependencies, just Node.
Five phases, one rule
Every change goes through the same five steps, and the rule is the same at each one: a claim without evidence is not a result.
| Phase | What counts as evidence |
|---|---|
| Plan | A plan.md with a goal, scope, numbered acceptance criteria, approach, risks and a rollback plan, approved by a person |
| Build | A real diff, and every configured build command (lint, types, build) exited 0 |
| Review | A separate reviewer agent returned Verdict: PASS, named every changed file and left no open blockers |
| Test | Every test command passed, and every acceptance criterion points to the test or log that proves it |
| Ship | Everything above still holds for the current code, and a person approved the release |
The agent drives the work with a small CLI called gov: gov start, gov run build, gov record review, gov advance, and so on. A person does exactly two things, in their own terminal: gov approve plan and gov approve ship.
Three decisions that make it work
Rules in a Markdown file are suggestions. Three design decisions turn them into something closer to guarantees.
Evidence is tied to the exact code it was produced on. Every build run, review and test result is stamped with a fingerprint of the working tree: a hash of every file's contents. If the agent changes one line after the review, the review no longer matches the code, and neither does the build run or the test run. Here's what gov status printed after I did exactly that in a test repo:
✔ plan
✘ build
· `node check.js` passed on an older tree — code changed since, re-run
✘ review ◀
· code changed after review — review is stale
This closes the gap I worry about most with agents: tests pass, the review passes, and then there's "one small fix" that nobody checked. Commits don't count as changes, because the fingerprint only looks at file contents. Committing your work doesn't throw away your evidence. Editing it does.
Evidence is produced, not written. The agent never writes "tests passed" anywhere. gov run test runs the test command from the config itself and saves the exit code and the full log. If the agent tries gov run test -- true to get an easy green, gov refuses: that isn't a configured test command. Same for reviews: the review has to come from a separate reviewer agent with a fresh context and read-only tools, not from the agent that wrote the code.
Editing the record shows. Every piece of evidence goes into a ledger file where each entry includes the hash of the one before it. Edit any entry and the chain breaks: gov log --verify reports where, and every gate fails until someone looks into it. Rewriting the whole ledger from scratch would still pass, which is why the hooks also stop the agent from writing to it at all.
Hooks, not good intentions
AGENTS.md explains the rules so the agent can work with the gates instead of against them. The hooks are what actually enforce them. Claude Code runs them before every tool call and every time the agent tries to stop:
- No code edits without an active task, or while the plan is still waiting for approval.
- The agent is blocked from running
gov approve, writing the ledger or changing the governance config. Those belong to a person. - No reading or writing
.envfiles, keys or credentials. git push, opening or merging PRs,npm publishand deploy commands only run once the ship gate passes, in any directory.- Force-pushes,
git reset --hard,--no-verify,sudoandcurl | share blocked outright. - The agent can't end its turn with the current phase unproven. If it tries, the stop hook sends it back with a list of what's missing, and tells it that if it's blocked, it should say so instead of calling the work done.
That last one matters most to me. It's the difference between "I've implemented the feature" and "The build passes; review hasn't run yet."
It governed its own construction
I built agentic-os with Claude Code. Halfway through, the agent wrote the hooks config before the files it still needed to create. Claude Code picked up the hooks right away, and from then on the agent couldn't write the templates or AGENTS.md: they were governance files, and there was no approved task. It didn't look for a way around the block. It stopped, explained what had happened, and asked me to switch the hooks off in my own terminal so it could finish.
It was annoying, and it was useful. It showed that the guards behave the way I want in the one situation where they're inconvenient for the agent. It also taught me an install-order lesson: the installer now writes the hooks config last.
Update: its first real task was fixing itself
Right after the agent wrote the first draft of this post, I asked it to push the post's branch. The hooks blocked it: the ship gate applied to every git push, even one in my blog's repository. So the first real task that went through agentic-os was a change to agentic-os. It went through the full workflow: a plan I approved, a build, an independent review, tests, and a release I signed off.
The reviewer rejected the first build. The fix I'd planned was simple: only gate pushes that act on the governed repo. The reviewer showed five ways around it, all confirmed by running them. It also found older bugs on the way:
- A
||inside quotes hid a write to a protected file from the tamper check. The agent had already slipped through that gap by accident, while editing the guard withsed. - When I commented out three lines in the JSON config, the file stopped parsing and governance silently switched off. No protected paths, no push gate, no approvals, and no warning.
The plan itself was wrong. The second version of the "only this repo" check failed review too. A directory outside the repo can still be the repo, through a git worktree. A gh pr merge <url> names the repo without any path. And the shell resolves some cd targets differently from how the guard read them. After two rounds I stopped patching. The agent went back to the plan phase and proposed dropping the exemption altogether, and I approved it. Every push, PR, publish and deploy is now gated in every directory. When I want to push my blog, I run the command myself.
The agent locked itself out once. It sent several edits to the live guard at once. The first one called a function the second one hadn't added yet, so every tool call hit an error, and the guard did what the new code told it to: deny everything. I deleted two lines and it carried on, one edit at a time.
In total it took five review rounds. Three were rejections, and between them they found more than ten ways past the guards, all in code that passed its own tests. The fixes are in pull request #1, with the plan, every review verdict and the evidence ledger committed next to the code.
What it isn't
It's a guardrail against the mistakes agents actually make: claiming success without running anything, reviewing their own work, drifting from the plan, pushing too early. It isn't a sandbox against an agent that's actively trying to break it, and the review made the gaps concrete:
- Path protection compares paths as text.
.governance//state.json, an absolute path, a glob or anode -escript can reach a protected file. - Case differences get through. On macOS's case-insensitive filesystem,
.GOVERNANCE/config.jsondoes too, even through the Edit tool. That one is next on my list. - Only known release tools are recognised. The detector knows a fixed list of commands. Aliases, shell variables and deploy scripts aren't recognised.
- Approvals are protected by hooks, not by cryptography.
All of these are listed in the README. For real isolation, use it together with branch protection and required CI checks. The ship gate sits in front of those, it doesn't replace them.
The framework tests itself the same way, with tests that drive the real CLI and hooks inside throwaway git repositories, including the installer. Most of the holes the reviewer found are now test cases, so they can't quietly come back.
Try it
git clone https://github.com/dukenicols/agentic-os ~/agentic-os
~/agentic-os/install.sh /path/to/your/repo
The installer finds your build and test commands for npm, pnpm, yarn, bun, Cargo, Go, Python or Make. It adds the hooks to your Claude Code settings without overwriting what's already there. Then restart Claude Code and start with /gov:plan <what you want built>. If you'd rather be asked than blocked while you try it, set "mode": "advisory" in .governance/config.json.
Agents are good at writing code. They're not yet good at knowing when they're finished. That part should rest on evidence, not on how confident the summary sounds.
More in Agents & automation · All writing