Agent Brain
Enforced, persistent decision memory for AI code agent teams — that survives context compaction. Agents log decisions and outcomes, learn from past rejections, and resume their pending roadmap after a /compact instead of re-researching it. Hooks make it mandatory (not "stored and hoped for"). And code reads route through SAN compression (~80% fewer tokens, tokenizer-measured) so the context window fills slower in the first place.
Portable across agent runtimes — your knowledge isn't trapped in one tool. Agent Brain is an MCP server, so Claude Code and Codex can both drive the same brain with MCP tools, standing protocol, and lifecycle hooks — switch agents for price or capability without resetting your project's accumulated decisions and SAN. Any MCP host works (Cursor, Cline, Continue, …). See docs/adapters.md; run ./setup.sh for the easiest install.
Contents
- What This Does · Features
- Quick Start — install in 2 minutes
- How To Use It — the agent loop, a worked example, what you get
- Architecture — what lives where, performance & internals
- MCP Tools (21) — the agent-facing API
- Agent Team — bundled role templates
- Model Routing — right model per phase, two-strikes escalation, plan handoff
- Brain Protocol — the enforced decision loop
- SAN Protocol — code compression: is it worth it, measuring savings (
token_savings) - SAN Setup — turning SAN on, model choice, other platforms
- Adaptive Warnings · Web Dashboard — 3D office, decisions browser (live feed), SAN search trace
- Verification · Requirements · Configuration · Customization
What This Does
AI coding agents start fresh every session: no memory of past decisions, no learning from rejections, no cross-agent knowledge sharing — and they burn tokens re-reading the same source files task after task. Agent Brain fixes both:
- Memory — decisions, outcomes, and review feedback persist across sessions and agents:
Agent → pre_check() → "WARNING: similar approach was rejected last week"
Agent → log_decision() → records what you decided and why
Agent → does work → PR created
Reviewer → log_outcome() → "rejected: violates DIP (dependency inversion)"
Next time, any agent → pre_check() → sees that rejection → avoids the mistake
- Cheap code reading — the optional SAN protocol compresses source files to ~17-27% of their original tokens (81% saved, tokenizer-measured), and
token_savingsshows you exactly how much it saved, per session, in numbers and %.
Features
| Feature | What it does |
|---|---|
| Decision Memory | Log decisions, outcomes, feedback. Persists across sessions. |
| Pre-Check Warnings | Before starting work, see past failures in the same area. |
| Fuzzy Matching | "Rate limiting on signup" finds "rate limiting on login" rejection. |
| Code Bridge | Link decisions to code symbols: "Show me all decisions that touched AuthService." (Richer with the optional code-review-graph MCP server; works standalone too.) |
| Agent Scorecards | Acceptance rate, trends, top rejection categories per agent. |
| Adaptive Warnings | Agents with high rejection rates get stricter pre-check warnings. |
| Team Dashboard | All agents at a glance — for project managers. |
| SAN Protocol | Compress code to ~20% of original tokens (81% saved, measured). Full codebase fits in context. |
| Token Savings Tracker | token_savings reports tokens saved this session / today / all-time, with %. |
| Enforcement Hook | Code edits are blocked until the agent logs a decision — memory actually gets populated. |
| Survives compaction | SessionStart hook re-injects the pending roadmap after /compact so the agent resumes instead of re-researching. Details |
| Relevance search | query_decisions(query="…") ranks by topic relevance, not just recency; get_roadmap returns open work in one call. |
| SAN as default read path | Hooks on both Read and Bash route raw code reads/dumps to get_san (per-file nudge, optional hard-block), so agents actually use SAN instead of cat/Read. A tool ladder (find→read→grep-literal→edit) is auto-installed to CLAUDE.md. Details |
| Records & pruning | Browse every decision as dated markdown; prune old/resolved ones (dry-run first, archived not deleted). Keeps the brain lean without losing the lessons. Details |
| Live decisions web view | A /decisions browser that streams decisions in real time as agents log them — filter by repo/area/agent/outcome, search, color-coded. Details |
| SAN search trace | A /san view that shows how a query finds code through SAN — index → content → block → src: line range, live. Details |
Quick Start
Recommended:
git clone https://github.com/sandeep84397/agent-brain.git
cd agent-brain
chmod +x setup.sh
./setup.sh
./setup.sh installs Agent Brain for every supported runtime it detects on your
machine: Claude Code, Codex, or both. If you want to force one runtime:
./setup.sh --claude
./setup.sh --codex
./setup.sh --all
The setup wizard will:
- Create a Python venv and install dependencies
- Prompt for your repo paths (or use the template config)
- Register the MCP server globally with Claude Code and/or Codex
- Install the brain hooks (decision-gate, amnesia re-inject, Read/Bash→SAN routing)
- Add the SAN tool-ladder to
~/.claude/CLAUDE.mdfor Claude Code or~/.codex/hooks.jsonguidance for Codex — no manual edit needed (idempotent; safe to re-run) - Offer to customize agent names interactively
- Run verification checks
No
setup.sh? The server works standalone. Justpip install mcp networkxand register manually:claude mcp add --transport stdio --scope user agent-brain -- python3 /path/to/server.pyThe server gracefully handles a missing
config.json— it starts with an empty brain.
Where things land: setup.sh installs a copy of the server and hooks to ~/.agent-brain/ with its own venv — that installed copy is what Claude Code and Codex run. The repo checkout keeps the source. CLI examples in this README use python3 brain/server.py <cmd> from the repo root after installing dependencies; against the installed copy, the equivalent is ~/.agent-brain/.venv/bin/python ~/.agent-brain/server.py <cmd>. If you edit the repo copy, re-run setup.sh and restart your agent runtime.
Existing users: upgrade to Claude + Codex
If you already installed Agent Brain before Codex support, update the checkout and re-run the same command:
git pull
./setup.sh
The installer is idempotent. It preserves ~/.agent-brain/config.json,
decisions.json, decisions.journal, records, metrics, and existing Claude
agent files unless you explicitly choose to overwrite templates. It refreshes
the installed server and hooks under ~/.agent-brain/, keeps Claude Code wired,
and adds Codex config when Codex is detected.
For each repo you want both runtimes to use:
./setup.sh --link-project=/absolute/path/to/your/project
Then restart Claude Code and Codex. In Codex, run /mcp and /hooks; trust the
Agent Brain hooks when prompted. Verify with:
~/.agent-brain/.venv/bin/python ~/.agent-brain/server.py diagnose --project=/absolute/path/to/your/project
Let an AI agent upgrade it
If an AI coding agent already has terminal access to your Agent Brain checkout, you can ask it to do the upgrade for you:
Update Agent Brain for Claude and Codex compatibility in this repo, link this
project, then run diagnose and validation.
The agent should run:
git pull
./setup.sh
./setup.sh --link-project="$(pwd)"
~/.agent-brain/.venv/bin/python ~/.agent-brain/server.py diagnose --project="$(pwd)"
~/.agent-brain/.venv/bin/python ~/.agent-brain/server.py validate
The only manual step Codex may still require is hook trust: after setup,
restart Codex, run /hooks, and trust the Agent Brain hooks. That trust step is
intentional Codex safety behavior for non-managed command hooks.
Codex setup
When Codex is installed, ./setup.sh writes:
~/.codex/config.toml—[mcp_servers.agent-brain]using the installed brain venv~/.codex/hooks.json— decision gate, roadmap injection, brain-before-research nudge, and SAN Read/Bash routing
After install, restart Codex. In Codex, run /mcp to confirm agent-brain is
enabled, then run /hooks and trust the Agent Brain hooks when prompted. Codex
requires explicit trust for non-managed command hooks.
To link a project for all supported runtimes:
./setup.sh --link-project=/absolute/path/to/your/project
This writes Claude project MCP files and appends an idempotent Agent Brain
protocol block to <project>/AGENTS.md for Codex. Restart your agent runtime in
that project so the instructions and MCP config reload.
Linking a project (so subagents can use brain)
./setup.sh registers brain at the user level. For project-local guidance and
subagent/project wiring, run:
./setup.sh --link-project=/absolute/path/to/your/project
This is idempotent and writes/merges:
<project>/.mcp.json— adds theagent-brainserver entry alongside any existing entries<project>/.claude/settings.local.json— setsenableAllProjectMcpServers: trueand addsagent-braintoenabledMcpjsonServers<project>/AGENTS.md— adds the Agent Brain protocol for Codex<project>/.gitignore— appends.mcp.json,.san/.san_hashes.json,.san/_cache/
After running it, restart Claude Code and/or Codex in the project, then verify:
~/.agent-brain/.venv/bin/python ~/.agent-brain/server.py diagnose --project=/absolute/path/to/your/project
Subagents not seeing brain tools? See the 4-layer model under Verification.
How To Use It
Once set up, you don't call brain tools yourself — your agents do, automatically, as part of their normal work. Your job is just to give agents tasks and (optionally) review the memory that builds up.
The loop every agent runs
For any non-trivial task, an agent follows this cycle (enforced by the hook — see Enforcement Hook):
1. pre_check(agent, area, action) ← "has anyone tried this before? did it fail?"
2. log_decision(agent, repo, area, ← records the plan; unlocks code edits
action, reasoning,
handoff_summary?, validation?, git metadata?)
3. … writes the code …
4. log_outcome(decision_id, outcome, ← records accepted / rejected / failed + why
outcome_by, reason)
You just say "add rate limiting to the signup endpoint". The agent does the rest.
Worked example — across two sessions
Monday — a decision gets rejected:
You: "Add rate limiting to /login"
Agent: pre_check(agent="karan", area="auth", action="rate limit login")
→ "No past failures in 'auth'. Proceed."
Agent: log_decision(... action="in-memory counter per IP", reasoning="simplest")
→ dec_20260609_..._a1b2c3
Agent: …writes code, opens PR…
PE: log_outcome(dec_..._a1b2c3, outcome="rejected", outcome_by="marcus",
reason="in-memory won't survive multi-instance deploy; use Redis")
Friday — a different agent, a related task, a different machine/session:
You: "Add rate limiting to the signup endpoint"
Agent: pre_check(agent="dev", area="auth", action="rate limit signup")
→ "SIMILAR REJECTIONS (1, 78% match):
[2026-06-09] karan tried: in-memory counter per IP
REJECTED by marcus: in-memory won't survive multi-instance deploy; use Redis"
Agent: …goes straight to a Redis-backed limiter, skips the mistake…
No human re-explained the Redis constraint. The brain carried it forward.
How it behaves
| When an agent calls… | What happens | What you see |
|---|---|---|
pre_check |
Searches past decisions in the same area + fuzzy-matches similar actions across all areas | Agent mentions relevant past rejections before coding |
log_decision |
Appends the decision to the journal + drops a marker file | Code edits are now unblocked for ~30 min |
Edit/Write without a recent log_decision |
PreToolUse hook blocks the edit (exit 2) | Agent is forced to log a decision first, then retries |
log_outcome (rejected) |
Records the rejection; raises that agent's rejection rate | Future pre_checks surface it; repeat offenders get stricter warnings |
Any tool with a repo/status |
Updates the live office dashboard | Agent appears working/reviewing/blocked at localhost:3333 |
What you get out of it (outcomes)
| Outcome | How it helps |
|---|---|
| Mistakes aren't repeated | A rejection logged once warns every agent, every future session — even on a different machine. |
| No re-explaining context | Constraints ("use Redis", "don't bypass the auth middleware") live in the brain, not in your head. |
| Cross-agent learning | What backend-engineer learns, frontend-engineer and QA see. Knowledge is team-wide, not per-agent. |
| Accountability & trends | Scorecards show each agent's acceptance rate and recurring failure patterns — agent_scorecard("karan", detail=True). |
| Auditable history | "Why did we build it this way?" → decisions_for("AuthService.login") returns every decision that touched it, with reasoning and outcome. |
| Enforced discipline | The hook means the memory actually gets populated — agents can't silently skip logging and edit code anyway. |
Inspecting the memory yourself
You rarely need to, but from the repo root (where you cloned agent-brain):
python3 brain/server.py stats # overall health: how many decisions/agents/repos
python3 brain/server.py office # who's working on what right now
python3 brain/server.py savings # tokens SAN saved (last session / today / all-time)
From any agent/MCP client you can also ask in plain language — "show me the team dashboard", "what decisions touched the payment service?", "what's karan's scorecard?", "how many tokens did SAN save this session?" — and the agent picks the right tool (team_dashboard, decisions_for, agent_scorecard, token_savings).
Architecture
┌─────────────────────────────────────────────────┐
│ Your Machine (global) │
│ │
│ ~/.agent-brain/ │
│ ├── server.py ← MCP server (17 tools) │
│ ├── config.json ← your repos + team │
│ ├── decisions.json ← memory snapshot │
│ └── decisions.journal← append-only deltas │
│ │
│ ~/.claude/agents/ │
│ ├── project-manager.md │
│ ├── product-owner.md │
│ ├── principal-engineer.md │
│ ├── backend-engineer.md │
│ ├── frontend-engineer.md │
│ └── qa-engineer.md │
│ │
│ project-repo/.san/ ← SAN-compressed code │
│ ├── _index.json │
│ └── src/**/*.san │
│ │
│ dashboard/ ← 3D office + decisions │
│ ├── server.py (python, zero deps) │ + SAN search (web)
│ └── static/ (Three.js + SSE) │
└─────────────────────────────────────────────────┘
Performance & internals
The brain is built to stay fast as the decision history grows into thousands of entries:
| Concern | How it's handled |
|---|---|
| Reading the graph | decisions.json is parsed once and held in an in-memory cache keyed on file mtime+size — repeat tool calls in a session reuse it (~0.03ms vs ~140ms re-parse). The cache self-invalidates if another session writes the file. |
| Writing a decision | Writes are O(delta), not O(graph). decisions.json is a periodic full snapshot; decisions.journal is an append-only log of mutations. Logging an outcome on a ~4MB brain appends ~800 bytes instead of rewriting 4MB. The journal auto-compacts back into the snapshot once it passes 256KB. |
| SAN freshness | The freshness sweep (stat every indexed file + scan the .san/ tree) is debounced to once per 60s per repo, so bursts of get_san/query_san calls don't each pay for it. |
| Bounded responses | Every list/detail tool caps its output (row limits, per-field truncation) so one giant decision can't blow up a response. Stored text fields are capped at write time too. |
| Multi-session safety | Each Claude Code session runs its own server process sharing ~/.agent-brain/. Writes use os.replace with pid-unique temp files to avoid cross-process rename collisions. |
Files in
~/.agent-brain/:decisions.json(snapshot) +decisions.journal(deltas) are the decision memory — both are needed; don't delete one without the other.office-state.jsonis live dashboard state (self-pruning),san_savings.jsonlis the token-savings log. All are per-machine and git-ignored.
MCP Tools (22)
Core (every agent uses these)
| Tool | Purpose |
|---|---|
pre_check |
Past failures before starting work + plan pointers, escalation hints, model routing, SAN coverage (pass repo=) |
log_decision |
Record what you decided and why; optional plan_file, handoff summary, git metadata, validation, blockers, deferred work, and do-not-touch notes make future resume cheaper |
log_outcome |
Record accepted/rejected/failed after review |
log_feedback |
Reviewers log feedback on decisions |
Query
| Tool | Purpose |
|---|---|
query_decisions |
Filter by area/agent/repo/outcome and rank by free-text relevance (query="...") — finds decisions about a topic without knowing the exact area |
get_decision |
Full detail + feedback for one decision |
get_roadmap |
What's left to do — pending + roadmap/blocker-tagged work, ranked. One call to resume context after a fresh session or compaction |
get_resume_context |
Compact repo/area digest: latest handoff, open work, validation, hygiene nudges, SAN visibility, and next action |
Records & pruning (keep the brain lean)
| Tool | Purpose |
|---|---|
export_records |
Write a human-readable audit trail to ~/.agent-brain/records/ — one markdown file per day, browsable via INDEX.md |
prune_decisions |
Forget old, resolved decisions: archives to decisions.archive.jsonl (recoverable) and removes from the live graph. Dry-run by default; keeps rejections + roadmap (the learning) |
resolve_stale_pending |
Mark long-abandoned pending decisions as superseded so they stop polluting get_roadmap. Dry-run by default |
Code Bridge
| Tool | Purpose |
|---|---|
decisions_for |
Decisions touching a code symbol or file (auto-detected) |
code_impact |
Blast radius: code symbols + callers |
Patterns
| Tool | Purpose |
|---|---|
get_patterns |
Cluster recurring rejections; pass action to find similar past failures |
Scorecards
| Tool | Purpose |
|---|---|
agent_scorecard |
Stats for one/all agents; detail=True for trends + advice |
team_dashboard |
All agents at a glance (limit caps rows) |
Office Dashboard
| Tool | Purpose |
|---|---|
heartbeat |
Report agent status (working/idle/discussing/blocked) for live dashboard |
detect_stalls |
Find agents with open decisions but no activity for N minutes (default 5) |
SAN (Structured Associative Notation)
| Tool | Purpose |
|---|---|
recompile_san |
Refresh SAN metadata: rebuild index, clean orphans, update hashes. dry_run=True for a freshness report only. Does NOT generate content. |
query_san |
Find code instead of grepping raw source — keyword search over index + content, far fewer tokens |
get_san |
Read code instead of raw Read — same structure, ~5x fewer tokens (detail="full") / ~11x (detail="sig"). file_path may be relative or absolute (repo auto-detected); max_chars caps output |
token_savings |
Tokens saved by SAN this session / today / all-time — number + % |
Admin (CLI only — not exposed via MCP)
Run from the repo root. These live on the CLI (not MCP) to keep the agent-facing tool surface lean:
python3 brain/server.py validate # full brain self-tests (117 checks)
python3 brain/server.py validate-san # SAN subsystem self-tests
python3 brain/server.py san-index <repo> # rebuild _index.json from .san/
python3 brain/server.py stats # overall brain health
python3 brain/server.py office [repo] # current office state (debug)
python3 brain/server.py savings # SAN token savings (last session / today / all-time)
python3 brain/server.py roadmap [repo] # open-work digest (same as get_roadmap)
python3 brain/server.py records [repo] # export the dated records dir
python3 brain/server.py prune [repo] [--before-days=N] [--apply] # forget old decisions (dry-run default)
python3 brain/server.py resolve-stale [repo] [--apply] # mark abandoned pending as superseded
Managing what the brain remembers
The brain only grows — it learns, it never forgets on its own. That's good for the valuable memory (what failed and why) but old, resolved decisions eventually add noise to pre_check and inflate get_roadmap. Two safe ways to keep it lean:
1. Browse the records. Every decision is exportable as dated, human-readable markdown:
python3 brain/server.py records # writes ~/.agent-brain/records/YYYY-MM-DD.md + INDEX.md
open ~/.agent-brain/records/INDEX.md # browse by date
Each entry shows the date, repo, area, agent, action, and outcome. The records are regenerated from the graph — deleting a day file just re-renders on the next export, so the graph stays the source of truth. To actually forget something, prune it.
2. Prune (or let the AI do it). prune_decisions is dry-run by default — it shows what would go without touching anything:
python3 brain/server.py prune --before-days=90 # preview: old, resolved decisions
python3 brain/server.py prune --before-days=90 --apply # archive + remove for real
What it does on --apply:
- Archives each pruned decision to
~/.agent-brain/decisions.archive.jsonl(recoverable — nothing is hard-deleted). - Removes it from the live graph, so
pre_check/get_roadmapstop surfacing it. - Keeps the learning: rejections/failures and
roadmap/blocker-tagged decisions are never pruned. - Re-exports the records dir to reflect the change.
You can also just ask an agent: "prune decisions older than 3 months, dry run first" — it calls prune_decisions(before_days=90, dry_run=True), shows you the list, and only applies after you confirm.
Is unbounded growth a problem? Disk is trivial (~28 MB/year) and reads stay sub-millisecond (mtime-cached). The real cost is relevance noise — a 2-year-old rejected approach surfacing as a "similar failure". Prune resolved/old decisions periodically; keep the rejections, which are the cheapest lesson you'll ever get.
Agent Team
The repo includes 6 agent templates. Each has the Brain Protocol baked in:
| Role | File | Responsibility | Pinned model |
|---|---|---|---|
| Project Manager | project-manager.md |
Coordination, tracking, blockers | Haiku (cheap coordination) |
| Product Owner | product-owner.md |
PRDs, acceptance criteria | Sonnet |
| Principal Engineer | principal-engineer.md |
Architecture, SOLID, reviews | Opus (review is high-leverage) |
| Backend Engineer | backend-engineer.md |
API, services, data layer | Sonnet |
| Frontend Engineer | frontend-engineer.md |
UI, app logic, integration | Sonnet |
| QA Engineer | qa-engineer.md |
Test plans, validation, quality gates | Sonnet |
Model pins live in each template's model: frontmatter — change them to fit your budget. See Model Routing for the full strategy.
Scale by duplicating templates (e.g., backend-engineer-2.md).
Placeholders
Each template has {{ROLE_NAME}} / {{ROLE_NAME_LOWER}} placeholders:
| File | Placeholders |
|---|---|
project-manager.md |
{{PM_NAME}}, {{PM_NAME_LOWER}} |
product-owner.md |
{{PO_NAME}}, {{PO_NAME_LOWER}} |
principal-engineer.md |
{{PE_NAME}}, {{PE_NAME_LOWER}} |
backend-engineer.md |
{{BE_NAME}}, {{BE_NAME_LOWER}} |
frontend-engineer.md |
{{FE_NAME}}, {{FE_NAME_LOWER}} |
qa-engineer.md |
{{QA_NAME}}, {{QA_NAME_LOWER}} |
setup.sh offers to replace these interactively. Or do it manually:
sed -i 's/{{BE_NAME}}/Arjun/g; s/{{BE_NAME_LOWER}}/arjun/g' ~/.claude/agents/backend-engineer.md
Already have custom agents?
If you already have agent .md files, don't overwrite them. Instead, add the Brain Protocol block to each:
# Brain Protocol
Before starting any task:
1. Call `pre_check(agent="<name>", area="<area>", action_description="<plan>")`
2. If warnings exist, adjust approach
3. Call `log_decision(agent="<name>", repo="<repo>", area="<area>", action="<plan>", reasoning="<why>", files_touched=["<paths>"])`
After feedback:
4. Call `log_outcome(decision_id="<id>", outcome="<result>", outcome_by="<who>", reason="<why>")`
NON-NEGOTIABLE.
Critical: do NOT set the frontmatter tools: field. Claude Code subagents inherit ALL tools from the parent session — including every mcp__agent-brain__* tool — only when tools: is omitted. Setting it (even with ToolSearch included) turns it into a literal allowlist that silently strips MCP tools, because mcp__* is not a valid wildcard. Reference: Claude Code subagents — Available tools.
---
name: my-agent
description: ...
model: claude-sonnet-4-6
# No `tools:` — inherits everything from the parent session, including MCP.
# To restrict tools, use `disallowedTools:` instead.
---
What if I really must restrict tools? Add
ToolSearchto yourtools:allowlist and bootstrap brain tools at the top of every task withToolSearch(query="agent-brain", max_results=25). This is a fallback for the rare case where you genuinely need a tool denylist; for normal use, omittools:entirely.
For reviewers (PE, QA), also add:
5. Call `log_feedback(agent="<name>", decision_id="<their-id>", feedback="<detail>", severity="blocker|warning|info")`
setup.sh shows this snippet if it detects existing agents (choose [m] for manual).
Model Routing (quality per cost)
Spend the expensive model where mistakes are costly to undo; spend the cheap ones where mistakes are cheap to fix. The brain supports this in three layers:
1. Per-role model pins
Each agent template pins a model in frontmatter (model: claude-sonnet-4-6). Defaults follow the phase-cost logic:
| Phase | Work | Model | Why |
|---|---|---|---|
| Plan / architecture | System design, module boundaries, implementation plan | Fable / Opus | A wrong architecture costs days of rework; one good plan makes every later step cheaper |
| Scaffolding / boilerplate | Project setup, DI wiring, data classes, mappers | Sonnet / Haiku | Pattern-matching, not reasoning — executing against the plan, not deciding |
| Core / complex logic | Encryption flows, state machines, tricky concurrency | Opus, escalate on failure | Start mid-tier; escalate only when the data says so (see below) |
| Review | Architecture + code review of cheap-model output | Opus / Fable | Read-heavy, write-light — high leverage per output token |
| Tests / docs / polish | Unit tests against spec, KDoc, README | Sonnet / Haiku | Cheap-model territory |
2. model_routing config
Declare your routing once in ~/.agent-brain/config.json:
"model_routing": {
"plan": "fable",
"implement": "sonnet",
"review": "opus",
"boilerplate": "haiku",
"escalate": "fable"
}
Every pre_check response then ends with one line —
MODEL ROUTING: plan=fable | implement=sonnet | review=opus | boilerplate=haiku | escalate=fable —
so whatever agent is orchestrating spawns subagents on the right tier without you re-explaining the strategy each session. Omit the key and the line disappears.
3. Two-strikes escalation (data-driven)
Repeated failed attempts on a cheap model can cost more than one clean shot on a strong one — but you don't know which problems are "strong-model problems" until the cheap model stumbles. The brain already logs every rejection, so it applies the two-strikes rule automatically:
When the same agent has ≥2 rejected/failed decisions in the same area,
pre_checkreturns:ESCALATION HINT: 'arjun' has 2 rejected/failed decisions in 'auth'. Two-strikes rule: do NOT retry on the same model tier — re-spawn this task on fable.
The escalation target comes from model_routing.escalate (generic wording if unset). This is per-agent — another agent entering the same area is not escalated by someone else's failures.
4. Plan files as handoff artifacts
Pay for deep thinking once, reuse it across many cheap executions. The planner writes the plan to a file and logs it:
log_decision(agent="marcus", repo="my-app", area="payments",
action="Designed payment module architecture",
reasoning="...", plan_file="docs/plans/payments-plan.md")
Every later pre_check in that area surfaces it:
PLAN AVAILABLE: docs/plans/payments-plan.md (by marcus, 2026-06-12).
Read it before re-deriving the approach — execute against it, don't re-plan.
The pointer stays active while the decision is pending or accepted; a rejected plan stops being advertised.
Cost mechanics that matter as much as model choice
- Context discipline beats model choice. A Sonnet call with clean context beats an Opus call drowning in irrelevant files. SAN reads (~20% of raw cost) +
pre_check(past failures only, not full history) are the brain's context discipline. - The orchestrator burns its own tokens. Spawning a Sonnet subagent from an Opus session still pays Opus rates for coordination. Cheapest pattern: cheap main session as orchestrator, escalate via subagents — not an expensive main session delegating down.
- Fewer turns > cheaper tokens. For a genuinely complex task, a strong model finishing in fewer turns can land near mid-tier pricing. Don't be dogmatic — the two-strikes hint exists precisely to catch this case from real outcome data.
Rough split to aim for: ~70% of tokens on Sonnet-tier, ~25% on Opus-tier, ~5% on Fable-tier — that 5% (architecture + final review) determines whether the output is actually good.
Brain Protocol
Every agent must follow this before starting work:
1. pre_check(agent, area, action_description)
→ See past failures. Adjust approach if warnings.
2. log_decision(agent, repo, area, action, reasoning)
→ Record your plan before implementing.
3. [do the work]
4. log_outcome(decision_id, outcome, outcome_by, reason)
→ Record what happened after review.
This is enforced in every agent's .md file as NON-NEGOTIABLE.
The hooks (5)
Most "AI memory" tools are passive — they store things and hope the agent uses them. Agent Brain enforces via Claude Code hooks, so memory and SAN actually get used. setup.sh installs all five idempotently:
| Hook | Event | Purpose |
|---|---|---|
enforce_brain_protocol.py |
PreToolUse Edit|Write |
Blocks a code edit unless a decision was logged first — so the brain actually gets populated |
inject_brain_context.py |
SessionStart startup/resume/compact |
Re-injects the open-work digest after compaction — the amnesia fix |
remind_brain_before_research.py |
PreToolUse Workflow |
Nudges a brain check before expensive fan-out research |
route_read_to_san.py |
PreToolUse Read |
Routes raw Read of SAN-covered code → get_san (per-file, escalating) |
route_bash_to_san.py |
PreToolUse Bash |
Closes the shell bypass: cat/sed-dumping a SAN-covered file → get_san (leaves discovery grep alone) |
All fail-open (any error → allow, never break a session) and respect BRAIN_SKIP_ENFORCE=1.
Enforcement Hook
Text in .md files is advisory — agents can skip it. The enforcement hook makes it mandatory: any Edit/Write to code files is blocked if no log_decision was called in the last 30 minutes.
How it works:
log_decision()writes a marker file (~/.agent-brain/.last_decision_marker)- A PreToolUse hook fires before every Edit/Write
- If marker is missing or stale (>30min), the hook blocks with exit code 2
- Claude sees the block reason and calls
log_decisionbefore retrying
Skips (no block): .md, .json, .yaml, .toml, config files, .claude/, .git/, .san/, node_modules/, build/
Custom skip patterns — extend the built-in skip list with fnmatch globs in ~/.agent-brain/config.json:
{
"hook_skip_paths": [
"**/docs/**",
"**/.github/**",
"**/CHANGELOG*",
"**/migrations/**"
]
}
Patterns are matched against the absolute file path. The hook fails open: an invalid hook_skip_paths value is ignored silently rather than blocking your session.
Install (setup.sh does this automatically):
// ~/.claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "python3 /path/to/agent-brain/brain/hooks/enforce_brain_protocol.py",
"timeout": 5000
}
]
}
]
}
}
Fail-open: If the marker file is corrupt or the hook script errors, it allows the edit (exit 0). The hook never crashes your workflow — it only blocks when it's confident no decision was logged.
Bypass for direct edits: The hook fires on all Edit/Write — agents and user alike. To skip enforcement when you're editing directly, set
BRAIN_SKIP_ENFORCE=1in your shell before launching Claude Code, or add it to your settings.json env block:{ "env": { "BRAIN_SKIP_ENFORCE": "1" } }Agents spawned via the team system won't inherit this, so enforcement stays active for them.
Surviving compaction (the amnesia fix)
A long session eventually hits /compact, and the summarizer can drop the pending work and roadmap you discussed earlier — the agent then re-researches things the brain already knows. Two hooks close that gap by pushing the brain's open-work digest back into context at the exact moments memory is born or lost:
| Hook | Event | What it does |
|---|---|---|
inject_brain_context.py |
SessionStart (startup/resume/compact) |
Injects the ranked open-work digest (pending + roadmap-tagged decisions) via additionalContext. source=compact is the post-compaction re-entry — it injects a fuller digest so the roadmap survives the summary. |
remind_brain_before_research.py |
PreToolUse matcher Workflow |
Soft, non-blocking nudge to check get_roadmap/query_decisions before a fan-out research workflow. Doesn't block — the push digest already put the answer in context. |
This is the same digest get_roadmap returns, so push (hook) and pull (tool) never diverge. The digest is token-budgeted (top ~15 items, action truncated) — ~1k tokens once per compaction.
Tagging durable work: put roadmap or blocker in a decision's area (e.g. area="kmp-foundation/roadmap") and it floats to the top of the digest above transient pending edits. Untagged pending decisions still appear, newest first — so the fix works on an existing brain with no migration.
Optional hard research gate: the reminder is soft by default. To block Workflow until a brain read happened this session, set "research_gate": "hard" in ~/.agent-brain/config.json. A pre_check/query_decisions/get_roadmap call satisfies the gate (it writes ~/.agent-brain/.last_query_marker); BRAIN_SKIP_ENFORCE=1 bypasses it. Soft is recommended — a hard gate can false-positive when the brain genuinely has nothing on the topic.
Install (setup.sh wires every brain hook idempotently, backing up settings.json.bak):
// ~/.claude/settings.json
{
"hooks": {
"SessionStart": [
{ "matcher": "startup|resume|compact",
"hooks": [{ "type": "command",
"command": "~/.agent-brain/.venv/bin/python /path/to/agent-brain/brain/hooks/inject_brain_context.py",
"timeout": 15000 }] }
],
"PreToolUse": [
{ "matcher": "Workflow",
"hooks": [{ "type": "command",
"command": "~/.agent-brain/.venv/bin/python /path/to/agent-brain/brain/hooks/remind_brain_before_research.py",
"timeout": 10000 }] }
]
}
}
Making SAN the default read path
SAN is far cheaper for reading code (see Is SAN worth it?) — but agents kept defaulting to raw reads because nothing routed them to SAN, and a tool ladder isn't obvious. These changes close the gap (none touch the SAN format or quality):
The tool ladder (the standing rule agents are taught + nudged toward):
| Step | Goal | Use | Not |
|---|---|---|---|
| Find | which file/symbol has X | query_san / semantic_search_nodes |
grep -r raw source |
| Read/understand | what a file/class does | get_san(file_path="<abs>") (sig/full) |
cat/head/sed the file |
| Exact literal | a precise string/line | grep/rg on the file |
get_san (SAN drops literals) |
| Edit | change exact bytes | raw Read then Edit |
— |
How it's enforced:
| Mechanism | What it does |
|---|---|
get_san accepts absolute paths |
Pass the exact path you got from a discovery hit — repo auto-detected, no repo-name lookup. Removes the friction tax. |
route_read_to_san.py (PreToolUse Read) |
Raw Read of a fresh-.san code file → nudge toward get_san. Per-file dedupe (every file's first read is nudged, not just the session's first). Escalates: 1st read soft-allow; a 2nd+ raw read of the same file gets a stronger nudge, or a block under hard mode. |
route_bash_to_san.py (PreToolUse Bash) |
Closes the shell bypass: cat/head/sed/tail dumping a fresh-.san source file → same nudge/escalation. Leaves grep/rg/awk (literal search) and discovery-grep-across-files alone — those are the right tools, not bypasses. |
pre_check(repo=...) surfaces coverage |
Appends SAN AVAILABLE: repo 'X' has N files compiled… so the read path is in view at check-before-work |
No comments yet
Be the first to share your take.