murderboard.tonydefazio.com — the explainer, as a page you can read: what this is, the one rule, the eleven roles, and what you hand over. Written for someone deciding whether to adopt it.
A run fans out one agent per reviewer role, every role, every time — so it is never cheap. Known good: Claude Opus 5. Other current models are likely fine. Fable is blocked by default: one run there spent a two-day allowance and returned no review.
You pay for the tokens, and we are not liable for any cost — ever. The cost gate is a safeguard, not a spending cap; set real limits with whoever bills you. TERMS.md
▶ START HERE — new to this? Two minutes, no install, no Claude needed.
In a hurry: open PROMPT.md, copy the block, paste it into any AI chat, then paste your document. That is a working murderboard. Everything below is how to stop having to remember to do it.
The rest of this README is written for someone wiring this into a project. If that is not you yet, START-HERE.md is the page you want.
Authorship. The ideas, decisions, and review here are mine; the code is Claude's (Anthropic's Claude Code). I set the problems, made the calls — including overriding rules the model had adopted — and merged nothing I hadn't reviewed. I did not write the implementation.
Agent commits carry a
Co-Authored-By: Claudetrailer. Commits predating 2026-08-06 are from before that was enforced automatically, so assume agent authorship unless a commit says otherwise.
A project-neutral anti-slop review harness for document deliverables, plus the literature tool that feeds it. A murderboard is a panel that tries to tear a thing apart before it ships, so what survives is trustworthy.
It is a small set of files you vendor into a project — the process, the five gates that keep it honest, and the skill that calls it up:
| File | What it is |
|---|---|
doc_review_process.md |
The process. A team of adversarial reviewer roles (claim/data verifier, citation validator, consistency auditor, hostile peer reviewer, line editor, methods expert, reuse auditor, naive-reader accessibility, density/figure-first, build & craft gate, argument order) that check every claim against a real source, verify every citation, attack overreach, ask whether a null result could ever have failed, ask what should have been a figure, read the order the case is made in, and run the mechanical checks against a render — before a doc, figure, or report is handed over. Every role runs on every deliverable; what scales to stakes is how you run them, not which ones. Roles are split along two axes: what it costs to satisfy them (judgment calls sit with the reviewer whose mode of thought they match; every check a script or a render decides sits in one role with a table for an output, so a skipped check leaves a visible hole), and their unit of analysis (a defect whose unit is the whole sequence or the whole page is invisible to every per-slide reader). |
fetch_paper.py |
The lit tool. Fetches open-access papers, caches them, checks a curated library before downloading (--have), promotes keepers into it (--promote), and flags anything it can't reach to a want-list (--need, and auto on any failed/paywalled fetch). |
murderboard_freshness.sh |
The freshness gate. Answers "is this consumer's vendored copy current?" by comparing the stamp against upstream HEAD — 0 current · 1 stale · 2 unknown, never a false "current". Silent when current, so it runs unattended; --hook serves a cached answer and refreshes detached, so a SessionStart hook never blocks on the network. --selftest proves every branch can still fire. This is step 0 of the process, mechanized. Not murderboard-only: --label/--slug/--clone/--file point the same gate at any vendoring relationship, so a repo can police every upstream it vendors from with one tool. |
murderboard_roster.sh |
The coverage gate. Derives the role roster from doc_review_process.md (never recalled, so a new role propagates to every consumer for free) and checks that a finished review report accounts for every role — 0 all present · 1 one missing · 2 unknown. It exists because "every role runs" was prose: a run that fired 7 of 11 roles and one that fired all 11 cleanly produced reports no reader could tell apart. It also reads the record's reports: line and opens the archive of what each role actually returned — every role needs a file and every file needs bytes in it, because the loss this catches arrived as an archive of eleven 0-byte files. An archive named but absent fails on its own, flag or no flag; undeclared is reported and still passes, so older records keep working, and --require-reports is how a project stops accepting them. |
murderboard_prose.sh |
The prose gate. Runs the mechanical half of role 5 over a document — every banned construction with its line number, and a word/sentence count per block — 0 clean · 1 hits · 2 unknown. The word list is derived from role 5 at run time, so editing the process file moves the gate, and a reworded list stops it rather than leaving it searching for the old one. It exists because a greppable check sitting inside a judgement role gets answered in prose: "the wording is clean" from a reviewer who searched and from one who did not are the same sentence. It deliberately cannot judge — it reports that a block is 220 words, never that the block is too long, and which sentence carries the payload stays with role 5. --selftest proves every check can still fire, including the ones that must come back empty: a gate that cannot report clean is as useless as one that cannot report a hit. |
murderboard_model_gate.sh |
The cost gate. A PreToolUse hook that reads the running model out of the session transcript and blocks a murderboard call-up on a model you cannot afford to exhaust — 0 allow · 2 block. It exists because the process is a fan-out: every role runs, always, so the smallest legitimate run is still the whole roster, and on 2026-09-07 one run under Fable spent a two-day usage limit and produced no review at all — the cost landed before the deliverable did. It is the only gate here that fails closed (an undetermined model blocks), because the asymmetry is not close: a wrong block costs one message, a wrong allow costs days nothing gives back. It catches the hand-run path too, not just /murderboard — the role fan-out is where the money goes. Overridable by MURDERBOARD_ALLOW_EXPENSIVE_MODEL=1 and re-aimable by MURDERBOARD_BLOCKED_MODELS, because which models are expensive is a claim about this month's prices; for the same reason it carries a review-by date that CI fails past, so the policy gets re-justified or dropped rather than quietly becoming folklore. It also asks before every run, via the harness's own permission prompt rather than the model's good intentions — because the other way this wastes money is being fired too early, at a draft that was not ready, by a session that decided on its own that something was a deliverable. That question is about the moment, not the model; one confirmation covers the fan-out it authorises rather than prompting once per role (MURDERBOARD_CONFIRM=0 to stop asking). --why prints the policy; --selftest covers the blocks, the confirmation, and the negative controls that must still pass. |
murderboard_agents.py → agents/ |
The team compiler. Slices doc_review_process.md into one agent file per role — frontmatter, that role's checklist and nobody else's, and the tools the process file says the role must be able to reach. It exists because a role spawned without the means to do its check still returns prose, and prose describing a check reads in a report exactly like the check: DOI or Die with no web access reports on citations it never resolved. That is the process file's own "can the alarm ring?" rule pointed at the reviewers. Compiled, never authored — write regenerates, check exits 1 if one of its own files was hand-edited or orphaned, list prints N<TAB>agent-name<TAB>path so a harness spawns by name instead of recalling one. It writes into .claude/agents, which is a shared directory, so it only ever removes files it can see it wrote — every generated file carries a banner, and anything without one is somebody's own subagent and is left alone. That rule exists because the first version did not have it and deleted them. verify <report> closes the loop the other way: every role opens its output with GRANT <n> ok — <tools held> or GRANT <n> MISMATCH — …, and the gate refuses a run record that claims named agents while its own reviewers reported otherwise. That matters because a grant written down and a grant that arrived are different facts — a role spawned through a fallback path inherits whatever the harness handed it, sometimes less than the table allows and sometimes more, including the editing tools no reviewer may have. A fallback run is a real run; it just may not be written up as something else. Vendor the compiler, not its output: re-running it after a re-vendor is how role 12 reaches you, where a vendored list of eleven filenames would have quietly stayed eleven. |
murderboard_revendor.py |
The re-vendor tool. Does the update the freshness gate tells you to do, without the corruption the obvious sed causes: it rewrites the stamp on exactly one line — line 1, or line 2 behind a shebang, or JSON's "_vendored" key — and never touches a stamp-shaped string in a body. murderboard_freshness.sh has 11 stamp-shaped strings in its body, because it documents the format. It also refuses two silent failures: a stamp on some other early line (the gate reads it, so the copy drifts behind a green check) and a file set that disagrees with your SessionStart hook's --file list. --check reports without writing; --selftest proves the rewrite is surgical and that the two broken implementations fail its fixtures. Configured by .murderboard-vendor.json in your repo, not by editing this file — so it can re-vendor itself. |
require_commit_before_message.sh |
The durability gate. Refuses a cross-session message while the working tree is dirty — 0 allow · 2 block — so a session may tell another session something only once that something exists in git. Messages between sessions are socket traffic: nothing persists them, and when a session exits its half of every conversation is gone. One estate lost a finding four sessions had established, and noticed only because someone asked whether the messages were committed. Wire it as a PreToolUse hook on the message-sending tool; vendor it to .claude/hooks/. --selftest proves every branch fires. |
skills/murderboard/SKILL.md |
The call-up, for consumers using Claude Code. /murderboard <artifact> runs the process as a sequence that cannot be half-executed: freshness gated at the moment of review (not at session start), roster derived, artifact resolved to the built file rather than its generator and fingerprinted before/after, and a run record emitted and then checked by murderboard_roster.sh. Vendor it to .claude/skills/murderboard/, or install the plugin and get it in place. It resolves either layout, and prefers your repo's vendored copy when both are present — that copy is the version your project declared. |
Nothing here imports anything else. Two of them read doc_review_process.md at runtime —
murderboard_roster.sh derives the roster from it, murderboard_agents.py compiles the agent
files from it — and that is the point rather than a coupling to remove: the process file is the
single authority, so anything that would otherwise become a second copy of a rule is derived
from it instead. The process doc tells its reviewer agents to use the lit tool when they need a
paper, and the skill sequences the rest.
Slop ships when nobody adversarially checks a draft against its real sources: a statistic
that disagrees with the run it summarizes, a reference list written from memory, a count
that contradicts itself, a figure whose caption overclaims. Worse, a plotted number can be
perfectly consistent with its caption and still be wrong because the code that produced
it misused a library. The murderboard is the standing habit that catches those before a
human's name is on them. (The doc_review_process.md appendix lists the concrete incidents
that motivated each rule.)
Step zero, and it is not optional: you need a repo of your own, and this is not it. The murderboard reviews your documents and writes records about them — your drafts, your findings, your numbers, sometimes things you have not published. All of that belongs in the project being reviewed. Nothing of yours belongs in this repo. This one is public, Apache-2.0, and vendored: other projects copy files out of it, so anything committed here lands in their copies and cannot be recalled. If you do not have a repo yet, make one before you start — it is the one part of this the murderboard cannot do for you.
If you use Claude Code, install it — two commands at the Claude Code prompt, not a shell:
/plugin marketplace add syncytium2/murderboard
/plugin install murderboard@murderboard
That gets the skill, the process document, the review gates and the literature tool, and wires
two hooks: the freshness check at session start, and the cost gate before any call-up.
/murderboard <artifact> works immediately.
The cost gate is the one thing an install turns on that can refuse you, and that is
deliberate — everything else here is silent unless something is wrong. The murderboard is a
fan-out (every role runs, always), so a single /murderboard on an expensive model can spend
a usage window in minutes; on 2026-09-07 one did, and produced no review for it. Refusing to
spend a stranger's money on our own fan-out is different from refusing their commands over
style, which is why the other gates ship unwired and this one does not. It is overridable
(MURDERBOARD_ALLOW_EXPENSIVE_MODEL=1) and re-aimable (MURDERBOARD_BLOCKED_MODELS); run
murderboard_model_gate.sh --why to see what it is currently stopping and on what grounds.
It does not do everything the four steps below do. It replaces copying the files, and the
session-start half of wiring the gates. Pointing the lit tool at your library is still
yours — it has none until you set MURDERBOARD_LIT — and so is the roster check, which
belongs in your CI where it can block, not in a plugin. Invoking it from your CLAUDE.md
matters most and no install can do it: putting the files within reach is not the same as
making anyone use them.
Vendoring remains the documented path, and is the better one when a fresh clone of your project must carry a working murderboard with it. An install lives in the person's home directory, not your repo, so a colleague who clones your project does not get it. The two also go stale differently, and both are gated: a vendored copy is judged by the stamp written into it, an install by the version its updater acts on. Pick by whether the murderboard needs to travel with the project or with the person.
- Copy the files into the consuming repo —
doc_review_process.mdunderdocs/, the tools undertools/, andskills/murderboard/SKILL.mdto.claude/skills/murderboard/SKILL.md— and stamp each with the upstream commit you copied from, so drift is visible. See "Vendoring" below. - Wire the gates so they fire without being remembered. Freshness at session start
(early warning) and again inside the skill at the moment of review (the actual gate);
coverage against the finished report:
And wire the cost gate, which is a
bash tools/murderboard_freshness.sh --hook # SessionStart: silent unless stale bash tools/murderboard_prose.sh DRAFT.md # role 5's search: 1 if it found something bash tools/murderboard_roster.sh check --require-reports REPORT.md # after a run: 1 if a role is missing, # or its report is not on diskPreToolUsehook rather than a command you run. It has to fire before the fan-out starts, so unlike the three above there is no useful place to call it by hand — by the time you would remember, the money is spent. In.claude/settings.json:Then prove it fires in your repo, because it is silent on every call it allows and a gate that cannot fire manufactures exactly the confidence it was built to earn:{ "hooks": { "PreToolUse": [ { "matcher": "Skill|Agent|Task", "hooks": [ { "type": "command", "command": "bash tools/murderboard_model_gate.sh", "timeout": 10 } ] } ] } }Putbash tools/murderboard_model_gate.sh --selftest # 18 fixtures, both directions bash tools/murderboard_model_gate.sh --why # what is blocked here, and until when--check-review-datein CI too. It fails once the block passes its review-by date, which is the mechanism that makes a human re-justify the policy instead of inheriting it: the models named in it were expensive in September 2026, and that is a claim with a shelf life. If the repo vendors from more than one upstream, wire one freshness entry per family. Staleness is not a murderboard-specific disease — it is a property of vendoring. Example, policing a vendored copy of another repo's files:Namingbash tools/murderboard_freshness.sh --hook \ --label session-protocol --slug <owner>/<repo> --clone ~/path/to/that/clone \ --file docs/session_protocol.md --file .claude/hooks/session-start.sh--filealso scopes the cross-stamp check to that family, so the other family's files are not reported as wrongly stamped. Each family caches upstream HEAD under its own key — a shared cache would compare one family's HEAD against another's stamp and be confidently wrong in both directions. - Point the lit tool at your library by setting
MURDERBOARD_LITto a directory of PDFs (ideally on a synced/shared drive so the cache is shared across machines):export MURDERBOARD_LIT="/path/to/your/lit" python3 tools/fetch_paper.py --have <author> <keyword> - Invoke it from your
CLAUDE.md. Add a rule that document deliverables run through the murderboard before delivery — pointing at/murderboardwhere the skill is installed, and atdoc_review_process.mdotherwise. See this repo'sCLAUDE.mdfor a drop-in paragraph.
There are no submodules — each consumer holds its own copy, which keeps a fresh checkout self-contained on any machine. The cost is manual updates. To keep that honest:
- When you copy the files in, add a one-line stamp in the consuming repo (e.g. top of the
vendored
doc_review_process.md, or adocs/adr/decisionsnote):vendored from syncytium2/murderboard @ <short-sha>. Line 1 — or line 2 when line 1 is a#!shebang, which must stay first. JSON has no comments, so it uses a"_vendored"key. - To update a consumer, re-copy the files and bump the stamp.
git diffon the vendored copy then shows exactly what changed since the last pull.
Do not bump the stamp with a whole-file substitution. That instruction used to end at
the line above, and the obvious reading of it — sed -i 's/@ old/@ new/' <file> — has
already corrupted a vendored document in a consumer repo (2026-08-14). The substitution
ran over the whole file and rewrote a second, unrelated stamp-shaped string in the body,
leaving the doc asserting it was vendored from a different repo entirely. Nothing failed
and nothing warned; it surfaced only because the next re-vendor re-copied that body and
repaired it — the same bug with its evidence deleted.
This is not a corner case in this particular set. murderboard_freshness.sh carries 11
stamp-shaped strings in its body, because it documents and echoes the stamp format; the
skill file describes the format too. A careless bump rewrites all of them, inside the gate
whose whole job is to notice drift. Worse if your body string is a prefix of the real
full-length stamp — then one substitution aimed at the short form eats every long one in
the same pass.
Use the tool, which rewrites exactly one line and refuses to guess:
python3 tools/murderboard_revendor.py --example-config > .murderboard-vendor.json # once
python3 tools/murderboard_revendor.py --check # what would change, touches nothing
python3 tools/murderboard_revendor.py # do it
python3 tools/murderboard_revendor.py --selftest # prove the rewrite cannot spread
It also refuses two silent failures a hand-rolled version has no way to notice: a stamp
sitting on some other early line — which the freshness gate happily reads, so the copy
would drift forever behind a green check — and a files list that disagrees with the
--file list in your SessionStart hook, which is how a file quietly stops being gated at
all.
After every re-vendor, recompile the review team:
python3 tools/murderboard_agents.py write # -> .claude/agents/murderboard/
python3 tools/murderboard_agents.py check # the same line, wired into CI
No --dir needed: the tool resolves its own directory (--print-dir shows it) and writes into
.claude/agents/murderboard/, a subdirectory it owns. Claude Code scans .claude/agents/
recursively and a subagent's identity comes from its name field rather than its path, so the
nesting costs nothing and means the tool is never in a position to remove a file of yours.
Your first run after this will take the fallback path, and that is expected. The agent directories are watched, but only those that existed when the session started — compiling creates one, so the named agents resolve from the next session onward. The run record says which path it took.
The agent files are output, not vendored input — which is why the config above lists the
compiler and not the eleven files it produces. A re-vendor that updates the process file and
skips this leaves your reviewers running the checklists they had before, in a repo whose
freshness gate now reports current. check exits 1 on exactly that, so wire it next to your
other gates rather than trusting anyone to remember the line above.
python3 fetch_paper.py <url> ... # fetch OA paper(s), cache, print text
python3 fetch_paper.py --have <kw> ... # search the curated library FIRST (prevents re-downloads)
python3 fetch_paper.py --promote <url> "Name.pdf" # file a keeper into the library
python3 fetch_paper.py --need "<citation>" # flag a paper for a human to fetch
python3 fetch_paper.py --list # what the cache holds
- Open-access hosts only (PMC, EuropePMC, arXiv, bioRxiv, PLOS, eLife, …; full list in the tool). It refuses paywalled publisher hosts and flags them to the want-list instead of scraping.
- Config:
MURDERBOARD_LIT(library dir),MURDERBOARD_PAPERS(cache dir override).IF2_LIT/IF2_PAPERSare honored too, for the project this tool originated in. - Needs Python 3;
pypdf(orpdftotext) for PDF text extraction.
Three public repositories vendor the murderboard. They are the evidence base for what this process does, and they are worth reading before adopting it — particularly the run record, which is what the output actually looks like.
| repo | what it is | what it shows |
|---|---|---|
syncytium2/colonel_kernel |
research tool relating action-potential firing to calcium signals | ADR-0037 records the adoption decision and its reasoning; dep-freshness.yml runs the freshness gate on a weekly cron |
syncytium2/bugarach |
coordination-detection port with an interactive viewer | a public run record — 11/11 roles, per-round severity table, stated deviation, six residual ⚠ |
syncytium2/no_peak |
validated port of a pulse-detection algorithm | the full vendored set — process, skill, both gates, both hooks |
What the evidence supports, and what it does not. These are case reports, not a benchmark. They show the process running end-to-end on real deliverables and catching defects that had survived ordinary review. They do not establish a rate, and nothing here has been measured against a baseline. Two claims are separable:
- Mechanical, and checkable by inspection today — you can verify from a finished report
which roles ran (
murderboard_roster.sh check), verify whether your vendored copy is stale (murderboard_freshness.sh), and the verify loop terminates by construction. The tests in CI demonstrate these. - Empirical, and not yet established — that this finds more, or better, than some other approach. No such claim is made here.
The defect class it is built for is worth naming, because it is the one a text-only reviewer cannot reach. From the bugarach run: the report claimed a retention figure "restricted to shared ROIs inside the declared regions." The numbers were real; the restriction was not — they had been computed over all events. Nothing inside the document contradicted itself. Only opening the source that produced the number exposes that, and it is why the process insists on the sources rather than the draft.
CITATION.cff in the repository root carries the metadata; GitHub's Cite this repository
button reads it. A Zenodo DOI is minted per release. Cite the concept DOI
10.5281/zenodo.22063835 for the project as a whole; it always resolves to the
latest release. If you vendored a specific commit, cite that release's own version DOI instead
(v0.1.0 is 10.5281/zenodo.22063836).
Extracted and generalized from a calcium-imaging analysis project (interface2), where it
grew out of real slop incidents — the appendix of the process doc records them, and the rules
in the core exist because of them rather than in anticipation of them.
Two of the sibling repos named in comments here are private, so their names are attribution,
not links: interface2 (origin and first consumer) and fireflies (an R analysis app).
colonel_kernel — which reported the fail-open
bug fixed in the no-heredoc hook — is public, and is linked from the adoption table above.
You do not need any of them. Everything murderboard needs to run is in this repo, and the session
protocol and both hooks are canonical here as of 2026-08-21 — adopted when this repo went
public, because a provenance stamp pointing into a private repo is a dead end for the reader
and makes murderboard_freshness.sh permanently answer 2 (unknown).
Apache-2.0. Vendor it, modify it, ship it in commercial work — keep the notices and
state your changes. If you carry a file into your own repo, stamp it
vendored from syncytium2/murderboard @ <short-sha> on line 1 or 2, which is what
murderboard_freshness.sh reads to tell you when your copy has gone stale.