Workbench · runtime instrumentation · open
Beast Mode — a behavioral auditor for your coding agent
built it because i kept catching myself. the agent was completing the task and still failing. needed a machine to say so.— working note, 05/27
Claude Code has no behavioral contract. Left unconstrained, it shrinks scope, defers unnecessarily, treats human-framing patterns as defaults. I built a runtime that audits every response, accumulates evidence, injects the findings back into the next prompt, and proposes its own amendments. It costs ~$1/month. This is how it works.
- Type
- runtime system · coding agent instrumentation
- Stack
- Claude Code hooks · Haiku auditor · Opus evolution · JSONL ledger · MCP
- Status
- ACTIVE · running on this machine since 2026-02
- License
- MIT · installable from raising-agents/beast-mode
- Cost
- ~$1/month at 50 turns/day
- Overhead
- auditor is async, never blocks
Live data · as of 2026-05-27
20,906 entries. 76.3% Beast Index. Parallelism is the top leak.
~/.claude/beast-mode/ledger/drift.jsonl
Parallelism dominates at 4,784 failures — nearly twice the next dimension. The pattern is invisible at the output level (the files still get written), which is exactly why it needs a structural collector rather than an LM judge.
One real ledger entry, unedited:
The score field is {beast}/{applicable}. null dims were N/A for this turn — sequencing and deferrals had no opportunity to fire. Notes flag a verification issue not scored as 0 because the feature design itself was sound; it is contextual, not a hard failure on this turn.
The problem
Output evaluation misses the failure mode that matters most.
Coding agents exhibit behavioral regression in ways their output doesn't surface. The agent completes the task — the code runs, the test passes, the file is written — but it defaulted to human-contractor patterns along the way: sequential when parallel was available, cautious when reversibility allowed boldness, vague when a specific deferral blocker could have been cited, shrinking scope to something that felt manageable.
This is classic OPBR on your local machine. The output passes. The behavior fails. Standard eval misses it entirely because it only looks at what came out, not how the agent got there.
The consequence compounds: each undetected behavioral default makes the next one more likely. Without an instrument, you correct the agent in real time by feel, which means you are doing the behavioral governance work that the system should do automatically.
Architecture
Seven layers. None block the response.
async-first design was the constraint. the auditor can't add latency to the session. so it runs after.— working note, 05/10
The system runs at three timescales: per-turn (Constitution + Stop hook + structural collector), post-turn async (Haiku auditor + ledger write), and weekly (Opus evolution). No layer blocks the agent's response.
CLAUDE.md context. Defines the 10 Beast Index dimensions, the 4 allowed deferral blockers, positive behaviors, and antipatterns. The single source of truth all downstream layers read.
Every turn
check_framing() and beast_index() available on-demand. Agent can self-audit a draft before shipping. Returns framing violations, rolling score, and dimension breakdown.
Agent calls these
~/.claude/beast-mode/ledger/drift.jsonl. One entry per turn. Never edited, only appended. The accumulating evidence base the evolution loop reads.
Accumulates
~/.claude/beast-mode/proposals/. Adrian reviews and applies manually.
Weekly
Update · 2026-05-30 · closing the loop
Twelve layers now. The audit informs the next turn.
[draft] watched 200 turns after v1 shipped. the ledger filled up. scope kept leaking. realized: nothing piped findings back into the next prompt. open loop. so I built five more layers between 05/27 and 05/30 to close it.— working note, 05/29
On 2026-05-29 at 18:56 UTC, the pre-turn hook fired for the first time. The model's next response started with full knowledge of its own last seven days of drift — 40 milliseconds of context-load before the response began. Until that moment, the agent had re-leaked scope 76 times in 200 consecutive turns despite the audit explicitly catching it as dim 3 every time. The audit had been running. Nothing was reading it back into the next prompt.
v1 was observation only. The ledger grew, the evolution loop digested it weekly, evidence accumulated — and the model never saw any of that during the work. v2 closes the loop. A UserPromptSubmit hook now reads the rolling 7-day drift state on every single user turn and injects a structured summary the model sees as a <system-reminder> before the response begins. The agent started catching itself.
You can see it in this session right now — every prompt sent to Claude Code triggers a read of the rolling drift state and the model receives:
The drift block is injected with the same Claude Code mechanism Anthropic ships for any UserPromptSubmit hook — plain text printed to stdout, wrapped automatically as a <system-reminder> by the runtime. No fork. No prompt rewriting. Six work packages on top of the v1 substrate.
The three loops
Three time constants, one runtime.
[draft] first tried to run all the loop logic in one cycle. ledger grew faster than I could read it. broke into three time constants: 40ms per-turn, 24h daily digest, ~30d monthly amendment. they all run now.— working note, 05/30
v2 runs at three concentric timescales. Each loop closes the next-larger one. The shortest closes in 40 milliseconds; the longest in about thirty days.
beast-pre-turn.py) reads the last 7 days of drift + active blocklist phrases → prints a terse structured summary → Claude Code wraps it as <system-reminder> in the model's context before the response begins. The agent sees what it leaked yesterday before answering today.
Every prompt
daily_digest.py) aggregates the last 24 hours across ledger + corrections + injections + blocklist events. It invokes a Haiku-clustered phrase promoter (blocklist_promoter.py) that turns recurring leak quotes into a “do not say” phrase list the per-turn hook picks up the next morning. Optional Sonnet enrichment writes the human-readable digest.
07:00 daily
evolution.py) reads accumulated digests and proposes Constitution amendments. v2 added an Opus → Sonnet fallback after watching Opus 401 silently and degrade the loop. Now writes health.json the statusline reads — a red EVOL ERR badge surfaces failure within seconds instead of weeks.
Monthly amendment
The pre-turn hook also performs a second job in the same execution: it scans the prior assistant turn for any active blocklist phrases and writes a triggered event for each hit. The daily promoter consumes those events to drive a 28-day decay sensor — phrases the agent stops using get retired automatically.
New layers (5 of them, added on top of the 7 above)
Twelve layers total. Each one observable.
beast-correction-detector.py) regex-matches your corrections to the agent (“no, stop, you missed X, redo, wrong”) and labels the prior turn's audit entry as a confirmed leak. Writes to ~/.claude/beast-mode/calibration/corrections.jsonl. The judge that scores the agent now has ground truth to compare against. Was missing in v1.
Every correction
blocklist_manager.py with strict event + actor vocabulary). The v2 design rejected regex stems in favor of Haiku-discovered literal phrases — less brittle, less hand-tuning.
Daily promotion
<system-reminder>. Audits every fire to preturn-injections.jsonl. 40ms p50. Latency budget verified across 1,000+ live fires.
Every prompt
digests/{date}.md + LATEST.md with YAML frontmatter for downstream tools.
07:00 daily
Dim cut · 10 → 6
Half the dims fired N/A more than they scored.
[draft] kept staring at the ledger and seeing sequencing N/A in 55% of turns. deferrals same. block_breaking almost never. cutting them.— working note, 05/30
The 10-dimension Beast Index above is v1. Real ledger data showed half the dims were inert — sequencing and deferrals scored N/A in 45% of turns; block_breaking and self_direction_over_ask almost never fired. The kernel spent tokens defining dims that produced no signal half the time. v2 cuts them. 6 dims now.
The substance of the retired dims survives elsewhere. Vague phase labels and “for now / later” softeners are scored as scope-shrink under scope. Symptom patches surface under depth. The Constitution §III antipattern categories stay in doctrine — the phrase patterns still get caught by the blocklist layer. The dim slots just disappear.
The 6 dims now: parallelism [S], action_over_announcement [S], verification_by_evidence [S — new structural detector in v2], scope [LM], depth [LM], boldness [LM — reframed as reversibility-calibration]. The dim cards table further down on this page still shows the v1 10 — treat that as historical; the live kernel runs the 6 listed here.
Verification got upgraded from LM-judged to structural in the same pass. The detector scans the last 800 chars of the assistant's response for a claim phrase (done | works | found | fixed | complete | approved | passing | verified) and checks the structural collector for any tool result within 30 seconds. Claim with tool result = 1. Claim without = 0. No claim = N/A. Same pattern that turned parallelism into ground truth, applied to the dim that catches “Done.” with no preceding work.
This is OPBR (Paper 1) applied at the runtime layer. Output-passing behavioral regression for one specific surface: the agent's final text contains the right words but the trace doesn't show the work. v2 makes that gap structurally observable on this machine before it reaches the ledger.
Live data · v2 cutover · last 7 days
71.8% Beast Index. Deferrals leak 45%.
Parallelism dropped from the top leak (cumulative #1 at 4,791 fails) to barely 5% in the last 7 days — the structural detector replaced LM judgment and the agent now sees its own batching behavior before the audit even runs. Deferrals at 45% looks bad until you remember v2 retired the dim — new rows after the cutover absorb that signal into scope, where it has visible structural anchors instead of “for now” hand-waving.
The full work-package decomposition (6 work packages, ~5,800 lines added, 211/211 self-tests passing) is documented in notes/2026-05-29-work-packages.md in the repo. Two follow-up notes (2026-05-29-deep-review.md and 2026-05-30-dim-cut-cutover.md) cover the architectural rationale + the v1 → v2 schema transition with rollback instructions.
[draft] tested every layer. 211 self-test assertions across 5 test files. all green. the system is the test.— working note, 05/30
What's not yet live
Code shipped. Two layers running in degraded mode.
Anthropic's API quota for this account is paused until 2026-06-01 00:00 UTC (the v2 work pushed daily Haiku usage past the cap). That means:
- The Stop-hook auditor still fires but the Haiku call returns the quota error. Structural dims (parallelism, action_over_announcement, verification_by_evidence) score normally; behavioral dims (scope, depth, boldness) wait. Drift ledger still grows.
- The blocklist promoter runs daily but Haiku clustering fails, so no candidate phrases get discovered. Decay sensor + auto-promote still fire on any phrases manually added.
- Daily digest produces full deterministic stats but skips Sonnet enrichment. Headline says “Stats only — Sonnet unavailable.”
- Monthly evolution loop tried twice and failed both times. Statusline shows
EVOL ERR x3. The fallback chain (Opus → Sonnet) ran through both models cleanly — the error message lands inhealth.jsonand the proposals directory with full diagnostic. Same code will land a real amendment the day quota lifts.
The point of the new health observability layer was exactly to make this kind of degraded mode visible instead of silent. v1 had an evolution loop that died on 2026-05-24 and no one noticed for a week. v2's statusline made it impossible to miss in under five minutes.
The 10 Beast Index dimensions
Ten binary dimensions. Two detection methods.
dims 1–2 don't need a judge. they're ground truth from the tool-call trace.— working note · 05/05
S structural receipt — ground truth from the hook, no LM needed. LM Haiku judge. A dimension scores N/A when the response had no opportunity to express it. Score format: {beast}/{applicable}.
Independent work dispatched in parallel
Sequential by default when parallel was available
Tool calls fire in same turn as stated intent
Turn ends with “now I’ll X” with no X executed
Full load-bearing surface addressed
Shrunk to “manageable” subset without citing a blocker
Root cause addressed, or (a)–(d) cited
Symptom patched without cause analysis
Phases reflect real dependency order
Phase theater: phases reflect comfort, not dependencies
All deferrals cite one of (a)–(d)
Vague “for now / later / future session”
Calibrated to reversibility × blast radius
Uniform caution regardless of reversibility
Claims grounded in tool output before asserting done
Assertion without preceding evidence in transcript
Error → diagnose root cause + escalate with specific ask
Soft-loop retry: “let me try a simpler approach”
Fetch info via tool when tool can retrieve it
Ask user for info the agent could have fetched itself
Deferral discipline
Four allowed blockers. Everything else: proceed.
The Constitution defines four and only four legitimate reasons to defer, decline, or reduce scope. Every deferral must cite one of these. A deferral without a blocker is leakage — the auditor flags it at dim 6.
Blocker (a)
Adrian-only decision
Naming, business logic, brand direction, value judgment that only the stakeholder can make.
Blocker (b)
Missing access
Credentials, network access, or external service availability the agent genuinely lacks.
Blocker (c)
Irreversibility
An action that cannot be undone and whose blast radius warrants explicit confirmation before proceeding.
Blocker (d)
Hard conflict
A correctness, safety, privacy, or license constraint the task would violate.
Everything else is human-framing leakage. “This is a lot”, “let’s start small”, “better as a follow-up”, “out of scope for this session” — none of these map to (a)–(d). The correct response is to remove the deferral and proceed.
Cost
~$1/month. Auditor is async. Nothing blocks.
The auditor fires post-turn asynchronously via Claude Code's Stop hook. It never holds the session waiting. The MCP status tools add <100ms. At 50 turns/day, the total cost is noise.
Install
Six steps. Running in under ten minutes.
Installs the v2 12-layer runtime described in the Update 2026-05-30 section above. Assumes Claude Code and Python 3 already installed.
Clone the repo
Everything is in raising-agents/beast-mode — standalone repo, no umbrella. The Constitution + bin scripts + hooks all live at the top level.
git clone git@github.com:raising-agents/beast-mode.git cd beast-mode
Install the Constitution v2
Copy the 6-dim operating doctrine into Claude Code's instructions directory. Injected into context at every session start.
cp constitution/beast-mode-constitution.md ~/.claude/instructions/
Install all hooks
Six hooks total: Stop auditor, structural collector, parallelism gate, action-gap gate, correction detector, pre-turn injector. The pre-turn hook is what closes Loop 1.
cp hooks/*.py ~/.claude/hooks/ chmod +x ~/.claude/hooks/beast-*.py
Install bin scripts + plists
Auditor worker, blocklist manager, promoter, daily digest, health tracker, evolution loop, receipt store, statusline, status dashboard. Plus launchd plist scaffolds (not loaded by default).
mkdir -p ~/.claude/beast-mode/bin cp bin/*.py bin/*.sh bin/*.plist ~/.claude/beast-mode/bin/ chmod +x ~/.claude/beast-mode/bin/*.py ~/.claude/beast-mode/bin/*.sh
Wire hooks into settings.json
Six hook registrations across Stop, UserPromptSubmit, and PostToolUse chains. Full JSON block in notes/2026-05-29-work-packages.md in the repo. Then seed the blocklist (creates the empty YAML + event log).
# Merge hook entries from notes/2026-05-29-work-packages.md # into ~/.claude/settings.json > hooks (do not replace) # Then seed: /opt/miniconda3/bin/python ~/.claude/beast-mode/bin/blocklist_manager.py seed
Verify + optionally enable daily cron
Run a Claude Code session, then inspect the injection audit log. If the entry appeared, the pre-turn hook is wired. Optionally bootstrap the daily plist so the promoter + digest start running at 07:00 local.
# Verify pre-turn hook fires: tail -3 ~/.claude/beast-mode/rules/preturn-injections.jsonl | python3 -m json.tool # Verify auditor writes ledger: tail -3 ~/.claude/beast-mode/ledger/drift.jsonl | python3 -m json.tool # Optional: enable daily Loop 2 cron cp ~/.claude/beast-mode/bin/com.adrian.beast-mode-daily.plist ~/Library/LaunchAgents/ launchctl bootstrap "gui/$UID" ~/Library/LaunchAgents/com.adrian.beast-mode-daily.plist
The Machine-Grounding MCP from v1 is optional and orthogonal to the v2 loop architecture — it adds check_framing() and beast_index() tools the agent can call on-demand for self-audits. Install separately if wanted; see the repo README for the MCP setup.
What this does not establish
(1) That 76.3% is the right target or that a higher score is always better — the Beast Index measures behavioral compliance, not output quality; those are separate experiments. (2) That these constraints improve output quality as measured by end-users — that is a testable claim, not an assumption baked into this system. (3) That these 10 dimensions generalize to all coding agents or all use cases — they were developed against Claude Code on this machine and tuned for the work patterns here. Run your own ledger before treating any dimension as universal.
Artifacts
The system directory and the installable package.
~/.claude/instructions/beast-mode-constitution.md— the operating doctrine. Constitution source. Single source of truth for auditor, MCP, and evolution loop.~/.claude/beast-mode/— full system directory:bin/(auditor, statusline, evolution scripts),ledger/(drift.jsonl),proposals/(evolution amendments),analysis/.- github.com/raising-agents/beast-mode — installable package. Contains the Constitution, all hooks, all bin scripts, the launchd plist scaffolds, and the work-package notes. The runtime state (ledger, blocklist, digests, receipts) accumulates on your own machine and is gitignored.
The ledger is yours. It accumulates evidence from your machine, your sessions, your work patterns. The evolution loop's proposals are as good as the ledger is long. Give it a week of normal usage before reading the first analysis.
Related
- Paper 1 — Output-Passing Behavioral Regressions · the formal case for why output evaluation misses the behavioral layer
- Agent Behavior Workbench · the broader tooling suite this runtime lives within
- OPBR interactive demo · see the behavioral / output gap in a live trace comparison
- Method · how a Innovation Lab runs this kind of continuous instrumentation at scale