Skip to content Home About

Claude & Codex,
on a leash.

Single-package governance layer for Claude Code — MCP gateway, layered safety hooks, automatic Codex review on every push, hash-chained audit, and a one-file kill switch. One install. Works in any repo.

$ npx @bookedsolid/rea init
Scaffold any repo. Idempotent. Node 22+.
View on GitHub
rea — gateway + hooks
# Initialize governance in the current repo
$ npx @bookedsolid/rea init
.rea/policy.yaml — autonomy L0–L3 with hard ceiling
.claude/hooks/ — PreToolUse + PostToolUse gates
.claude/commands/ — /rea, /review, /codex-review, /freeze, /halt-check
.mcp.json — wired to run rea serve as gateway
.husky/commit-msg — attribution + message validation
.husky/pre-push — stateless Codex gate on every push
# Verify the install and start the gateway
$ rea doctor
Hooks, policy, mcp wiring, audit chain, codex CLI — all pass
$ rea serve
__rea__health live · 7 profiles · per-downstream state
$ rea status
Gateway live · Autonomy: L1 · Max: L2 · HALT: inactive

Four verbs. One package. Zero exceptions.

REA gates and audits every agentic tool call against operator-defined policy. That is the whole product. Gateway and hook layers fail closed independently.

Gates every MCP tool call

rea serve is an MCP server that proxies downstream servers through a multi-layer middleware chain. Native tools and proxied downstream calls are classified, policy-checked, redacted, audited, and size-capped before execution. Claude Code owns the gateway lifecycle via .mcp.json.

$ rea serve

Enforces policy and autonomy ceilings

.rea/policy.yaml uses a zod-strict schema — unknown fields are rejected, not ignored. Autonomy level (L0–L3), a hard ceiling (max_autonomy_level), blocked paths, redaction and injection tuning, review knobs. Re-read on every invocation — edits take effect on the next tool call.

$ .rea/policy.yaml

Reviews diffs adversarially

Codex is a first-class part of REA, not a bolt-on. .husky/pre-push runs rea hook push-gate, which executes codex exec review --json against the diff on every git push and blocks on [P1] findings (and [P2] by default). Stateless — no cache, no SHA matching, no receipt to fabricate. Verdict and findings land in .rea/last-review.json for the auto-fix loop.

$ /codex-review

Halts instantly — one file

.rea/HALT is a single file. If it exists, every tool call is denied at the middleware and hook layers. rea freeze --reason "…" creates it; rea unfreeze --reason "…" removes it. No daemon, no UI. Both calls produce audit entries — the middleware never clears HALT on its own.

$ rea freeze --reason "…"

Six real failure modes REA prevents.

Every row is a thing an agentic toolchain can do wrong. Every second column is what REA does about it — by default, at install, with no extra wiring.

Problem
Agent pushes to main without review
REA
.husky/pre-push delegates to rea hook push-gate, which runs Codex on the diff and blocks on [P1] findings (and [P2] by default). Codex runs fresh on every push — no cache, no receipt to fabricate. .rea/last-review.json captures the verdict for the auto-fix loop.
Problem
Child MCP process dies silently
REA
The eager supervisor wires onclose + onerror on the SDK transport and nulls the client before the next call. The circuit breaker trips; a 30-second flap guard prevents zombie reuse.
Problem
Downstream leaks a secret in an error string
REA
__rea__health sanitizes diagnostic strings to null by default. Policy opt-in (gateway.health.expose_diagnostics: true) is required to expose them — and still runs redaction and injection classification before emit.
Problem
Agent reads .env
REA
env-file-protection blocks bash reads of .env*; blocked-paths-enforcer denies writes against blocked_paths. Both layers fail closed.
Problem
Runaway autonomy escalation
REA
autonomy_level > max_autonomy_level is rejected at parse time — the ceiling is set by the human operator and never by an agent. When things go sideways, .rea/HALT kills everything in one file.
Problem
Codex review skipped by env var
REA
REA_SKIP_PUSH_GATE and REA_SKIP_CODEX_REVIEW are value-carrying audited skips — they require a reason string and write the variant to the audit record. HALT always wins. REA_ALLOW_CONCERNS=1 overrides only the [P2] block. Skipping a review is not a review.

Thirteen layers. Every tool call. Fail closed.

Every native MCP tool call AND every proxied downstream call flows through one chain. The order matters — each layer fails closed, and denial at any layer is permanent.

Claude Code
host agent
rea serve
multi-layer middleware chain
Downstream
filesystem, github, etc.
1
audit.enter
Hash-chained record start. Every invocation — including kill-switch denials — is logged. The audit layer is always outermost.
2
kill-switch
Deny if .rea/HALT exists. Fail-closed. Only rea unfreeze clears the HALT file.
3
tier
Classify the tool as read, write, or destructive. Re-derived from tool name on every call — never trusts mutable context.
4
policy
Autonomy gate. Compare the tool’s tier against the current L0–L3 autonomy level. Deny if the tier exceeds what the policy allows.
5
blocked-paths
Scan tool arguments for protected paths. .rea/ is always blocked regardless of policy; operator paths layer on top.
6
rate-limit
Token bucket per downstream server. Denied calls do not consume budget.
7
circuit-breaker
Trip on downstream failure. Prevents cascading errors when an MCP server is unhealthy; resets on recovery.
8
injection
Prompt-injection heuristics on tool arguments and any untrusted input. With injection.suspicious_blocks_writes: true (the bst-internal posture), a suspicious verdict on a write/destructive tool denies instead of warning.
9
redact (args)
Scan tool arguments for credentials — AWS keys, GitHub tokens, API keys, PEM keys, Discord tokens, and operator-supplied redact.patterns[]. Redact before the call goes downstream.
execute
Only if layers 1–9 pass does the tool actually execute on the downstream server.
10
result-size-cap
Bound the response. Oversized results are truncated with a note; large payloads never bloat the context window.
11
redact (result)
Scan the downstream result for the same credential patterns. Secrets never reach the AI.
12
injection (result)
Re-run prompt-injection heuristics on the downstream result. Catches injection payloads embedded in returned content — the layer that defeats “malicious file reads agent instructions” attacks.
13
audit.exit
Hash-chained record close. Captures outcome, redaction deltas, and timing. Tail hash integrity is verified by rea audit verify.

.rea/ is hardcoded as always-blocked. Policy is re-read on every invocation. __rea__health is the one documented short-circuit exception — see below.

Codex is a first-class adversarial reviewer. Not a bolt-on.

The BST engineering process bakes adversarial review into the default flow — and REA ships it out of the box. Claude plans and builds. Codex reviews. Both are audited.

01
Plan
Claude Opus
Full middleware chain
02
Pre-impl review
/codex-review
Audited
03
Build
Claude Opus
Full middleware chain
04
Adversarial review
/codex-review
Audited, redacted, kill-switched
05
Push gate
rea hook push-gate
Automatic on every git push
The codex-adversarial agent

Part of the curated ten-agent roster. Wraps /codex:adversarial-review. The rea-orchestrator delegates to it after any non-trivial change — independent perspective, not self-review.

The /codex-review slash command

One of the five shipped commands. Produces an audit entry with the request summary, response summary, and pass / concerns / fail signal.

The rea hook push-gate command

Stateless. .husky/pre-push calls it on every git push. It runs codex exec review --base <ref> --json, parses the verdict, and exits 0 on pass, 1 on HALT, or 2 on a [P1] finding (or [P2] when concerns_blocks is true). Codex runs fresh every push — no cache, no SHA matching, no receipt to fabricate. Verdict and findings land in .rea/last-review.json for the auto-fix loop.

__rea__health — the one tool that always answers.

A built-in MCP tool that appears in every listTools response regardless of downstream state. The handler short-circuits the middleware chain — it is callable under HALT and at any autonomy level — because it is the tool an operator reaches for when everything else is frozen. Every invocation still writes an audit record.

response (sanitized, default)
{
  "version": "0.12.0",
  "session_id": "ses_01hxyz...",
  "uptime_seconds": 1847,
  "halt": { "active": false, "halt_reason": null },
  "policy": {
    "profile": "bst-internal",
    "autonomy_level": "L1",
    "max_autonomy_level": "L2"
  },
  "downstreams": [
    {
      "name": "github",
      "connected": true,
      "healthy": true,
      "circuit_state": "closed",
      "tools_count": 47,
      "open_transitions": 0,
      "last_error": null
    }
  ]
}
Wire response is sanitized by default
  • halt_reason and downstreams[].last_error surface as null. Full diagnostic detail lives in the audit record’s metadata — local disk, hash-chained, not LLM-reachable.
  • Operators who need error strings on the MCP wire can opt in via gateway.health.expose_diagnostics: true. Opt-in mode still runs the full sanitizer pass — redactSecrets replaces known patterns, classifyInjection replaces any non-clean diagnostic with the INJECTION_REDACTED_PLACEHOLDER token, and oversize values are bounded before scanning.
  • Short-circuit is documented and audited — the __rea__health handler writes its own audit record even though it bypasses the chain.

.rea/serve.state.json — per-downstream, on disk.

The authoritative live source for per-downstream health. Written atomically via temp+rename on every circuit transition or supervisor event, debounced through a 250 ms trailing timer. rea status is a read-only consumer of this file.

Per-downstream live state fields and meanings
Field Type Meaning
name string Registry server name
connected boolean MCP client currently holds an open stdio transport
healthy boolean Gateway considers the server safe to route calls to
circuit_state closed | open | half-open Current breaker position
retry_at ISO timestamp | null Next allowed half-open probe, when open
last_error string | null Bounded, redacted diagnostic from the most recent failure
tools_count integer | null Tool count from the last successful tools/list
open_transitions integer Cumulative circuit-open events in this session
session_blocker_emitted boolean Whether SESSION_BLOCKER has fired for this server yet

A newly-started rea serve whose predecessor crashed without cleanup detects the abandoned state file and takes over ownership rather than stalling forever. Older state files degrade gracefully: downstreams surfaces as null with an upgrade hint.

Eager death detection. Zero process.env passthrough.

Downstream MCP servers run as child processes over stdio. REA supervises them eagerly and refuses to hand secrets to them by default.

Supervisor

  • Eager callbacks. onclose + onerror on the SDK StdioClientTransport null the client before the next callTool can use a stale handle.
  • 30-second flap guard. Refuses a second reconnect that lands too quickly after the previous one — the child is clearly unhealthy and the circuit breaker is a better place to handle it.
  • SESSION_BLOCKER fires exactly once. Per (session_id, server_name), with a LOUD structured log line on emit. A new session drops every counter and starts fresh.
  • Tracks circuit opens. SessionBlockerTracker subscribes to breaker onStateChange events; recovery (transition to closed) re-arms the emit.

Env safety

  • Fixed allowlist. Neutral OS vars only — PATH, HOME, TZ, NODE_OPTIONS, … — are forwarded to child processes.
  • env_passthrough refuses secret-looking names. The schema rejects *_TOKEN, *_KEY, *_SECRET, and other patterns — secrets must be named explicitly.
  • $${VAR} placeholders. Registry env: values may reference host env vars. Secret-looking values are redacted in logs by default.
  • Unresolved placeholder = unhealthy. A $${VAR} whose host variable is unset is treated as fatal — the downstream is marked unhealthy rather than handed an unresolved placeholder.

Eleven hooks. Each does one thing.

Every hook is set -euo pipefail with a HALT check near the top. Hooks run at Claude Code tool-invocation time, independently of the gateway — both layers fail closed. The push-review gate is no longer a hook script: .husky/pre-push calls rea hook push-gate inline.

The 11 REA hooks, their events, and one-line purposes
Hook Event Purpose
dangerous-bash-interceptor PreToolUse: Bash Block categories of destructive shell commands
env-file-protection PreToolUse: Bash Block reads of .env* files
dependency-audit-gate PreToolUse: Bash Verify packages exist on the registry before install
attribution-advisory PreToolUse: Bash Block commits / PRs containing AI attribution markers
pr-issue-link-gate PreToolUse: Bash Advisory warn when gh pr create has no linked issue
security-disclosure-gate PreToolUse: Bash Route security-keyword gh issue create to private disclosure
secret-scanner PreToolUse: Write|Edit Scan file writes for credential patterns
settings-protection PreToolUse: Write|Edit Block agent writes to .claude/settings.json, hook dirs, policy
blocked-paths-enforcer PreToolUse: Write|Edit Enforce blocked_paths from policy
changeset-security-gate PreToolUse: Write|Edit Guard changesets against GHSA leaks and malformed frontmatter
architecture-review-gate PostToolUse: Write|Edit Flag edits crossing architectural boundaries (advisory)

Five commands. No more.

Copied into .claude/commands/ during rea init. Each produces an audit entry.

/ rea

Session status — autonomy level, HALT state, last audit entries, next action.

/ review

Invoke the code-reviewer agent on current changes.

/ codex-review

Invoke the codex-adversarial agent → /codex:adversarial-review.

/ freeze

Prompt for a reason and write .rea/HALT. All tool calls deny immediately until unfrozen.

/ halt-check

Verify every middleware and hook respects HALT. Read-only smoke test.

Ten curated agents. Seven profiles.

rea init installs ten curated agents into .claude/agents/. Profiles layer additional specialists on top and set sensible policy defaults.

The curated ten

The ten curated agents that ship with REA
Agent Role
rea-orchestrator Single entry point for non-trivial tasks
code-reviewer Review current changes; structured findings
codex-adversarial Wrap /codex:adversarial-review
security-engineer Threat model, secrets, disclosure
accessibility-engineer WCAG, semantic HTML, keyboard support
typescript-specialist Strict mode, generics, no any
frontend-specialist Components, CSS, client-side patterns
backend-engineer Servers, data access, APIs
qa-engineer Tests, coverage, edge cases
technical-writer Docs, READMEs, release notes

Seven profiles

Pass --profile <name> to layer additional specialists on top of the curated ten and set sensible policy defaults.

client-engagement
Client consulting projects

Full hook suite, client-appropriate CLAUDE.md template, engagement-specific policy defaults.

bst-internal
BST’s own repositories

Org-wide policy defaults, BST CLAUDE.md template, BST commit conventions, injection.suspicious_blocks_writes: true.

bst-internal-no-codex
BST without a Codex CLI

Matches bst-internal but defaults review.codex_required: false — first-class opt-out.

lit-wc
Lit / Web Components

Adds Shadow DOM guardrails and Custom Elements Manifest integrity checks on top of the base.

open-source
Public repositories

DCO sign-off enforcement, contributor-friendly CLAUDE.md template, stricter attribution rules.

open-source-no-codex
OSS without a Codex CLI

Matches open-source but defaults review.codex_required: false.

minimal
Lean governance baseline

The smallest sensible install — curated ten only, no extra specialists, permissive policy defaults.

The -no-codex variants default review.codex_required: false for teams without a Codex CLI on the bench — a first-class opt-out rather than relying on REA_SKIP_CODEX_REVIEW.

One file. Every tool call denied.

rea freeze creates .rea/HALT. Every MCP tool call is denied at the middleware and hook layers until an operator runs rea unfreeze. Every denied call is still audited.

rea freeze

Writes .rea/HALT with the reason, operator, and timestamp. Every subsequent tool call — gateway or hook — is denied. The audit chain keeps recording.

$ rea freeze --reason "incident triage; .env write anomaly"
.rea/HALT created
! All tool calls denied until unfrozen

rea unfreeze

Removes .rea/HALT and resumes tool call processing. Requires explicit human action with a reason — the middleware never clears HALT on its own. Both calls produce audit entries.

$ rea unfreeze --reason "false alarm — resolved"
.rea/HALT removed · Gateway resumed

Zod-strict policy. Re-read every invocation.

Unknown fields are rejected, not ignored. autonomy_level > max_autonomy_level is rejected at parse time. The ceiling is set by the human operator and never by an agent.

Policy file reference for .rea/policy.yaml
Field Type Purpose
version string, "1" Schema version; only "1" accepted in the current major
profile string Profile name from profiles/ (e.g. bst-internal)
autonomy_level L0|L1|L2|L3 Current autonomy. L0 = read-only; L3 = full tool access
max_autonomy_level L0|L1|L2|L3 Hard ceiling. autonomy_level cannot exceed this
promotion_requires_human_approval boolean Require operator confirmation to raise autonomy. Default true
blocked_paths string[] Glob patterns. .rea/ is always blocked regardless of this list
block_ai_attribution boolean Enforce no-AI-attribution in commits and PR bodies
context_protection.delegate_to_subagent string[] Commands that must run in a subagent context to preserve parent context window
notification_channel string Optional Discord webhook URL. Empty string = no notifications
review.codex_required boolean When true, the push gate requires a healthy codex CLI on PATHrea doctor fails fast if missing. Default true
review.concerns_blocks boolean When true, [P2] findings block the push (in addition to the always-blocking [P1]). Override per-push with REA_ALLOW_CONCERNS=1. Default true
review.timeout_ms number Codex-exec budget for the push gate. Default 1800000 (30 min, raised from 10 min in 0.12.0)
review.last_n_commits new number Default base-resolution to HEAD~N for branches with many commits. Overridden by --last-n-commits or --base
injection.suspicious_blocks_writes boolean bst-internal posture — suspicious verdict on a write/destructive tool denies instead of warning. Default false
redact.patterns[] string[] User-supplied secret patterns; vetted via safe-regex at load
redact.match_timeout_ms number Per-call regex budget. Default 100
gateway.health.expose_diagnostics boolean When true, __rea__health emits redacted+classified diagnostic strings on the wire. Default false

Loopback-only metrics. Hash-chained audit.

Observability without new attack surface. Security invariants you can point to in a threat model.

Metrics

  • Prometheus endpoint, opt-in. Set REA_METRICS_PORT=9464; no silent listeners.
  • Loopback-only. Binds to 127.0.0.1, serves only GET /metrics, fixed-body 404 on everything else, no TLS — scrape through SSH or a reverse proxy.
  • What it exposes: per-downstream call and error counters, in-flight gauge, audit-lines-appended counter, circuit-breaker state gauge, seconds-since-last-HALT-check gauge.
$ REA_METRICS_PORT=9464 rea serve

Security

  • Hash-chained audit. .rea/audit.jsonl is append-only and tamper-evident. Tail integrity verified by rea audit verify.
  • Policy re-read every invocation. Any edit to .rea/policy.yaml takes effect on the next tool call.
  • .rea/ always blocked. Hardcoded — cannot be unblocked from policy.
  • npm OIDC provenance. Published via OIDC, not long-lived tokens. Attestation is verifiable from the npm badge.
  • Gateway and hook layers fail closed independently. Bypassing one does not disable the other.

Already on REA 0.10.x?

Run rea upgrade. The 0.11.0 release replaced the cache-attestation push gate with a stateless Codex gate — rea cache, rea audit record codex-review, policy.review.cache_max_age_seconds, and policy.review.allow_skip_in_ci are gone. rea upgrade strips removed policy keys with a timestamped backup, refreshes the husky/fallback hook bodies via the legacy marker path, and migrates skip env vars to REA_SKIP_PUSH_GATE + REA_SKIP_CODEX_REVIEW (value-carrying, audit-logged). Coming from the original @bookedsolid/reagent? npx @bookedsolid/rea init --from-reagent is still supported.

Install REA in any repo.

One command scaffolds policy, hooks, slash commands, gateway wiring, and a commit-msg hook. Node 22+.

$ npx @bookedsolid/rea init
$ rea doctor