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 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.
main without review
.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.
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.
__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.
.envenv-file-protection blocks bash reads of
.env*; blocked-paths-enforcer
denies writes against blocked_paths. Both layers fail
closed.
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.
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.
.rea/HALT exists. Fail-closed. Only
rea unfreeze clears the HALT file.
.rea/ is always
blocked regardless of policy; operator paths layer on top.
injection.suspicious_blocks_writes: true (the
bst-internal posture), a suspicious verdict on
a write/destructive tool denies instead of warning.
redact.patterns[]. Redact before the call goes downstream.
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.
/codex-review/codex-review rea hook push-gate git push 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.
/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.
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.
{
"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
}
]
} -
halt_reasonanddownstreams[].last_errorsurface asnull. 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 —redactSecretsreplaces known patterns,classifyInjectionreplaces any non-cleandiagnostic with theINJECTION_REDACTED_PLACEHOLDERtoken, and oversize values are bounded before scanning. - Short-circuit is documented and audited — the
__rea__healthhandler 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.
| 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+onerroron the SDKStdioClientTransportnull the client before the nextcallToolcan 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_BLOCKERfires 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.
SessionBlockerTrackersubscribes to breakeronStateChangeevents; recovery (transition toclosed) re-arms the emit.
Env safety
- Fixed allowlist. Neutral OS vars only —
PATH,HOME,TZ,NODE_OPTIONS, … — are forwarded to child processes. -
env_passthroughrefuses secret-looking names. The schema rejects*_TOKEN,*_KEY,*_SECRET, and other patterns — secrets must be named explicitly. -
$${VAR}placeholders. Registryenv: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.
| 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
| 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.
Full hook suite, client-appropriate CLAUDE.md template, engagement-specific policy defaults.
Org-wide policy defaults, BST CLAUDE.md template, BST commit conventions,
injection.suspicious_blocks_writes: true.
Matches bst-internal but defaults
review.codex_required: false — first-class opt-out.
Adds Shadow DOM guardrails and Custom Elements Manifest integrity checks on top of the base.
DCO sign-off enforcement, contributor-friendly CLAUDE.md template, stricter attribution rules.
Matches open-source but defaults
review.codex_required: false.
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 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.
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.
| 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
PATH — rea 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 onlyGET /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.jsonlis append-only and tamper-evident. Tail integrity verified byrea audit verify. - Policy re-read every invocation. Any edit to
.rea/policy.yamltakes 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.