DEV Community

Sarthak Banga
Sarthak Banga

Posted on

The Doer-Checker Pattern: How We Got an Autonomous Agent to 75% Bug Resolution With Zero Production Incidents

Verification-Gated Autonomy: Building an Agent That Fixes Production Bugs Without Human Intervention

The Problem Nobody Talks About

Every AI engineering team I've spoken to has the same dirty secret: their "autonomous" agents aren't autonomous at all. Behind every impressive demo is a human reviewing outputs, catching hallucinations, and quietly fixing what the model got wrong.

I ran into this problem head-on when I built an autonomous bug-fixing agent at a Fortune 500 company. The agent reads bug reports from the backlog, reproduces each bug against production data, writes the fix, runs regression tests, and deploys through CI/CD. No human in the loop.

The first version was confidently terrible. It would "fix" a bug by shipping code that passed its own validation — because the same system that made the change was also judging whether the change was correct.

That failure led me to a pattern I now use across every production AI system I build. I call it Verification-Gated Autonomy — and it's the difference between agents you demo and agents you trust in production.

The Core Principle

The system that makes the change should never be the authority on whether the change is correct.

This sounds obvious. But look at how most agentic AI systems work today:

Agent receives task → Agent produces output → Agent evaluates own output → Ship it
Enter fullscreen mode Exit fullscreen mode

This is a confidence loop, not a verification loop. The agent will almost always rate its own work favourably — not because it's lying, but because it lacks an independent frame of reference. It's like asking a student to grade their own exam.

The fix is architectural separation:

Agent A (Doer) produces output → Agent B (Checker) independently verifies → Gate decision
Enter fullscreen mode Exit fullscreen mode

Agent B doesn't know what Agent A intended. It doesn't see the prompt, the reasoning chain, or the intermediate steps. It receives only the source data and the final output, then makes an independent judgment: does this output correctly represent what's in the source?

Architecture: The Bug-Fixing Agent

Here's how this works in practice. My bug-fixing agent has four stages, with verification gates between each:

┌─────────────────────────────────────────────────────┐
│                    STAGE 1: TRIAGE                   │
│  Read bug report → Classify severity → Route        │
│                                                      │
│  Gate: Does the classification match the symptoms?   │
│  Checker: Independent model re-reads the bug report  │
│  and classifies independently. Match? Proceed.       │
│  Mismatch? Flag for human review.                    │
└──────────────────────┬──────────────────────────────┘
                       │
┌──────────────────────▼──────────────────────────────┐
│                 STAGE 2: REPRODUCE                    │
│  Identify affected code → Set up test environment    │
│  → Reproduce the bug with production-like data       │
│                                                      │
│  Gate: Can the bug be independently reproduced?      │
│  Checker: Run the reproduction steps in an isolated  │
│  environment. Confirmed? Proceed. Not reproduced?    │
│  Route back to triage with additional context.       │
└──────────────────────┬──────────────────────────────┘
                       │
┌──────────────────────▼──────────────────────────────┐
│                   STAGE 3: FIX                        │
│  Generate fix → Apply to codebase → Run unit tests   │
│                                                      │
│  Gate: Does the fix resolve the bug WITHOUT           │
│  introducing regressions?                             │
│  Checker: Independent model reviews the diff against  │
│  the original bug report. Regression suite runs.      │
│  All green + checker approves? Proceed.               │
└──────────────────────┬──────────────────────────────┘
                       │
┌──────────────────────▼──────────────────────────────┐
│                  STAGE 4: DEPLOY                      │
│  Create PR → CI/CD pipeline → Deploy to staging       │
│  → Smoke tests → Production                          │
│                                                      │
│  Gate: Standard CI/CD gates + post-deploy             │
│  verification against the original bug symptoms.      │
└─────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

The critical insight: each gate uses a different model invocation with a different prompt and different context. The checker at Stage 3 doesn't see the doer's reasoning about why the fix should work. It only sees: "here's the original bug, here's the code before, here's the code after — is the bug fixed?"

Implementation: The Doer-Checker Pattern

Here's a simplified version of the core pattern. In production, each component is a separate service with its own prompt management, but the principle is the same:

class DoerCheckerPipeline:
    """
    Core pattern: separate the system that produces work
    from the system that judges work.
    """

    def __init__(self, doer_model: str, checker_model: str):
        # Key: doer and checker can be different models,
        # different temperatures, or even different providers
        self.doer = LLMClient(model=doer_model, temperature=0.7)
        self.checker = LLMClient(model=checker_model, temperature=0.2)

    async def execute_with_verification(
        self,
        task: Task,
        source_data: dict,
        max_retries: int = 2
    ) -> VerifiedResult:

        for attempt in range(max_retries + 1):
            # DOER: produce the output
            doer_result = await self.doer.execute(
                prompt=self._build_doer_prompt(task),
                context=source_data
            )

            # CHECKER: independently verify
            # Note: checker gets source_data and output,
            # but NOT the doer's reasoning or prompt
            verification = await self.checker.evaluate(
                prompt=self._build_checker_prompt(),
                source=source_data,
                output=doer_result.output
            )

            if verification.passed:
                return VerifiedResult(
                    output=doer_result.output,
                    confidence=verification.confidence,
                    verification_details=verification.details,
                    attempts=attempt + 1
                )

            # If checker rejects, feed the rejection reason
            # back to the doer for the next attempt
            task = task.with_feedback(verification.rejection_reason)

        # All retries exhausted — route to human
        return VerifiedResult(
            output=None,
            routed_to_human=True,
            rejection_history=verification.history
        )
Enter fullscreen mode Exit fullscreen mode

A few things to notice:

  1. The checker uses a lower temperature (0.2 vs 0.7). The doer needs creativity to generate fixes. The checker needs consistency to make reliable pass/fail judgments.

  2. The checker never sees the doer's prompt or reasoning. This is the most important design decision. If the checker sees "I fixed the null pointer by adding a null check on line 47," it's biased toward confirming that reasoning. Instead, it only sees the source data and the output.

  3. Failed verifications feed back into the doer. The rejection reason becomes additional context for the next attempt. This creates a genuine improvement loop, not just a retry.

  4. There's a hard limit on retries. After N failures, the system routes to a human. This is the "gated" part of Verification-Gated Autonomy — the agent knows when to stop.

Confidence Scoring: Not All Verifications Are Equal

Binary pass/fail isn't enough for production. The checker also produces a confidence score that drives downstream decisions:

class ConfidenceTier:
    """
    Confidence tiers determine what happens after verification.
    Thresholds are calibrated per document type and task.
    """
    HIGH = (0.95, 1.0)    # Auto-deploy, no human review
    MEDIUM = (0.80, 0.95)  # Deploy to staging, human spot-check
    LOW = (0.60, 0.80)     # Human review required before deploy
    REJECT = (0.0, 0.60)   # Do not deploy, route to human

    @staticmethod
    def route(confidence: float) -> str:
        if confidence >= 0.95:
            return "auto_deploy"
        elif confidence >= 0.80:
            return "staging_with_spot_check"
        elif confidence >= 0.60:
            return "human_review"
        else:
            return "rejected"
Enter fullscreen mode Exit fullscreen mode

This tiered approach means the system handles the easy cases autonomously (freeing engineers for feature work) while routing genuinely hard cases to humans. Over time, as the models improve and your training data grows, the percentage of auto-deploy cases increases naturally.

Results After Six Months in Production

After six months running this pattern across our bug-fixing agent and six other production AI systems:

  • ~75% of incoming bugs are handled autonomously (high-confidence tier)
  • +40% improvement in edge case detection compared to single-model approaches
  • -65% rework rate on AI-generated outputs
  • Zero production incidents caused by an unchecked AI output

The most important metric is the last one. In a Fortune 500 environment, one bad deployment can cost more than a year of engineering salaries. The verification gate is what makes autonomy trustworthy enough for production.

When the Checker Gets It Wrong

No verification system is perfect. Here's how we handle checker failures:

False positives (checker approves bad output): These are caught by downstream monitoring — regression tests, production metrics, user reports. When detected, the specific failure case gets added to the checker's evaluation dataset, improving future accuracy.

False negatives (checker rejects good output): These surface as an unusually high human-review rate. We track the rejection-to-override ratio: if humans consistently override the checker's rejections for a specific category, we retune the checker's prompts and thresholds for that category.

Systematic drift: Both doer and checker outputs are logged with full lineage. A weekly automated analysis checks for distribution shifts in confidence scores, rejection rates, and human override patterns. Drift triggers a review cycle.

Applying This Beyond Bug Fixing

The doer-checker pattern isn't specific to bug fixing. We now use it across all seven of our production AI systems:

  • Document extraction: One model extracts data from commercial real estate documents. A separate model re-reads the source document and verifies each extracted field independently.
  • Code review: One model analyses pull requests and generates review comments. A separate model evaluates whether each comment is actionable, accurate, and non-redundant.
  • RAG pipelines: One model generates answers grounded in retrieved context. A separate model checks whether the answer is actually supported by the retrieved documents (not hallucinated from training data).

The pattern works anywhere an AI system produces an output that has a verifiable "ground truth" or at least a verifiable source.

The Uncomfortable Question

If you're building autonomous agents, ask yourself: who's checking the checker?

If the answer is "the same system that did the work" — you have a confidence loop, not a verification loop. Your system will be confidently wrong, and you won't know until a customer or a regulator tells you.

If the answer is "a human reviews everything" — you don't have autonomy, you have a fancy auto-complete. Your system won't scale, and your engineers will burn out reviewing AI outputs instead of building features.

The answer should be: an architecturally separate system, with different context, different incentives, and a hard gate that routes to humans when confidence is low.

That's Verification-Gated Autonomy. It's not glamorous. It doesn't make for exciting demos. But it's the pattern that lets you actually trust an agent in production — and that's what matters.


Sarthak Banga is a Staff Software Engineer and Agentic AI Lead at a Fortune 500 company, where he has built seven production AI systems in the last 12 months. He writes about practical patterns for production AI at linkedin.com/in/sarthak-banga.

Top comments (0)