Feature Request: Built-in Peer Model Audit Gate for Code Quality Enforcement
Summary
I've been running a production workflow where GPT (acting as an independent auditor) reviews Claude Code's output before any work can be marked complete. After several months of iteration, this cross-model audit loop has proven significantly more effective than prompt-based instructions alone. I'd like to propose this as a native Claude Code feature.
---
The Problem: Single-Model Blind Spots
Claude Code has a well-documented tendency to:
- Mark work as complete when edge cases remain untested (
test-gap) - Drift from original claims in evidence documentation (
claim-drift) - Self-validate outputs that a peer reviewer would immediately flag
Instruction-based corrections ("always write tests for hook functions") are absorbed but fade across sessions or are rationalized away under time pressure. The model cannot reliably catch its own blind spots through self-review.
---
The Solution: Cross-Model Audit Gate (Working Implementation)
I've implemented this as a Claude Code hook workflow:
Architecture
PostToolUse (any file edit) -> index.mjs
|
+- watch_file + trigger_tag?
| |
| +- audit.lock exists? -> skip (already running)
| +- spawn audit.mjs (detached, background)
| -> hook returns immediately (non-blocking)
| -> audit-bg.log streams real-time NDJSON output
| -> audit.lock created (PID + TTL)
| -> agent auto-registers Cron watcher
|
| ... audit runs in background (Codex review) ...
|
| audit.mjs completes:
| -> gpt.md created/updated
| -> respond.mjs (tag sync)
| -> audit.lock deleted
| v
| [Detection: Cron watcher OR next PostToolUse]
| v
| +--- [agree_tag] ----- retrospective.mjs
| | v
| | retro-marker set
| | v
| | session-gate blocks Bash
| | v
| | HITL retrospective (user + AI)
| | v
| | echo session-self-improvement-complete
| | v
| | git commit allowed
| |
| +--- [pending_tag] -> respond.mjs --auto-fix -> correction
|
+- gpt.md newer? -> respond.mjs (auto-sync)
+- planning file? -> respond.mjs --gpt-only
+- quality rule? -> run command (ESLint, npm audit, ...)
Reentrance: FEEDBACK_LOOP_ACTIVE env var
Lock: audit.lock with PID + TTL (30min) -- prevents concurrent audits
Key Design Decisions
1. Async Non-Blocking Audit (Critical for UX)
The audit runs as a detached background process (spawn(detached: true)). The PostToolUse hook returns immediately -- the user continues working while Codex reviews in the background. Real-time streaming via NDJSON line-by-line parsing writes agent messages to audit-bg.log. A lock file with TTL prevents concurrent audits and handles PID recycling.
2. Session Reuse (Critical for Performance)
The auditor (GPT) maintains a single session across all audit rounds. On first run, it reads the full codebase. On subsequent rounds, it only reads the delta (changed files + new evidence). Without session reuse, every audit requires full codebase re-read -- making this impractical at scale.
3. Gate Enforcement (Not Advisory)
The audit result is not a suggestion. Work cannot be marked [agree_tag] until the peer model explicitly approves. Claude cannot self-promote past the gate.
4. Structured Evidence Format
Claude submits a Claim + Changed Files + Test Command + Test Results package. GPT responds with specific rejection codes (test-gap [major], claim-drift [minor]) referencing exact file:line locations. This forces precision on both sides.
5. HITL Retrospective Gate
After all items reach agree_tag, retrospective.mjs sets a marker. session-gate.mjs (PreToolUse hook) blocks Bash/Agent until the human-in-the-loop retrospective is completed. Only echo session-self-improvement-complete clears the gate.
6. Facade Pattern for Policy
Prompt templates are lean facades (~30 lines) that reference policy files via {{REFERENCES_DIR}}. To change audit criteria, teams edit templates/references/{locale}/rejection-codes.md -- no code changes needed.
Real Example: The Non-Standard Tag Vicious Cycle
A subtle bug demonstrated the importance of structural enforcement over instruction:
The audit prompt told Codex to use human-readable labels as judgment types. But the consensus engine expected machine-parseable status tags. Codex used a non-standard tag that the engine didn't recognize -- causing an infinite loop:
Codex writes [non-standard-tag] -> respond.mjs searches for [pending_tag] -> no match
-> "no response" -> next audit -> same result -> infinite loop
Fix required two structural changes:
{{REFERENCES_DIR}}-- Codex couldn't readoutput-format.mdbecause the path was wrong (relative to wrong root)- Explicit tag enforcement in the prompt -- only
[agree_tag]or[pending_tag]allowed, all other labels banned
This bug could not be caught by self-review. It required structural analysis of the tag flow across three components.
---
Proposed Native Feature
Option A: --audit-mode flag for Claude Code sessions
claude --audit-mode --audit-model gpt-4o --audit-session-persist
- After each significant output, a second model instance reviews the work
- Session is reused across the project lifecycle (not reset per review)
- Claude cannot mark tasks complete until the audit model approves
Option B: Native Multi-Agent Review Workflow API
A first-class workflow primitive:
# .claude/audit.yml
auditor:
model: claude-opus-4-5 # or external model via API
session: persist # maintain context across rounds
gate: required # block completion without approval
format: structured # claim/evidence/verdict schema
async: true # non-blocking background execution
Option C: Structured Self-Critique Pass
A lighter version: before marking any task complete, Claude is required to run a separate "adversarial review" pass with an explicitly contrarian system prompt. Less powerful than a true peer model, but deployable immediately without multi-model infrastructure.
---
Why This Matters for the Product
The core insight is: behavioral constraints enforced by structure are more reliable than behavioral constraints enforced by instruction.
You cannot instruct Claude to consistently catch its own test-gap issues across sessions. But you can build a gate that makes it structurally impossible to proceed until a peer model confirms the gap is closed.
This is especially valuable for:
- Enterprise code review workflows where quality gates are mandatory
- Long-running agentic tasks where drift accumulates over many steps
- Security-sensitive code where self-validation is insufficient by design
---
Implementation Notes from the Working System
- Async audit:
spawn(detached: true)+child.unref()-- hook returns immediately, Codex runs in background. Lock file (audit.lock) with PID + 30-minute TTL prevents concurrent runs and handles PID recycling - Streaming output:
streamCodexOutput()-- NDJSON events parsed line-by-line in real-time, agent messages written toaudit-bg.logas they arrive - Cross-platform:
cli-runner.mjshandles Windows.cmd/.bat/.ps1for both sync (spawnResolved) and async (spawnResolvedAsync) spawning - Hook trigger:
PostToolUse(Edit|Write)watching fortrigger_taginwatch_file-- fully configurable - Session persistence: Thread ID stored in
session.id-- session resets only when all items are approved - Auto-sync: When
gpt.mdis newer thanwatch_file,respond.mjspromotesagree_tagitems directly and feedspending_tagcorrections back toclaude -pautomatically - Facade prompts:
{{REFERENCES_DIR}}resolves to correct path relative to repo root at runtime -- policy files readable by any auditor model regardless of installation path - Tag enforcement: Audit prompt explicitly lists allowed tags and bans non-standard labels
- Quality gates:
quality_rulesinconfig.jsonmap file patterns to lint/audit commands -- run inline on every matching edit - Session gate (HITL):
session-gate.mjs(PreToolUse) blocks Bash/Agent until retrospective completion -- session-aware (only blocks the completing session) - Shared context:
context.mjs-- single source for config, paths, parsers, i18n (no duplication across 6+ scripts) - i18n: All user-facing strings externalized to
locales/{en,ko}.json-- configurable locale viaplugin.locale
---
References
Open-source implementation: berrzebb/consensus-loop
consensus-loop/
+-- context.mjs <- Shared module: config, paths, parsers, i18n cache
+-- index.mjs <- PostToolUse hook: async audit dispatch + quality gates
+-- audit.mjs <- Background Codex audit with streaming NDJSON output
+-- respond.mjs <- Tag sync engine: promote / demote / auto-fix
+-- retrospective.mjs <- Sets retro marker after all items agreed
+-- session-gate.mjs <- PreToolUse: HITL gate (blocks Bash until retro complete)
+-- cli-runner.mjs <- Cross-platform binary resolver (sync + async spawn)
+-- i18n.mjs <- Standalone locale helper (fallback for non-context imports)
+-- locales/{en,ko}.json <- All user-facing strings
+-- templates/
| +-- *.md <- Facade prompts (~30 lines each, {{REFERENCES_DIR}} injection)
| +-- references/{ko,en}/ <- Team-editable policy files
+-- examples/
| +-- config.example.json <- Annotated full config reference
| +-- templates/{en,ko}/ <- Starting points for prompt templates
+-- docs/{en,ko}/README.md <- Full plugin reference
- Evidence format:
watch_file(author) +respond_file(auditor verdict) -- paths fully configurable inconfig.json
---
Note: This issue was written with the assistance of Claude Code (claude-opus-4-6).
This issue has 10 comments on GitHub. Read the full discussion on GitHub ↗