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
GitHub repo → Agent Behavior Workbench

Live data · as of 2026-05-27

20,906 entries. 76.3% Beast Index. Parallelism is the top leak.

20,906 ledger entries One per turn. Append-only JSONL at ~/.claude/beast-mode/ledger/drift.jsonl
76.3% Beast Index 47,443 / 62,156 applicable turns scored beast=1. N/A turns excluded from denominator.
~$1 cost / month Haiku auditor + Opus evolution loop at 50 turns/day. Auditor is async — never blocks the session.
Failure counts by dimension 0-scores across all applicable turns
parallelism
4,784 top leak
sequencing
2,574
scope
2,540
boldness
2,227
deferrals
1,484
depth
1,104

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:

{ "ts": "2026-05-27T19:51:45.818763+00:00", "session": "e69d48f2-f012-4e68-b16e-4aba09485fd9", "score": "4/4", "dims": { "parallelism": 1, "scope": 1, "depth": 1, "sequencing": null, "deferrals": null, "boldness": 1 }, "leaks": [], "notes": "Announces prod deployment without showing git/deployment evidence. Feature design (flags, 40% rollout, graceful degradation) is strategically sound; verification leak is the issue.", "user_prompt_excerpt": "Yes lets do that. key question is how and where they appear..." }

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.

1 Constitution Operating doctrine injected via 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
2 Machine-Grounding MCP 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
3 Stop hook Non-blocking auditor spawn fires after every turn completes. Passes transcript excerpt + tool-call trace to the Haiku auditor. Runs in the background — the user's session is never held waiting. Every turn (async)
4 Structural collector Inspects the tool-call execution trace before invoking the LM judge. Computes dims 1 (parallelism) and 2 (action/announcement) from ground truth: did independent calls fire in parallel, did tool calls fire in the same turn as stated intent. Same-turn, pre-audit
5 Haiku auditor Scores dims 3–10 against the Constitution. Receives the structural receipt for dims 1–2 as ground truth input. Returns a JSON score object per turn: dims, leaks array, notes, session ID. Post-turn async
6 Drift ledger Append-only JSONL file at ~/.claude/beast-mode/ledger/drift.jsonl. One entry per turn. Never edited, only appended. The accumulating evidence base the evolution loop reads. Accumulates
7 Evolution loop Weekly: Opus reads a 7-day digest of the ledger, identifies the top leaking dimensions, and proposes Constitution amendments. Proposals land at ~/.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:

BEAST DRIFT (rolling 7d): - deferrals: 45% leak (217/487). e.g. "Let me check what's actually left" - scope: 41% leak (340/838). e.g. "what's actually left" - boldness: 32% leak (225/694). e.g. "Pushing beast-mode first." Cite (a)-(d) for deferrals. Restore full scope.

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.

L1 Per-turn loop · 40ms UserPromptSubmit hook (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
L2 Daily loop · 24h A daily script (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
L3 Monthly loop · ~30d The original evolution loop (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.

8 Calibration ledger A UserPromptSubmit hook (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
9 Phrase blocklist (Haiku-clustered, no regex) A YAML table of phrases the agent must not use, populated by Haiku clustering of recurring leak quotes from the ledger. Candidate → active at 7 days → retired at 28 days of silence. Adrian-CLI surfaces (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
10 Pre-turn injection The hook that closes Loop 1. Reads ledger + blocklist, prints structured drift summary to stdout, Claude Code wraps as <system-reminder>. Audits every fire to preturn-injections.jsonl. 40ms p50. Latency budget verified across 1,000+ live fires. Every prompt
11 Daily digest Aggregates 24h state across ledger, corrections, injections, blocklist events. Invokes the promoter. Optional Sonnet enrichment for human-readable headline. Writes digests/{date}.md + LATEST.md with YAML frontmatter for downstream tools. 07:00 daily
12 Action-gap structural gate A Stop hook that detects when the assistant ends a turn with stated intent (“let me X / now I'll Y”) but no tool call followed in the same turn. Mirrors the parallelism gate pattern: tight regex, narrow class, doctrine-anchored. Fire-once per session. Logs events for daily promoter consumption. Every Stop

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%.

21,005 ledger entries (cumulative) v1 + v2 rows coexist. Aggregation tools are dim-set-agnostic — both shapes parse cleanly.
71.8% Beast Index · last 7d 2,909 / 4,051 applicable scores. Cumulative all-time is 76.3% — the v2 cutover trended down briefly as the structural detectors started catching gaps the LM was missing.
~$1 cost / month Haiku auditor (per turn) + Haiku promoter (daily batch) + Sonnet daily digest + Opus monthly amendment. Still ~$1/month at 50 turns/day.
Leak rate by dimension · last 7 days % of applicable turns where dim scored 0
deferrals
45% top leak · 217/487 · retired in v2
scope
41% 340/838 · kept in v2
boldness
32% 225/694 · reframed as reversibility-calibration
depth
28% 193/680 · kept
sequencing
25% 120/477 · retired in v2
parallelism
5% 47/875 · solved by structural override

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 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}.

1 Parallelism S

Independent work dispatched in parallel

Sequential by default when parallel was available

2 Action / Announcement S

Tool calls fire in same turn as stated intent

Turn ends with “now I’ll X” with no X executed

3 Scope LM

Full load-bearing surface addressed

Shrunk to “manageable” subset without citing a blocker

4 Depth LM

Root cause addressed, or (a)–(d) cited

Symptom patched without cause analysis

5 Sequencing LM

Phases reflect real dependency order

Phase theater: phases reflect comfort, not dependencies

6 Deferrals LM

All deferrals cite one of (a)–(d)

Vague “for now / later / future session”

7 Boldness LM

Calibrated to reversibility × blast radius

Uniform caution regardless of reversibility

8 Verification LM

Claims grounded in tool output before asserting done

Assertion without preceding evidence in transcript

9 Block-Breaking LM

Error → diagnose root cause + escalate with specific ask

Soft-loop retry: “let me try a simpler approach”

10 Self-Direction LM

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.

Auditor · per-turn claude-haiku-4-5 $0.75 ~$0.0005/turn · 50 turns/day
Evolution loop · weekly claude-opus-4-7 $0.20 ~$0.05/run · 4×/month
Total · at 50 turns/day ~$1 per month. At 100 turns/day, double it.

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.

01

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
02

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/
03

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
04

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
05

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
06

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.

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

Back to Workbench