rabadon

overview / agents

Claude Code, Cursor, and an agent rabadon has never heard of

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 agentwhat to do
Claude Coderabadon init — merges hooks into .claude/settings.json
Cursorrabadon init — also writes .cursor/hooks.json
anything elseuse the contract below. No change to rabadon is needed.

what each agent can and cannot do

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 CodeCursorgeneric contract
refuse a shell command before it runsyesyesyes
refuse a file edit before it landsyesnoyes, if you call it before
observe a file edit after the factyesyesyes
refuse an MCP tool callyesyesyes
capture the session goal from the promptyesyesyes
goal-drift verdict at the end of a turnyesyesyes
ledger, audit, export, lensyesyesyes
put a diagnosis into the agent's contextyeslateyes, if you call it before an action
rabadon remove takes the wiring back outyesyesn/a — you own the call site
a ledger line attributable to this agentyesnot yetnot 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:

{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"…"}}

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:

{"ev":"INJECT_ANSWER","signal":"oscillation","mseq":7,"psig":"…","sig":"…","same":false}

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.

rules

test-tamper

commands

rabadon init · rabadon exec · rabadon remove