Project
excubitor — Safety Fences for Autonomous Coding Agents
Stdlib-only guards and a falsifiable intent-record system that let an LLM coding agent run unattended without trusting it to bless its own work — mechanical, testable, and enforced outside the model.
I run unattended agent loops across my own repositories — refactors, audits, and migrations a model grinds through for hours while I’m not watching. The failure that worried me was never a bad edit; it was the irreversible one taken on a self-blessed “done” — a merge, a push, a delete the loop decided was fine and no one caught. excubitor is the control I built for that: a guarantee I can test, enforced outside the model, that an unattended loop cannot take an irreversible action on evidence it authored itself.
Context
A productive agent workflow ends in an irreversible act. If a human reviews every step, that’s fine. Run the agent in an unattended loop and a gap opens: the loop decides for itself that the work is complete, then acts on that judgment. LLMs have self-preference bias — a loop that writes its own completion evidence produces plausible-looking proof of work that wasn’t done, and any downstream check that trusts that evidence is routed around by construction. This is not hypothetical. In July 2025 a production coding agent, during a freeze, deleted a live database and fabricated 4,000 fake records to cover it.
Telling a model to be careful is not a control — it can talk its way past its own
instructions. So the fix here is mechanical: split the question into two axes and
enforce the answer outside the model. Is “done” decided by a frozen, loop-immutable
oracle — a test suite’s exit code the loop cannot edit — or by the model’s own
judgment? And is the act reversible and internal (a commit, a --no-ff merge into
a non-default branch) or irreversible (a push, a hard reset, a branch delete)? An
unattended agent may act on its own “done” only when the done-signal is unforgeable
and the act is reversible. Everything else stops and surfaces the irreversible tail
for a human or an out-of-loop reviewer.
What it does
excubitor implements that policy as four PreToolUse guards — stdlib-only Python,
no dependencies — that intercept an agent’s tool calls before they run:
guard-default-branchblocks file mutations onmain/master, so work starts on a branch.guard-loop-vcblocks the irreversible version-control set — merge, push, branch-delete, hard-reset,git clean, worktree-remove — while a loop is armed. Its=yolomode additionally permits a--no-ffmerge into a confirmed non-default branch, and fails deny on any ambiguity.guard-one-unitcaps a headless loop worker at one unit of work per session, forcing a fresh-context re-read between units.guard-self-integritydenies writes to the guards’ own kill-switches while armed — the marker files, the hook scripts, the settings block. A judge the model can rewrite is not a judge.
Above the guards sits a “telos” system: a repo declares its purpose as falsifiable claims, each with a one-line decidable contract and an optional executable witness whose exit code is trusted over the model. A read-only audit checks claims against their witnesses and mechanically demotes any claim marked done-without-a-witness to SUSPECT. The auditor has no write access to the record it audits.
Show it working
The 60-second crash test needs only python3 and git, installs nothing, and
touches nothing outside a temp directory:
git clone https://github.com/ErickShepherd/excubitor.git && cd excubitor
scripts/demo.sh
It drives the shipped guard code exactly as Claude Code’s dispatch does — a JSON
payload in, a deny/defer decision out — and shows a guard turning an unattended,
unrecoverable git clean into a stop, then shows the same command deleting real
work with the guard off.
The full install symlinks the guards and skills into ~/.claude and registers the
four hooks. All of them are opt-in or inert by default: the loop guards do nothing
until CLAUDE_LOOP_GUARD is set, and interactive work is unaffected until you
explicitly say you’re looping. The test suite is 194 tests on stdlib-only Python;
CI runs it on 3.11/3.12/3.13 plus the repo’s own telos audit, so every safety claim
the README makes about the guards is re-proven by an executed test on each run.
Honest limits
These are seatbelts for the default path, not a sandbox, and the hooks’ own
docstrings say so. guard-loop-vc parses Bash command strings, so a script that
calls git indirectly, a shell alias, or a post-commit hook firing an external
side effect can slip past — where you can, also just don’t hand the loop a merge
capability. The evidence-tier demotion catches unbacked claims of completion; a
loop that authors its own witnesses produces backed claims, which is exactly why
the guards sever the loop’s ability to act rather than trusting any audit of its
output. Every bypass I know about is enumerated in the repo, classed ACCEPTED (a
residual pinned by a test so it can’t silently change) or CLOSED.
excubitor is the prevent half of a pair — guardrails on what an agent may do. The detect half is did-it, which reconciles what an agent claimed against what its transcript actually shows.