overview / agents
rabadon's laws were never about Claude Code. git push --force origin main is the same refusal whoever typed it, and the rule engine does not know or care which editor the session is running in. What used to be agent-specific was the binding: the field names an editor puts on stdin were read in five separate places, so a second agent meant finding all five and getting all five right.
They are read in one place now (native/hookev.h), and three dialects are understood.
| your agent | what to do |
|---|---|
| Claude Code | rabadon init — merges hooks into .claude/settings.json |
| Cursor | rabadon init — also writes .cursor/hooks.json |
| anything else | use the contract below. No change to rabadon is needed. |
This table is the honest part, and it exists because a supervisor that quietly supervises less than it claims is worse than one that says where it stops.
| Claude Code | Cursor | generic contract | |
|---|---|---|---|
| refuse a shell command before it runs | yes | yes | yes |
| refuse a file edit before it lands | yes | no | yes, if you call it before |
| observe a file edit after the fact | yes | yes | yes |
| refuse an MCP tool call | yes | yes | yes |
| capture the session goal from the prompt | yes | yes | yes |
| goal-drift verdict at the end of a turn | yes | yes | yes |
| ledger, audit, export, lens | yes | yes | yes |
| put a diagnosis into the agent's context | yes | late | yes, if you call it before an action |
rabadon remove takes the wiring back out | yes | yes | n/a — you own the call site |
| a ledger line attributable to this agent | yes | not yet | not yet |
Two rows there are new and worth reading slowly. rabadon remove strips its own .cursor/hooks.json entries as of 2026-08-26 (native/exit_path_test.sh holds it); before that date it stripped .claude/settings.json only, and a Cursor user had no exit path at all. The last row is a measured absence rather than a guess: no field in the ledger names the agent, so on 2026-08-26 the count of ledger lines attributable to Cursor was 0 — and it would have been 0 even if Cursor had fired, because there is nothing to attribute with. That row changes when the field exists, not when Cursor is believed to work.
The injection channel. When a likely-level signal fires, rabadon does not stop the agent. It writes what the agent cannot see — the file last edited, the readable form of the error, the contrast ("after this move the suite was green, after this one red"), and which attempt this is — and delivers it on the next PreToolUse, in the documented envelope:
It rides the next call and not the one it was computed in, because the signal that needs it most (root_migration) is only knowable once a command has failed, which is PostToolUse, and additionalContext exists only on PreToolUse. No model is called to write it: every sentence is assembled from the move record and the ledger. Never a line number (file-level localisation helps; line-level context measurably hurts), never an instruction, never more than 400 characters, and the same signal speaks at most twice per session — the third goes to the ledger only. RABADON_INJECT=0 turns it off. It never changes an exit code.
Whether it landed is on the ledger, not in a ring. An INJECT line carries psig, the normalised signature that was repeating at the moment the diagnosis was assembled, and the first move the agent makes afterwards is written as its own event:
same:false means the agent's next move was a different move. One answer per injection, and the comparison is finished at the time it is made — because the move record is a 200-slot ring, and a session long enough to produce a signal is exactly the session long enough to roll past it. Before these two fields the question "did the agent read it" could only be asked while the ring still held the move, which on measured data meant it could not be asked at all.
On Cursor this arrives late, and that is not hidden. With no beforeFileEdit, there is no pre-edit moment to ride, so the diagnosis is delivered at the next point that exists — the next beforeShellExecution. An agent that edits several times before running anything gets the diagnosis after those edits, not between them. Everything else about it is identical.
The Cursor gap is real. Cursor has afterFileEdit and no beforeFileEdit, so an edit made by the agent's own edit tool is recorded but not stopped before it happens. Two things still hold on Cursor: a shell write (>, tee, sed -i, rm) goes through beforeShellExecution and is refused pre-spend, and rabadon exec -- <cmd> compiles protected paths into an OS sandbox, so a forbidden write fails with EPERM no matter who tried it.