Arbiter
Arbiter makes the moment of judgment accountable instead of assumed. It’s a Python MCP server plus a Claude Code PreToolUse hook: it records AI-generated ADRs and design specs as Markdown with frontmatter, and enforces a review sequence before any code is written: AI critique, then human issue-disposition, then sign-off. Approved documents can be published to a Nexus sink.
When an assistant produces a confident spec, the easiest thing to do is approve it. Review turns into ceremony: a green check on text nobody really read. Arbiter forces judgment to happen where it’s cheap and where it leaves a record. Until a spec is approved and stamped with a content hash, every Write/Edit/MultiEdit on a non-exempt source path is blocked. The gate is active during implementation: begin_implementation arms it, end_implementation disarms it.
In short: a ledger that makes “who approved what, and why” a recorded, attributable act, so you can’t rubber-stamp your way past it.
Core concepts
- The gate. Until a spec is
approvedand content-hash-stamped, file-edit tools on non-exempt paths are blocked.docs/**andtests/**are allow-globbed by default (configurable viaallow_globs). - Arm / disarm.
begin_implementationarms the gate for a specific spec;end_implementationdisarms it. The gate is only enforced during implementation. - Accountable review flow.
critiqueruns AI review and opens issues in a sidecar; the human edits the body to address each;approvetakes per-issue dispositions, verifies the body actually changed, then stamps the content hash and setsstatus=approved. - Content-hash stamping + tamper detection. Approval binds the spec to a hash;
statusreports state and detects tampering. - No database. All state lives in Markdown files under
ARBITER_DOCSplus a small.arbiter/marker underARBITER_ROOT. - Optional Nexus publish.
publishpushes an approved doc to a configured Nexus sink — a safe no-op when not configured. - The same gate from a CLI. You do not need MCP to run it:
arbiter record,status,critique,approve,check-gate. The CLI calls the same functions the MCP server does, so a human and an agent cannot reach different verdicts on the same spec.
Quickstart
Arbiter is a Python package; it runs as an MCP server and a PreToolUse hook. Commands transcribed exactly from the source repo README.
Install
pip install -e ".[dev]"Register the MCP server (.mcp.json)
{ "mcpServers": { "arbiter": { "command": "python", "args": ["-m", "khala.arbiter.server"], "env": { "ARBITER_ROOT": "/abs/path/to/your/project", "ARBITER_DOCS": "/abs/path/to/your/project/docs", "ANTHROPIC_API_KEY": "sk-ant-..." } } }}ARBITER_ROOT is the project root where .arbiter/ state lives (defaults to .); ARBITER_DOCS is where spec/ADR Markdown is written (defaults to $ARBITER_ROOT/docs); ANTHROPIC_API_KEY is required only for the built-in AnthropicCritic.
Register the PreToolUse hook (.claude/settings.json)
{ "hooks": { "PreToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "python /abs/path/arbiter/hooks/pretooluse_gate.py" } ] } ] }}The hook reads the tool payload from stdin and exits 0 (allow) or 2 (block).
How-to
Record → critique → approve → implement
The first-consumer flow, transcribed from the README:
1. record → record("spec", "Playlist Self-Update") → SPEC-playlist-self-update2. critique → critique("SPEC-playlist-self-update") → opens issues (e.g. I-001)3. fix + disposition → human edits the body to address each issue4. approve → approve(id, [{"issue_id":"I-001","disposition":"accepted"}], "reviewer") verifies body changed, stamps content hash, status=approved5. begin_implementation → arms the gate; Write/Edit on src/ paths now allowed6. end_implementation → disarms the gate when coding is completeCheck whether paths are currently allowed
Use check_gate to query whether a list of paths would pass the gate, and status to report state and detect tampering — useful before starting an edit session.
Publish an approved doc to Nexus (optional)
Add to .arbiter/config.yaml:
nexus: url: "https://your-nexus-instance/ingest"Then call publish. Without this config it returns {"published": false, "reason": "nexus not configured"}.
Reference
- Source:
arbiter/in the Khala monorepo (README.md; roadmap inBACKLOG.md). - Ten MCP tools:
record,critique,approve,status,supersede,begin_implementation,end_implementation,check_gate,index,publish. - MVP boundaries: the
Bashtool is not gated (onlyWrite/Edit/MultiEdit); single-approver flow; no database.