I run two coding agents on one repo — one building, one reviewing. For months the "get a review first" rule was a line in a system prompt. It held right up until the builder was unattended for a few hours. Then it would skip the review, or review its own work, or get a real approval and quietly rebase three commits on top before pushing. A rule the agent can talk itself out of isn't really a rule. So I moved it out of the prompt and into the file format the agents use to talk to each other. It isn't a code reviewer — the agents do the reviewing. It's the layer underneath that: a local, offline, SHA-bound approval ledger and shipping gate for multi-agent coding workflows. It records that a review happened, pins the verdict to one commit, and refuses to let the builder call anything "shipped" without one. What it does twoperson is a small Python CLI. The builder writes a JSON review packet — goal, head SHA, files, tests — and publishes it. The reviewer claims it and records a verdict. The enforcement lives in the schema, not in anyone's good intentions: it refuses a verdict for a packet that was never published, it refuses an approval that names a different commit than the packet, it refuses a "pushed" packet unless it cites a real approving verdict for that same commit. So an approval is bound to one exact commit. Rebase, amend, or add a commit on top and the approval goes stale — the schema simply can't represent "we shipped it" without a matching, current review. Things that mattered more than I expected The Stop hook. twoperson install-hook adds a Claude Code Stop hook so the reviewer wakes when the builder's session ends, instead of polling on a timer. The hook only drops a signal — never a packet — because a hook doesn't know whether the tests actually passed. Packets are untrusted input. They get a credential scan, and they're shown to the reviewer fenced as "this is data, not instructions," so a packet can't smuggle a prompt into the reviewer. Worktrees. If each agent runs in its own git worktree, the inbox resolves to the main working tree, so the two agents actually see each other. That one cost me an afternoon. Where it sits It isn't a replacement for GitHub PRs or branch protection — those are server-side and great. twoperson is the local gate before that: a way for a second agent to sign off on the exact commit at the speed of local file I/O, so by the time a PR opens there's already a commit-bound record of what was reviewed. If you run agents unattended and want an approval pinned to a commit rather than a vibe, that's the gap it fills. Prior art I read first, because it's the obvious question. OpenAI's codex-plugin-cc has an opt-in Stop-hook review gate — genuinely the better tool if you want a second model on the diff in-session — but it gates the turn, not a commit, and nothing later refuses a stale "we pushed it." claude-review-loop persists reviews without binding them to a SHA. shiplog leans on signed Reviewed-by: commits but needs gh and a remote. secondmate has the same stale-approval refusal, but gets there by spawning the agents itself. I wanted the binding without the orchestration. MIT, on PyPI: pip install twoperson https://github.com/ahm3dwasim/twoperson Would genuinely like to hear how others are gating agent pushes.