We are shipping code faster than ever. Or rather, our local AI agents are doing it for us. But there is a massive, hidden tax to this speed. Senior engineers are drowning in pull requests filled with AI slop.
The core problem is the hallucination that looks perfectly right but is fundamentally wrong. You know the exact kind. The AI spits out massive volumes of code that compiles perfectly and passes the linter. Beneath the surface, however, it quietly violates module boundaries, bungles a state machine, or breaks architectural rules. I got sick of playing syntax police on every pull request, so I built an adversarial pipeline to catch this garbage before it ever reaches the repository. Here is how you can build it too.
The absolute best place to kill a hallucination is before it is even committed. I use .cursorrules and claude.md to force architectural context into local coding agents. But rules without strict enforcement are just polite suggestions.
To give these rules teeth, I rely on custom bash hooks that intercept the edit and commit phases. These scripts dynamically inject guidelines and run deterministic checks before the code settles in the working tree. Industry leaders increasingly recommend running cheap, deterministic checks like linting and formatting first before invoking any LLMs, because there is no point in spending tokens on code that will not compile.
Here is a simplified example of a pre-commit hook that intercepts AI-generated code to enforce strict architectural boundaries:
#!/bin/bash
# pre-commit hook to intercept AI writes and enforce architecture
echo "Running local AI architecture shield..."
# Get all staged TypeScript/React files
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.tsx\?$' || true)
if [ -z "$STAGED_FILES" ]; then
exit 0
fi
# Deterministic check: Ensure UI components don't directly import database clients
for FILE in $STAGED_FILES; do
if grep -q "import { .*db.* } from" "$FILE"; then
echo "ERROR: AI hallucination detected in $FILE"
echo "UI components must not import database clients directly."
echo "Please ask your agent to use the API layer."
exit 1
fi
done
echo "Local checks passed. Ready for adversarial review."
exit 0
There is an obvious catch to this. This approach visibly slows down local writes. But engineering is fundamentally about trade-offs. Losing a few seconds of local generation speed is entirely worth saving hours of senior review time later.
Before a developer even opens a pull request, they trigger a heavy-duty, multi-agent adversarial review locally. Think of it as a courtroom for code quality.
Instead of one massive LLM call that gets confused by a large context window, we orchestrate a debate between specialized agents.
The Prosecutors (Finders):
These agents scan the entire diff for logic bugs, untested states, and convention violations. They act with high sensitivity to catch absolutely everything. To keep them grounded, we force them to output structured JSON with specific file and line evidence.
Here is the exact payload structure a Finder must return:
{
"lens": "correctness",
"can_judge": true,
"reason_if_cannot_judge": "",
"findings": [
{
"id": "correctness-1",
"severity": "BLOCKING",
"confidence": "HIGH",
"file": "apps/web/src/components/DataTable.tsx",
"line": 42,
"claim": "Missing error boundary for async data fetch.",
"failure_scenario": "If the API returns a 500, the component throws an unhandled error and crashes the entire React tree.",
"rule_cited": "conventions.md: Error Handling",
"evidence": "Lines 40-45 show a raw await with no try/catch block."
}
]
}
The Defense (Refuters):
Prosecutors overreach. That is their job. To balance them, "Refuter" agents aggressively try to disprove the finders. A refuter must quote the exact diff line to validate an issue. If it hits an internal exemption registry or cannot logically prove the flaw, the finding is ruthlessly dropped. On deep scans, we require a two-thirds majority among refuters to confirm a blocker.
The Detective (Completeness Critic):
If zero blockers survive the defense, a "Critic" agent wakes up to hunt for edge cases the initial finders missed. It looks for unasserted test paths or missing disabled states in the UI.
Crucially, these agents never auto-mutate the codebase. They just hand the developer a pristine, high-signal report in their terminal. The human always retains final execution authority.
Running a massive multi-agent debate inside GitHub Actions is simply too slow and expensive. Once the code actually hits continuous integration, we have to shift our strategy.
We rely on parallel job sharding and lightweight models with incredibly narrow instruction sets. This provides high-speed, cost-effective PR validation that serves as a final safety net without clogging up the deployment pipeline. Using smaller, faster models for focused PR tasks is a proven tactic for managing review latency at scale.
By slicing the git diff into isolated chunks, we can run smaller, faster models in parallel. Here is how that looks in a GitHub Actions workflow:
name: AI PR Review Fast-Track
on:
pull_request:
branches: [ main ]
jobs:
shard-diff:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v4
- name: Generate sharded diff chunks
id: set-matrix
run: |
# Custom script splits the PR diff into 300-line chunks
CHUNKS=$(node ./scripts/shard-diff.js)
echo "matrix=$CHUNKS" >> $GITHUB_OUTPUT
fast-ai-review:
needs: shard-diff
runs-on: ubuntu-latest
strategy:
matrix:
chunk: ${{ fromJson(needs.shard-diff.outputs.matrix) }}
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: Run Lightweight Model Review
env:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
run: |
npx @internal/ci-reviewer \
--diff ${{ matrix.chunk }} \
--model fast-tier \
--strict-mode
This dual-speed architecture completely solves the latency problem. You run the computationally heavy multi-agent debate locally where the developer has time to process it, and you reserve the CI pipeline for lightning-fast validations.
By forcing AI to review its own work through an adversarial lens, we effectively stop the flow of architectural debt into my repositories.
The results have been immediate. Pull request reviews are faster because human reviewers are no longer playing syntax police or hunting for subtle state bugs. The code is significantly cleaner. Most importantly, developers and reviewers have stopped treating AI output with extreme suspicion.
When you build a pipeline that successfully filters out the slop, AI stops being an architectural liability and goes back to being the massive force multiplier it was always meant to be.