Your Agent Loop Is Lying About Being Done: Writing Verifiable Stop Conditions
We deployed an autonomous debugging agent to triage production alerts. In staging, it solved tickets in three steps. In production, it either spun for forty-fiv...
Listen to Article
PlayingClick play to listen to audio narration
Table of Contents
Your Agent Loop Is Lying About Being Done: Writing Verifiable Stop Conditions
Introduction
We deployed an autonomous debugging agent to triage production alerts. In staging, it solved tickets in three steps. In production, it either spun for forty-five minutes burning through our token budget or claimed is_done: true after a single failed API call. The pattern repeated across three different frameworks. The root cause was identical: the language model was hallucinating completion.
LLMs are probabilistic text generators optimized for next-token prediction, not deterministic state machines. When you ask a model to decide whether a multi-step workflow is finished, it pattern-matches against training data rather than evaluating actual system state. The done flag it returns is a guess, not a guarantee.
Writing verifiable stop conditions shifts termination logic from the generative layer to a deterministic verification layer. This article covers how to architect agent loops that enforce strict completion criteria, prevent cost explosions, and guarantee output integrity.
Why This Matters
Unbounded agent loops introduce three critical production risks:
- Cost and Latency Runaway: Without hard verification boundaries, agents retry failed steps indefinitely or chase semantic drift, multiplying API calls and degrading user-facing SLAs.
- State Inconsistency: Premature termination leaves databases partially updated, files half-written, or orchestration workflows in zombie states.
- Audit and Compliance Gaps: When an agent claims completion, downstream systems act on that assertion. If the assertion is unverified, you lose traceability and introduce silent data corruption.
Engineering teams building agentic pipelines treat the LLM as a component, not the authority. Verifiable stop conditions restore deterministic control over non-deterministic execution, aligning AI behavior with production reliability standards.
How It Works
A verifiable stop condition architecture separates generation from validation. The agent proposes actions and updates a centralized state object. Before the loop continues or exits, a dedicated verifier evaluates that state against explicit criteria. The verifier returns a structured decision: proceed, terminate, or trigger a fallback.
This pattern introduces a feedback channel. If verification fails, the system injects corrective context back into the agent rather than blindly repeating the same step. If verification passes, the loop exits cleanly with a validated result. If verification detects a structural violation, a circuit breaker halts execution to prevent resource exhaustion.
flowchart TD
Start([User Request]) --> Agent[Agent Core]
Agent --> Tool{Tool Execution?}
Tool -- Yes --> Exec[Execute Tool]
Tool -- No --> Gen[Generate Response]
Exec --> State[Update Agent State]
Gen --> State
State --> Verifier[Stop Condition Verifier]
Verifier --> Decision{Verification Pass?}
Decision -- No --> Feedback[Inject Correction Context]
Feedback --> Agent
Decision -- Yes --> Output[Return Final Result]
Decision -- Violation --> Error[Trigger Fallback/Circuit Breaker]
The flow enforces a strict checkpoint at every iteration. The agent never decides its own termination. The verifier operates on immutable state snapshots, applies deterministic rules, and routes control flow accordingly. This decoupling allows you to swap models, adjust toolsets, or update business rules without rewriting loop logic.
Core Concepts
- Agent State: A structured container holding execution history, tool outputs, intermediate artifacts, and metadata. It must be versioned and serializable to support verification rollbacks.
- Verifier Contract: An interface defining how completion is evaluated. It accepts state snapshots and returns a typed decision with explicit reasoning.
- Deterministic Bounds: Hard limits on iterations, token consumption, or wall-clock time. These act as safety nets when semantic verification cannot resolve ambiguity.
- Feedback Injection: The mechanism that translates verification failures into actionable context. Instead of repeating the same prompt, the system appends structured error signals to guide the next reasoning step.
- Violation vs. Incomplete: Verification distinguishes between “not done yet” and “fundamentally broken.” Incomplete states loop back with corrections. Violations trigger immediate circuit breaking and fallback handlers.
Examples & Code Walkthrough
The following implementation demonstrates a production-grade async agent loop with a composable verification layer. It uses standard library primitives, type safety, and defensive error handling.
import asyncio
import logging
import time
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable, List, Optional, Protocol
logger = logging.getLogger(__name__)
class VerificationStatus(Enum):
INCOMPLETE = "incomplete"
COMPLETE = "complete"
VIOLATION = "violation"
@dataclass(frozen=True)
class VerificationResult:
status: VerificationStatus
reason: str
feedback: Optional[str] = None
metadata: dict = field(default_factory=dict)
class StopConditionEvaluator(Protocol):
async def evaluate(self, state: dict) -> VerificationResult:
"""Evaluate agent state against completion criteria."""
...
class CompositeVerifier:
"""Chains multiple evaluators. Fails fast on violations, requires all to pass for completion."""
def __init__(self, evaluators: List[StopConditionEvaluator]):
self._evaluators = evaluators
async def evaluate(self, state: dict) -> VerificationResult:
for evaluator in self._evaluators:
result = await evaluator.evaluate(state)
if result.status == VerificationStatus.VIOLATION:
return result
if result.status == VerificationStatus.INCOMPLETE:
return result
return VerificationResult(
status=VerificationStatus.COMPLETE,
reason="All verifiers passed."
)
class StructuralCompletenessChecker(StopConditionEvaluator):
"""Ensures required artifacts exist and match expected schema."""
def __init__(self, required_keys: List[str]):
self._required_keys = required_keys
async def evaluate(self, state: dict) -> VerificationResult:
missing = [k for k in self._required_keys if k not in state]
if missing:
return VerificationResult(
status=VerificationStatus.INCOMPLETE,
reason=f"Missing required artifacts: {missing}",
feedback="Generate missing outputs before claiming completion."
)
return VerificationResult(status=VerificationStatus.COMPLETE, reason="Structural check passed.")
class SemanticGoalChecker(StopConditionEvaluator):
"""Validates that the final output aligns with the original goal using a lightweight deterministic check."""
def __init__(self, goal_predicate: Callable[[dict], bool]):
self._predicate = goal_predicate
async def evaluate(self, state: dict) -> VerificationResult:
if not state.get("is_terminal_call", False):
return VerificationResult(
status=VerificationStatus.INCOMPLETE,
reason="Agent has not issued a terminal call.",
feedback="Continue execution until a final answer is generated."
)
if not self._predicate(state):
return VerificationResult(
status=VerificationStatus.INCOMPLETE,
reason="Output does not satisfy goal constraints.",
feedback="Refine response to match required format and constraints."
)
return VerificationResult(status=VerificationStatus.COMPLETE, reason="Semantic goal satisfied.")
class AgentLoop:
def __init__(
self,
agent_step: Callable[[dict], dict],
verifier: StopConditionEvaluator,
max_iterations: int = 10,
max_seconds: float = 120.0
):
self._agent_step = agent_step
self._verifier = verifier
self._max_iterations = max_iterations
self._max_seconds =Written by Senior AI Research Scientist
Editorial staff persona reviewing transformer layers, neural networks fine-tuning, retrieval-augmented generation (RAG), and model evaluation metrics.