Preamble#
The agent loop is the central computational spine of any production-grade agentic AI system. It is not a casual while-loop wrapped around a language model call. It is a bounded, instrumented control system with formal stability properties, measurable exit criteria, deterministic failure recovery, and provenance-complete audit trails. This chapter formalizes the agent loop as an engineering artifact of the same rigor demanded of flight controllers, transaction processors, and distributed consensus protocols. Every phase—plan, decompose, retrieve, act, verify, critique, repair, commit—is defined with typed contracts, mathematical characterization, pseudo-algorithmic specification, and explicit analysis of failure modes, cost ceilings, and convergence guarantees.
15.1 The Canonical Agent Loop: Plan → Decompose → Retrieve → Act → Verify → Critique → Repair → Commit#
15.1.1 Motivation and Architectural Position#
Single-shot generation is fundamentally insufficient for any task requiring correctness guarantees, multi-step reasoning, external state mutation, or evidence-grounded synthesis. The canonical agent loop replaces one-shot inference with an iterative refinement cycle where each phase is independently verifiable, each transition is typed, and the entire trajectory is bounded by resource and quality constraints.
The loop occupies the orchestration layer of the agentic protocol stack:
| Layer | Responsibility |
|---|---|
| User Boundary (JSON-RPC) | Task submission, approval gates, result delivery |
| Orchestration (Agent Loop) | Plan, execute, verify, repair, commit |
| Retrieval Engine | Provenance-tagged evidence assembly |
| Tool Infrastructure (MCP / gRPC) | Typed, discoverable, auditable tool execution |
| Memory Subsystem | Working, session, episodic, semantic, procedural layers |
| Observation Plane | Logs, metrics, traces, test harnesses |
15.1.2 Formal Loop Definition#
Let the agent loop be defined as a finite state machine where:
- = set of transition events (phase completion, error, timeout, budget exceeded, quality gate passed/failed)
- = deterministic transition function
- = terminal states
The transition function enforces:
15.1.3 Loop Invariants#
Every iteration of the agent loop must preserve the following invariants:
- Token Budget Monotonicity: Cumulative token consumption at iteration .
- Recursion Depth Bound: Loop depth , a hard constant.
- State Persistence: Every state transition is durably logged before begins execution.
- Idempotency of State Mutations: Every tool invocation or state write is idempotent or guarded by a deduplication key.
- Provenance Completeness: Every output artifact carries a full lineage trace back to input task, retrieved evidence, tool invocations, and repair history.
15.1.4 Pseudo-Algorithm: Canonical Agent Loop#
ALGORITHM CanonicalAgentLoop
INPUT:
task: TaskSpec // Typed task specification
config: LoopConfig // Budget, depth limits, quality thresholds
ctx: ExecutionContext // Memory, tools, retrieval handles
OUTPUT:
result: AgentResult // Final output with provenance
STATE:
phase ← PLAN
iteration ← 0
checkpoint ← ∅
repair_count ← 0
BEGIN:
WHILE phase ∉ {COMMIT, FAIL, HALT} DO
IF iteration > config.max_iterations THEN
phase ← FAIL
PERSIST_FAILURE_STATE(checkpoint, "iteration_budget_exceeded")
BREAK
END IF
TRY:
MATCH phase:
PLAN:
plan ← PLAN_PHASE(task, ctx)
sub_tasks ← DECOMPOSE_PHASE(plan, ctx)
phase ← RETRIEVE
checkpoint ← SAVE_CHECKPOINT(phase, plan, sub_tasks)
RETRIEVE:
evidence ← RETRIEVE_PHASE(sub_tasks, ctx)
phase ← ACT
checkpoint ← SAVE_CHECKPOINT(phase, evidence)
ACT:
outputs ← EXECUTE_PHASE(sub_tasks, evidence, ctx, config)
phase ← VERIFY
checkpoint ← SAVE_CHECKPOINT(phase, outputs)
VERIFY:
verdict ← VERIFY_PHASE(outputs, task, evidence, ctx)
IF verdict.passed THEN
phase ← COMMIT
ELSE
phase ← CRITIQUE
END IF
checkpoint ← SAVE_CHECKPOINT(phase, verdict)
CRITIQUE:
diagnosis ← CRITIQUE_PHASE(outputs, verdict, task, ctx)
phase ← REPAIR
checkpoint ← SAVE_CHECKPOINT(phase, diagnosis)
REPAIR:
repair_count ← repair_count + 1
IF repair_count > config.max_repairs THEN
phase ← ESCALATE_OR_FAIL(task, checkpoint, config)
ELSE
repaired ← REPAIR_PHASE(outputs, diagnosis, ctx, config)
outputs ← repaired
phase ← VERIFY
checkpoint ← SAVE_CHECKPOINT(phase, outputs)
END IF
COMMIT:
result ← COMMIT_PHASE(outputs, task, ctx)
RETURN result
CATCH error:
IF IS_RETRIABLE(error) AND retry_budget_remaining(config) THEN
BACKOFF_WITH_JITTER(iteration)
// Remain in current phase; retry
ELSE
phase ← FAIL
PERSIST_FAILURE_STATE(checkpoint, error)
END IF
iteration ← iteration + 1
END WHILE
RETURN FAILURE_RESULT(checkpoint)
END15.1.5 Phase Contract Types#
Every phase boundary is a typed contract. The following type signatures define the inter-phase data flow:
| Phase | Input Type | Output Type |
|---|---|---|
PLAN | TaskSpec × ExecutionContext | Plan |
DECOMPOSE | Plan × ExecutionContext | List<SubTask> |
RETRIEVE | List<SubTask> × ExecutionContext | EvidenceBundle |
ACT | List<SubTask> × EvidenceBundle × ExecutionContext | ExecutionOutputs |
VERIFY | ExecutionOutputs × TaskSpec × EvidenceBundle | VerificationVerdict |
CRITIQUE | ExecutionOutputs × VerificationVerdict × TaskSpec | CritiqueDiagnosis |
REPAIR | ExecutionOutputs × CritiqueDiagnosis × ExecutionContext | ExecutionOutputs |
COMMIT | ExecutionOutputs × TaskSpec × ExecutionContext | AgentResult |
These types are versioned. Schema evolution follows backward-compatible additive-field rules. Breaking changes require a new major version of the loop protocol.
15.2 Loop as a Control System: Setpoints, Error Signals, Feedback Gains, and Stability Analysis#
15.2.1 Control-Theoretic Framing#
The agent loop is modeled as a discrete-time closed-loop feedback control system. The objective is to drive the output quality toward a target setpoint while maintaining bounded resource consumption and guaranteed termination.
Define:
- : Setpoint — the desired quality level at iteration , typically a fixed target quality score .
- : Plant output — the measured quality of the agent's current output at iteration , produced by the verification and critique phases.
- : Error signal — the quality deficit.
- : Control input — the repair action intensity (ranging from minimal edit to full regeneration).
- : Plant model — the stochastic transformation from control input to quality improvement, representing the LLM's repair capability.
The loop objective is:
where is the quality gate threshold, is the maximum iteration bound, and is the cumulative cost (tokens, latency, API calls).
15.2.2 Discrete Feedback Dynamics#
Model the quality evolution as:
where:
- is the repair gain — the expected fractional improvement per unit of repair effort. In practice, is estimated empirically from historical repair trajectories and varies by task class.
- is process noise representing the stochastic nature of LLM generation (non-determinism, hallucination variance, context sensitivity).
The control law governing repair intensity is:
This is a discrete PID controller adapted for agent loops:
| Parameter | Role in Agent Loop | Practical Interpretation |
|---|---|---|
| (proportional) | Immediate repair intensity proportional to current deficit | Large quality gap → aggressive repair (full regeneration) |
| (integral) | Accumulated deficit drives escalation | Persistent failure → escalate to human or alternate strategy |
| (derivative) | Rate of improvement determines continuation | Diminishing returns → early termination or strategy switch |
15.2.3 Stability Conditions#
The loop is BIBO-stable (bounded-input, bounded-output) if and only if:
which yields the stability constraint:
Violating this bound causes oscillation: the agent alternately over-repairs and under-repairs, consuming budget without convergence. In production, is set conservatively:
where is a damping margin selected to absorb stochastic variance .
15.2.4 Convergence Rate and Budget Consumption#
Under the proportional-only controller , the error decays as:
The number of iterations to reach the quality gate from initial error is:
The cumulative cost is:
where is the per-iteration base cost and scales the cost impact of repair intensity. This formula directly informs budget allocation: if , the system must either relax , reduce , or fail gracefully.
15.2.5 Stability Analysis Under Stochastic Noise#
When is non-negligible, the expected squared error at steady state under proportional control is:
This implies a noise floor: even with unlimited iterations, quality cannot exceed:
If , the system cannot satisfy the quality gate through iteration alone. This condition triggers strategy escalation: switching models, retrieving additional evidence, decomposing the task further, or escalating to a human operator.
15.2.6 Pseudo-Algorithm: PID-Governed Loop Controller#
ALGORITHM PIDLoopController
INPUT:
r_star: Float // Quality setpoint
epsilon_gate: Float // Quality gate threshold
K_p, K_i, K_d: Float // PID gains
G: Float // Estimated repair gain
k_max: Int // Maximum iterations
C_max: Float // Maximum cumulative cost
STATE:
e_prev ← 0
e_integral ← 0
C_cum ← 0
k ← 0
BEGIN:
LOOP:
y_k ← MEASURE_QUALITY(current_output)
e_k ← r_star - y_k
IF e_k ≤ epsilon_gate THEN
RETURN COMMIT
END IF
IF k ≥ k_max THEN
RETURN FAIL("iteration_budget_exceeded")
END IF
// PID control law
e_integral ← e_integral + e_k
e_derivative ← e_k - e_prev
u_k ← K_p * e_k + K_i * e_integral + K_d * e_derivative
// Clamp repair intensity
u_k ← CLAMP(u_k, u_min, u_max)
// Estimate cost of this repair
c_k ← c_base * (1 + beta * u_k)
IF C_cum + c_k > C_max THEN
RETURN FAIL("cost_budget_exceeded")
END IF
// Dispatch repair
EXECUTE_REPAIR(u_k)
C_cum ← C_cum + c_k
e_prev ← e_k
k ← k + 1
// Derivative-based early termination
IF k ≥ 3 AND |e_derivative| < delta_min THEN
// Diminishing returns detected
RETURN ESCALATE_OR_FAIL("convergence_stalled")
END IF
END LOOP
END15.2.7 Multi-Dimensional Quality Setpoints#
In practice, quality is not scalar. Define a quality vector:
with setpoint vector and error:
The quality gate is satisfied when:
The norm ensures no single quality dimension is allowed to fall below threshold, preventing the system from trading off safety for completeness or correctness for coherence.
15.3 Planning Phase#
15.3.1 Task Decomposition: HTN, Goal Decomposition Trees, and Dependency DAGs#
Hierarchical Task Networks (HTN)#
An HTN decomposes a high-level task into a tree of subtasks through method application. Formally:
where:
- = set of primitive tasks (directly executable actions: tool calls, LLM generations, retrieval queries)
- = set of abstract tasks (compound goals requiring decomposition)
- = method library mapping abstract tasks to ordered sequences of subtasks
- = initial world state
A valid plan is a total ordering of primitive tasks obtained by recursively decomposing through methods in until only primitive tasks remain.
Goal Decomposition Trees (GDT)#
A GDT is a directed tree where:
- Root represents the top-level goal.
- Internal nodes are conjunctive (AND) or disjunctive (OR) decompositions.
- Leaves are primitive executable actions.
The tree satisfies:
OR-nodes provide alternative decomposition strategies, enabling the planner to switch methods when one path fails without invalidating the entire plan.
Dependency DAGs#
Subtasks may have data or ordering dependencies. These form a DAG where:
The critical path length determines minimum sequential execution time:
Subtasks not on the critical path may execute in parallel, subject to concurrency controls (Section 15.4).
Pseudo-Algorithm: HTN Decomposition with Dependency Extraction#
ALGORITHM HTNDecompose
INPUT:
tau_0: AbstractTask // Top-level task
M: MethodLibrary // Decomposition methods
s_0: WorldState // Current state
OUTPUT:
plan: List<PrimitiveTask>
dag: DependencyDAG
BEGIN:
queue ← [(tau_0, ∅)] // (task, parent_dependencies)
plan ← []
dag ← empty DAG
WHILE queue IS NOT EMPTY DO
(task, deps) ← DEQUEUE(queue)
IF task ∈ T_primitive THEN
APPEND task TO plan
ADD_NODE(dag, task, dependencies=deps)
ELSE
// Select best method based on state and cost
methods ← M[task]
method ← SELECT_METHOD(methods, s_0, cost_model)
IF method IS NULL THEN
RETURN PLANNING_FAILURE("no_applicable_method", task)
END IF
prev_dep ← deps
FOR EACH sub_task IN method.sequence DO
IF method.ordering = TOTAL THEN
ENQUEUE(queue, (sub_task, prev_dep))
prev_dep ← {sub_task}
ELSE // partial order
declared_deps ← method.dependencies[sub_task] ∪ deps
ENQUEUE(queue, (sub_task, declared_deps))
END IF
END FOR
END IF
END WHILE
VALIDATE_DAG_ACYCLIC(dag)
RETURN (plan, dag)
END15.3.2 Plan Representation: Ordered Action Lists, Partial-Order Plans, Conditional Plans#
Ordered Action Lists (Total-Order Plans)#
The simplest representation: where executes before . Total-order plans are deterministic and easy to checkpoint but sacrifice parallelism.
Partial-Order Plans (POP)#
A POP is a tuple :
- : set of actions
- : partial ordering constraints ( means before )
- : binding constraints on variables
- : causal links meaning produces condition consumed by
A POP is threat-free when no action can interpose between a causal link and negate . Threat resolution adds ordering constraints to maintain causal integrity.
The flexibility of a POP is measured by the number of valid linearizations:
Higher flexibility enables more parallelism and more rescheduling options under failure.
Conditional Plans (Contingency Plans)#
For tasks with uncertain outcomes, plans include conditional branches:
where is an observation predicate evaluated at runtime. Conditional plans increase plan size exponentially in the worst case:
where is the number of branch points. In practice, branch depth is bounded to (typically 2–4), and only high-uncertainty decision points (tool failures, ambiguous retrieval results, permission-gated mutations) warrant conditional branches.
15.3.3 Plan Validation: Feasibility Checks, Resource Availability, and Pre-Condition Verification#
Before execution, every plan undergoes a validation pass that is deterministic and fast:
Feasibility Matrix#
Define a feasibility function where each component validates:
| Check | Formal Condition | Failure Action |
|---|---|---|
| Tool Availability | Substitute tool or fail | |
| Pre-condition Satisfaction | Reorder or add prerequisite actions | |
| Token Budget | Prune low-priority subtasks | |
| Latency Budget | Increase parallelism or reduce scope | |
| Permission Gates | Request approval or defer | |
| DAG Acyclicity | is a directed acyclic graph | Reject plan, re-plan |
| Resource Concurrency | Serialize or increase limits |
The plan is valid if and only if:
Any zero component triggers re-planning on the specific failed constraint, not full re-planning from scratch. This is a targeted re-planning strategy that preserves valid subplans.
ALGORITHM ValidatePlan
INPUT:
plan: Plan
state: WorldState
config: ResourceConfig
OUTPUT:
validation: ValidationResult
BEGIN:
issues ← []
// Structural validation
IF NOT IS_DAG(plan.dependency_graph) THEN
APPEND issues, CRITICAL("cyclic_dependency_detected")
RETURN ValidationResult(valid=FALSE, issues)
END IF
// Per-action validation
FOR EACH action IN plan.actions DO
// Tool availability
IF action.tool NOT IN ToolRegistry.active_tools() THEN
APPEND issues, ERROR("tool_unavailable", action.tool)
END IF
// Pre-condition check
unmet ← action.preconditions \ state.satisfied_conditions
IF unmet ≠ ∅ THEN
APPEND issues, ERROR("unmet_preconditions", action, unmet)
END IF
// Authorization check for mutating actions
IF action.mutates_state THEN
IF NOT AuthzService.check(action.tool, action.scope) THEN
APPEND issues, BLOCKED("authorization_required", action)
END IF
END IF
END FOR
// Budget validation
estimated_tokens ← SUM(ESTIMATE_TOKENS(a) FOR a IN plan.actions)
IF estimated_tokens > config.remaining_token_budget THEN
APPEND issues, WARNING("token_budget_exceeded", estimated_tokens)
END IF
critical_path ← COMPUTE_CRITICAL_PATH(plan.dependency_graph)
IF critical_path.duration > config.latency_budget THEN
APPEND issues, WARNING("latency_budget_exceeded", critical_path)
END IF
valid ← ALL(issue.severity ≠ CRITICAL AND issue.severity ≠ ERROR
FOR issue IN issues)
RETURN ValidationResult(valid, issues)
END15.4 Execution Phase#
15.4.1 Action Selection and Dispatch#
Given a validated partial-order plan represented as a dependency DAG, the dispatcher selects the next set of executable actions at each scheduling tick. An action is ready when all its predecessors have completed:
The ready set at tick is:
Actions are dispatched from subject to a concurrency limit and priority ordering:
where downstream_count prioritizes actions on the critical path.
ALGORITHM DispatchReadyActions
INPUT:
dag: DependencyDAG
active_count: Int
P_max: Int
OUTPUT:
dispatched: List<Action>
BEGIN:
ready ← {a ∈ dag.actions :
status(a) = PENDING AND
∀ pred ∈ dag.predecessors(a): status(pred) = COMPLETED}
slots_available ← P_max - active_count
IF slots_available ≤ 0 THEN
RETURN []
END IF
// Sort by priority and critical path contribution
sorted_ready ← SORT(ready, key=λa: (-priority(a), -downstream_count(dag, a)))
dispatched ← sorted_ready[0 : slots_available]
FOR EACH action IN dispatched DO
SET_STATUS(action, DISPATCHED)
RECORD_DISPATCH_TIME(action, NOW())
EMIT_TRACE_EVENT("action_dispatched", action.id, action.tool)
END FOR
RETURN dispatched
END15.4.2 Tool Invocation with Timeout and Retry Policies#
Every tool invocation is governed by a typed invocation contract specifying:
| Field | Type | Semantics |
|---|---|---|
Duration | Maximum wall-clock time before abort | |
Int | Maximum retry attempts | |
Duration → Duration | Delay before retry | |
| `CLOSED \ | OPEN \ | |
String | Deduplication key for at-most-once semantics |
Exponential Backoff with Jitter#
The backoff function prevents thundering herd effects:
where is the base delay, is the ceiling, and is the jitter range.
Circuit Breaker State Machine#
The circuit breaker prevents repeated invocations of a failing tool:
ALGORITHM InvokeToolWithPolicy
INPUT:
action: Action
policy: InvocationPolicy
tool_client: ToolClient
OUTPUT:
result: ToolResult | ToolError
BEGIN:
IF policy.circuit_state = OPEN THEN
RETURN ToolError("circuit_open", action.tool)
END IF
FOR r ← 0 TO policy.R_max DO
TRY:
result ← tool_client.invoke(
tool=action.tool,
input=action.input,
timeout=policy.tau_timeout,
idempotency_key=policy.idempotency_key,
deadline=NOW() + policy.tau_timeout
)
RECORD_INVOCATION_TRACE(action, result, latency=ELAPSED())
IF result.is_success THEN
CIRCUIT_BREAKER_RECORD_SUCCESS(action.tool)
RETURN result
ELSE IF result.is_retriable THEN
CONTINUE // Will retry
ELSE
RETURN ToolError("non_retriable", result.error_class)
END IF
CATCH TimeoutException:
RECORD_TIMEOUT(action, attempt=r)
CATCH TransientException AS ex:
RECORD_TRANSIENT_ERROR(action, ex, attempt=r)
END TRY
IF r < policy.R_max THEN
delay ← MIN(policy.b_max, policy.b_0 * 2^r + UNIFORM(0, policy.j_max))
SLEEP(delay)
END IF
END FOR
CIRCUIT_BREAKER_RECORD_FAILURE(action.tool)
RETURN ToolError("retries_exhausted", action.tool, attempts=policy.R_max)
END15.4.3 Intermediate State Persistence and Checkpointing#
Every state transition during execution is durably persisted before the next phase begins. This enables resumption after crash, timeout, or resource exhaustion (see Section 15.11).
Checkpoint Data Structure#
Checkpoints are stored in an append-only, content-addressed store:
Append-only semantics guarantee that no checkpoint is ever overwritten, enabling full replay and forensic analysis.
Write-Ahead Logging (WAL) Protocol#
Before any state mutation (tool invocation, memory write, output generation):
- Write the intent record to the WAL:
INTENT(action_id, tool, input, timestamp) - Execute the action.
- Write the completion record:
COMPLETE(action_id, result, timestamp)orABORT(action_id, error, timestamp)
On recovery, the WAL is replayed:
- Actions with
INTENTbut noCOMPLETEorABORTare in-flight: recheck via idempotency key, then retry or mark as failed. - Actions with
COMPLETEare skipped. - Actions with
ABORTtrigger compensating actions if applicable.
ALGORITHM CheckpointAndPersist
INPUT:
loop_id: String
iteration: Int
phase: Phase
state: LoopState
OUTPUT:
checkpoint_ref: CheckpointRef
BEGIN:
checkpoint ← Checkpoint(
loop_id=loop_id,
iteration=iteration,
phase=phase,
plan_state=SERIALIZE(state.plan),
completed_actions=state.completed,
pending_actions=state.pending,
outputs_so_far=SERIALIZE(state.outputs),
token_consumption=state.tokens_used,
timestamp=NOW()
)
key ← SHA256(loop_id || iteration)
// Durable write with fsync guarantee
WAL.append(key, checkpoint)
WAL.fsync()
EMIT_METRIC("checkpoint_written", {
loop_id: loop_id,
iteration: iteration,
size_bytes: SIZE(checkpoint)
})
RETURN CheckpointRef(key, iteration)
END15.5 Verification Phase#
15.5.1 Output Validation: Schema, Semantic, and Factual Verification#
Verification is the gate that separates acceptable from unacceptable outputs. It operates on three levels, each progressively more expensive and more discriminating.
Level 1: Schema Validation (Deterministic, ~0 latency)#
Every output must conform to its declared type schema. This is checked deterministically:
where is a JSON Schema, Protobuf descriptor, or equivalent structural type. Schema violations are hard failures that require immediate repair without entering the critique phase.
Level 2: Semantic Validation (Model-Assisted, ~medium latency)#
Semantic checks verify that the output is meaningful and internally consistent:
- Entailment checking: Does the output logically follow from the retrieved evidence?
- Contradiction detection: Does the output contradict any evidence or known facts?
- Coherence scoring: Is the output internally consistent across sections?
Define a semantic validity score:
where is the set of claims extracted from output , is the evidence bundle, and NLI is a natural language inference classifier. Claims classified as CONTRADICT are flagged.
Level 3: Factual Verification (Multi-Source, ~high latency)#
Factual verification cross-references output claims against authoritative sources:
where is the set of authoritative sources (knowledge bases, verified databases, official documentation).
Composite Verification Score#
The overall verification verdict combines all levels:
The quality gate passes when .
15.5.2 Test Execution: Unit, Integration, and Behavioral Test Harnesses#
For code-producing agents, verification includes executable test suites:
| Test Level | Scope | Trigger | Pass Criterion |
|---|---|---|---|
| Unit | Individual function/component | Every action producing code | All unit tests pass |
| Integration | Cross-component interaction | After all code actions complete | Integration suite passes |
| Behavioral | End-to-end user-facing behavior | Before commit | All acceptance tests pass |
| Regression | Previously fixed defects | Before commit | No regressions introduced |
The test harness is exposed to the agent runtime (Section 15.4.1) so the agent can observe failures directly:
Each TestFailure contains the test name, expected output, actual output, stack trace, and relevant source location—all structured data the repair phase can consume.
15.5.3 Self-Consistency Checks: Multiple Generation Comparison, Voting, and Consensus#
When verification via external tests or factual checking is unavailable, self-consistency provides a proxy for correctness. The agent generates independent responses for the same task (with temperature ) and measures agreement.
Majority Voting#
For tasks with discrete answers:
The consistency score is:
If , the result is flagged as low-confidence and routed to critique.
Semantic Similarity Voting#
For free-form outputs, pairwise semantic similarity replaces exact match:
where is cosine similarity of embeddings. The output with highest average similarity to all others is selected:
Cost-Quality Trade-off#
Self-consistency scales linearly in cost: generations cost . The diminishing marginal value of additional samples follows:
Typically provides sufficient consistency signal. Beyond , returns diminish sharply for most task classes.
ALGORITHM SelfConsistencyVerification
INPUT:
task: TaskSpec
ctx: ExecutionContext
N: Int // Number of independent generations
gamma_min: Float // Minimum consistency threshold
OUTPUT:
best_output: Output
consistency_score: Float
verdict: PASS | LOW_CONFIDENCE
BEGIN:
outputs ← []
FOR i ← 1 TO N DO
o_i ← GENERATE(task, ctx, temperature=0.7, seed=RANDOM())
APPEND o_i TO outputs
END FOR
// Compute pairwise similarity matrix
S ← MATRIX(N, N)
FOR i ← 1 TO N DO
FOR j ← i+1 TO N DO
S[i][j] ← SEMANTIC_SIMILARITY(outputs[i], outputs[j])
S[j][i] ← S[i][j]
END FOR
END FOR
// Select output with highest average agreement
avg_sim ← [MEAN(S[k][j] FOR j ≠ k) FOR k ← 1 TO N]
best_idx ← ARGMAX(avg_sim)
best_output ← outputs[best_idx]
consistency_score ← avg_sim[best_idx]
IF consistency_score ≥ gamma_min THEN
RETURN (best_output, consistency_score, PASS)
ELSE
RETURN (best_output, consistency_score, LOW_CONFIDENCE)
END IF
END15.6 Critique Phase#
15.6.1 Critic Agent Architecture: Independent Evaluation with Separate Context#
The critic agent is a structurally independent evaluator that operates with a different context window, different system prompt, and ideally a different model checkpoint than the generator. This architectural separation prevents the system from confirming its own errors.
Separation Principles#
| Property | Generator Agent | Critic Agent |
|---|---|---|
| System Prompt | Task-execution oriented | Evaluation-and-defect-detection oriented |
| Context Window | Task spec + plan + evidence + prior outputs | Task spec + output under review + evaluation rubric |
| Model | Primary generation model | Evaluation-specialized model (may differ) |
| Memory Access | Full working memory | Read-only access to task memory |
| Objective | Maximize task completion quality | Maximize defect detection recall |
The critic receives the output , the original task specification , the evidence bundle , and a structured evaluation rubric , but does not receive the generator's chain-of-thought, planning rationale, or intermediate states. This prevents the critic from being anchored by the generator's reasoning.
Formal Critic Function#
where:
15.6.2 Rubric-Based Scoring: Correctness, Completeness, Coherence, Safety#
The evaluation rubric defines dimensions and scoring criteria:
where:
- = dimension name (correctness, completeness, coherence, safety, …)
- = weight such that
- = scoring function for dimension
The weighted aggregate score is:
Individual dimension gates enforce that no dimension falls below a minimum:
The critique passes if and only if:
| Dimension | Description | Weight (typical) | Minimum |
|---|---|---|---|
| Correctness | Factual accuracy, logical validity, code correctness | 0.40 | 0.80 |
| Completeness | Coverage of all task requirements | 0.25 | 0.70 |
| Coherence | Internal consistency, logical flow, no contradictions | 0.20 | 0.75 |
| Safety | No harmful content, no credential leaks, policy compliance | 0.15 | 0.95 |
15.6.3 Adversarial Critique: Red-Team Prompting, Edge Case Generation#
Beyond rubric-based scoring, the critique phase optionally invokes an adversarial critic that actively attempts to break the output:
Red-Team Prompting#
The adversarial critic is instructed to:
- Identify inputs or scenarios where the output would produce incorrect results.
- Find logical inconsistencies or unstated assumptions.
- Generate counterexamples that violate claimed properties.
- Check for prompt injection vulnerabilities, data leakage, or policy violations.
Edge Case Generation#
For code-producing agents, the adversarial critic generates edge-case test inputs:
These edge cases are then executed against the produced code. Failures are classified by severity and added to the CritiqueDiagnosis.defects list.
ALGORITHM CritiquePhase
INPUT:
output: Output
task: TaskSpec
evidence: EvidenceBundle
rubric: EvaluationRubric
config: CritiqueConfig
OUTPUT:
diagnosis: CritiqueDiagnosis
BEGIN:
// Rubric-based evaluation
scores ← {}
defects ← []
FOR EACH (dimension, weight, scorer) IN rubric DO
score ← scorer(output, task, evidence)
scores[dimension] ← score
IF score < dimension.minimum THEN
APPEND defects, Defect(
dimension=dimension.name,
severity=COMPUTE_SEVERITY(dimension.minimum - score),
description=scorer.explain_deficit(output, task, evidence),
location=scorer.locate_deficit(output)
)
END IF
END FOR
// Adversarial critique (if budget permits)
IF config.enable_adversarial THEN
adv_defects ← ADVERSARIAL_CRITIC(output, task, evidence)
EXTEND defects WITH adv_defects
END IF
// Generate repair hints
repair_hints ← []
FOR EACH defect IN defects DO
hint ← GENERATE_REPAIR_HINT(defect, output, evidence)
APPEND repair_hints, hint
END FOR
// Overall severity
max_severity ← MAX(d.severity FOR d IN defects) IF defects ELSE NONE
RETURN CritiqueDiagnosis(
scores=scores,
defects=defects,
severity=max_severity,
repair_hints=repair_hints
)
END15.7 Repair Phase#
15.7.1 Error Diagnosis: Root Cause Classification, Stack Trace Analysis#
The repair phase begins with root cause analysis (RCA), classifying each defect into a taxonomy that determines the appropriate repair strategy:
| Root Cause Class | Description | Typical Repair Strategy |
|---|---|---|
SCHEMA_VIOLATION | Output structure does not match type | Re-format or re-generate structured output |
FACTUAL_ERROR | Incorrect claim contradicted by evidence | Replace claim with evidence-supported statement |
LOGIC_ERROR | Invalid reasoning or code bug | Targeted fix at identified location |
INCOMPLETENESS | Missing required component | Additive generation for missing parts |
COHERENCE_FAILURE | Internal contradiction | Rewrite contradictory sections |
SAFETY_VIOLATION | Policy-violating content | Remove or redact violating content |
HALLUCINATION | Claim not grounded in any evidence | Remove or replace with grounded alternative |
RETRIEVAL_GAP | Insufficient evidence for task | Re-retrieve with expanded queries, then regenerate |
TOOL_FAILURE | Tool returned error or unexpected result | Retry tool, substitute tool, or adjust input |
For code-producing agents, stack traces from failed tests are parsed to extract:
This structured diagnostic is injected directly into the repair prompt as deterministic context, not prose.
15.7.2 Targeted Correction: Minimal Edit Repair vs. Full Regeneration#
The repair strategy is selected based on the root cause class and defect severity:
Decision Function#
Cost Model#
Specifically:
The system always prefers the least expensive repair that is likely to resolve the defect, as estimated by historical repair success rates per root cause class:
Minimal Edit Repair Procedure#
- Locate the defect within the output using
Defect.location. - Extract a bounded repair window around the defect site: where is the defect line and is the window radius.
- Construct a repair prompt containing:
- The repair window content
- The defect description and root cause
- The relevant evidence fragment
- The repair hint from critique
- Generate a replacement for the repair window only.
- Splice the replacement back into the original output.
- Re-run verification on the repaired output.
15.7.3 Repair Budget: Maximum Repair Attempts, Escalation Policies#
The repair phase is budget-bounded to prevent infinite loops:
where:
- : maximum number of repair iterations
- : maximum token cost allocated to repairs
- : maximum wall-clock time for repair phase
Escalation Policy#
When the repair budget is exhausted without achieving the quality gate:
ALGORITHM RepairPhase
INPUT:
output: Output
diagnosis: CritiqueDiagnosis
ctx: ExecutionContext
config: RepairConfig
OUTPUT:
repaired_output: Output
repair_log: RepairLog
STATE:
repair_count ← 0
repair_cost ← 0
repair_log ← []
BEGIN:
current_output ← output
remaining_defects ← diagnosis.defects
// Sort defects by severity (highest first)
SORT remaining_defects BY severity DESCENDING
WHILE remaining_defects IS NOT EMPTY DO
defect ← remaining_defects[0]
// Budget check
IF repair_count ≥ config.R_max THEN
RETURN ESCALATE(current_output, remaining_defects, config)
END IF
IF repair_cost ≥ config.C_max_repair THEN
RETURN ESCALATE(current_output, remaining_defects, config)
END IF
// Select repair strategy
rca ← ROOT_CAUSE_CLASSIFY(defect)
strategy ← SELECT_STRATEGY(rca, defect, current_output)
// Execute repair
MATCH strategy:
MINIMAL_EDIT:
window ← EXTRACT_REPAIR_WINDOW(current_output, defect.location, config.window_radius)
repair_prompt ← COMPILE_REPAIR_PROMPT(window, defect, diagnosis.repair_hints)
patched_window ← GENERATE(repair_prompt, ctx)
current_output ← SPLICE(current_output, defect.location, patched_window)
SECTION_REWRITE:
section ← EXTRACT_SECTION(current_output, defect.scope)
repair_prompt ← COMPILE_SECTION_REPAIR_PROMPT(section, defect, ctx)
new_section ← GENERATE(repair_prompt, ctx)
current_output ← REPLACE_SECTION(current_output, defect.scope, new_section)
FULL_REGENERATION:
repair_prompt ← COMPILE_FULL_REGEN_PROMPT(task, ctx, defects=remaining_defects)
current_output ← GENERATE(repair_prompt, ctx)
remaining_defects ← [] // All defects addressed by regeneration
// Update accounting
repair_count ← repair_count + 1
repair_cost ← repair_cost + MEASURE_COST(strategy)
APPEND repair_log, RepairEntry(defect, strategy, rca, success=PENDING)
// Re-verify the specific defect
defect_resolved ← VERIFY_DEFECT(current_output, defect)
IF defect_resolved THEN
REMOVE defect FROM remaining_defects
repair_log.last().success ← TRUE
ELSE
repair_log.last().success ← FALSE
// May try different strategy next iteration
END IF
END WHILE
RETURN (current_output, repair_log)
END15.8 Commit Phase#
15.8.1 Output Finalization, Provenance Attachment, and Audit Record#
The commit phase transforms a verified output into a production artifact with full provenance and auditability.
Provenance Record#
Every committed output carries a provenance record:
This provenance record is:
- Immutable: Once committed, the provenance record cannot be altered.
- Content-addressed:
- Cross-referenced: Each evidence reference links to the specific chunk, document, and retrieval query that produced it.
Audit Record#
The audit record captures the complete execution trajectory:
This record serves compliance, debugging, and continuous improvement functions.
ALGORITHM CommitPhase
INPUT:
output: Output
task: TaskSpec
ctx: ExecutionContext
verification_scores: Scores
repair_log: RepairLog
OUTPUT:
committed_result: AgentResult
BEGIN:
// Attach provenance
provenance ← Provenance(
task_id=task.id,
plan_hash=HASH(ctx.plan),
evidence_refs=ctx.evidence.references(),
tool_invocations=ctx.tool_trace.all(),
repair_history=repair_log,
verification_scores=verification_scores,
model_id=ctx.model_config.id,
timestamp=NOW(),
approver=NULL // Filled if human approval required
)
// Human approval gate (if configured)
IF task.requires_human_approval THEN
approval ← REQUEST_HUMAN_APPROVAL(output, provenance)
IF approval.status = REJECTED THEN
RETURN FAIL_WITH_REJECTION(approval.reason)
END IF
provenance.approver ← approval.approver_id
END IF
// Compute content hash for deduplication
content_hash ← SHA256(CANONICAL_SERIALIZE(output))
// Build audit record
audit ← AuditRecord(
loop_trace=ctx.loop_trace,
all_checkpoints=ctx.checkpoints,
all_tool_traces=ctx.tool_trace,
all_repair_logs=repair_log,
total_cost=ctx.cost_accumulator.total(),
total_latency=NOW() - ctx.start_time,
final_quality_scores=verification_scores
)
// Durable commit
committed_result ← AgentResult(
output=output,
provenance=provenance,
audit=audit,
content_hash=content_hash,
status=COMMITTED
)
PERSIST_RESULT(committed_result)
EMIT_COMMIT_EVENT(committed_result)
// Post-commit memory promotion
PROMOTE_TO_EPISODIC_MEMORY(task, output, provenance, ctx)
RETURN committed_result
END15.8.2 State Transition Logging and Checkpoint Commit#
The final checkpoint after commit marks the loop as terminally complete:
This terminal checkpoint enables:
- Idempotent resubmission: If the same task is resubmitted, the system can detect the existing committed result and return it without re-execution.
- Replay analysis: The full checkpoint chain from to can be replayed to understand the agent's decision trajectory.
- Cost attribution: Total cost is partitioned across planning, retrieval, execution, verification, critique, and repair phases for budget optimization.
15.9 Bounded Recursion: Depth Limits, Loop Detection, and Termination Guarantees#
15.9.1 Recursion in Agent Loops#
Agent loops can become recursive in several ways:
- Nested agent invocations: An agent invokes a sub-agent, which invokes another sub-agent.
- Repair-verify cycles: Repair produces output that fails verification, triggering another critique-repair cycle.
- Plan re-decomposition: A failed subtask triggers re-planning, which may generate new subtasks that themselves require planning.
- Retrieval-generation loops: Generation reveals a knowledge gap, triggering retrieval, which changes the generation context.
Without explicit bounds, any of these recursion paths can diverge.
15.9.2 Formal Termination Guarantee#
Define the recursion depth as the nesting level of agent invocations:
The system enforces:
Similarly, define the repair cycle count within a single agent:
And the total iteration count across all recursion levels:
Theorem (Termination Guarantee): Under the constraints , per agent, and as a global iteration cap, the agent loop terminates in at most iterations with a deterministic terminal state in .
Proof sketch: Each iteration either advances the phase (finite state machine with no cycles except the verify→critique→repair→verify loop, which is bounded by ), spawns a sub-agent (bounded by ), or exhausts a budget (cost, iterations, time), each of which triggers a terminal transition. The product of all bounds is finite.
15.9.3 Loop Detection#
Even within bounds, the agent may enter semantic loops where it repeatedly generates the same output, repair, and failure pattern. Detection uses:
When a loop is detected, the system breaks the cycle by:
- Switching the repair strategy (e.g., from minimal edit to full regeneration).
- Perturbing the generation temperature or prompt structure.
- Introducing fresh evidence via an expanded retrieval query.
- Escalating to a different model or to human review.
ALGORITHM BoundedRecursionGuard
INPUT:
agent_id: String
parent_depth: Int
config: RecursionConfig
global_counter: AtomicInt // Shared across all agents
OUTPUT:
guard: RecursionGuard
BEGIN:
current_depth ← parent_depth + 1
IF current_depth > config.d_max THEN
RAISE RecursionDepthExceeded(agent_id, current_depth)
END IF
IF global_counter.get() > config.K_global_max THEN
RAISE GlobalIterationBudgetExhausted(agent_id)
END IF
// Semantic loop detector state
history ← RingBuffer(capacity=config.loop_window)
guard ← RecursionGuard(
depth=current_depth,
max_depth=config.d_max,
repair_count=0,
max_repairs=config.R_max,
history=history,
global_counter=global_counter,
// Methods
check_repair_budget=λ: guard.repair_count < config.R_max,
increment_repair=λ: BEGIN
guard.repair_count ← guard.repair_count + 1
global_counter.increment()
END,
check_loop=λ(output, diagnosis): BEGIN
FOR EACH (prev_output, prev_diag) IN guard.history DO
IF sim(output, prev_output) > config.theta_loop
AND sim(diagnosis, prev_diag) > config.theta_loop THEN
RETURN TRUE // Loop detected
END IF
END FOR
history.push((output, diagnosis))
RETURN FALSE
END
)
RETURN guard
END15.10 Rollback and Compensating Actions: Reverting Partial Execution Safely#
15.10.1 The Rollback Problem in Agentic Systems#
Agent loops execute actions with side effects: file writes, API calls, database mutations, message sends. When a later action fails and the loop must revert, earlier side effects may need to be undone. Unlike database transactions, most real-world side effects (sending an email, deploying a service) cannot be atomically rolled back.
15.10.2 Action Classification by Reversibility#
Every action in the plan is classified at planning time:
| Reversibility Class | Definition | Examples | Rollback Mechanism |
|---|---|---|---|
| Pure | No side effects | LLM generation, embedding computation | No rollback needed |
| Reversible | Side effects can be undone | File write (delete file), branch creation (delete branch) | Compensating action |
| Compensable | Cannot undo, but can issue compensating action | Payment (refund), notification (correction notice) | Compensating action with semantic difference |
| Irreversible | Cannot be undone or compensated | External API call with permanent effect, data deletion | Must gate with approval before execution |
The plan must explicitly record the compensating action for every non-pure action:
where denotes approximate state restoration (exact restoration may be impossible for compensable actions).
15.10.3 Saga Pattern for Agent Loops#
The Saga pattern from distributed systems is adapted for agent loops:
- Each action has a compensating action .
- Actions execute in order.
- If action fails, compensating actions execute in reverse order.
ALGORITHM SagaRollback
INPUT:
completed_actions: List<(Action, CompensatingAction)>
failed_action: Action
failure_reason: Error
OUTPUT:
rollback_result: RollbackResult
BEGIN:
rollback_log ← []
rollback_success ← TRUE
// Execute compensating actions in reverse order
FOR i ← LEN(completed_actions) - 1 DOWNTO 0 DO
(action, compensating) ← completed_actions[i]
IF action.reversibility_class = PURE THEN
CONTINUE // Nothing to compensate
END IF
IF action.reversibility_class = IRREVERSIBLE THEN
APPEND rollback_log, RollbackEntry(
action=action,
status=CANNOT_ROLLBACK,
note="Irreversible action; manual intervention required"
)
rollback_success ← FALSE
CONTINUE
END IF
TRY:
result ← EXECUTE_COMPENSATING_ACTION(compensating)
APPEND rollback_log, RollbackEntry(
action=action,
compensating=compensating,
status=COMPENSATED,
result=result
)
CATCH error:
APPEND rollback_log, RollbackEntry(
action=action,
compensating=compensating,
status=COMPENSATION_FAILED,
error=error
)
rollback_success ← FALSE
ALERT_OPERATOR("compensation_failed", action, error)
END TRY
END FOR
RETURN RollbackResult(
success=rollback_success,
log=rollback_log,
requires_manual_intervention=NOT rollback_success
)
END15.10.4 Idempotency and At-Most-Once Semantics#
Rollback safety depends on idempotent compensating actions. Every compensating action must be safe to execute multiple times:
This is enforced by:
- Attaching a unique idempotency key to every action and its compensating action.
- Checking the key in a deduplication store before execution.
- Implementing compensating actions as declarative state assertions ("file X should not exist") rather than imperative commands ("delete file X"), so re-execution on an already-compensated state is a no-op.
15.11 Failure-State Persistence: Resumable Execution After Crash, Timeout, or Resource Exhaustion#
15.11.1 Failure Modes#
| Failure Mode | Cause | Recovery Mechanism |
|---|---|---|
| Process Crash | OOM, segfault, unhandled exception | Resume from last checkpoint |
| Timeout | Latency budget exceeded | Resume with extended budget or reduced scope |
| Resource Exhaustion | Token budget, API rate limit, storage | Resume after resource replenishment |
| Model Error | API error, model overload | Retry with backoff or failover model |
| Tool Failure | External service down | Circuit breaker, retry, or substitute tool |
| Human Interrupt | Operator cancels or pauses | Persist state, await resume signal |
15.11.2 Resumable Execution Protocol#
The system implements exactly-once semantics for the agent loop through the WAL and checkpoint mechanism:
ALGORITHM ResumeFromFailure
INPUT:
loop_id: String
checkpoint_store: CheckpointStore
wal: WriteAheadLog
OUTPUT:
resumed_state: LoopState | NEW_EXECUTION
BEGIN:
// Find latest checkpoint
latest_cp ← checkpoint_store.latest(loop_id)
IF latest_cp IS NULL THEN
RETURN NEW_EXECUTION
END IF
IF latest_cp.phase ∈ {COMMIT, FAIL} THEN
// Terminal state; do not resume
RETURN ALREADY_TERMINAL(latest_cp)
END IF
// Reconstruct state from checkpoint
state ← DESERIALIZE(latest_cp)
// Replay WAL entries after checkpoint
wal_entries ← wal.entries_after(loop_id, latest_cp.iteration)
FOR EACH entry IN wal_entries DO
MATCH entry:
INTENT(action_id, ...):
// Check if this action completed
completion ← wal.find_completion(action_id)
IF completion IS NULL THEN
// In-flight action at crash time
// Check idempotency
IF CAN_VERIFY_COMPLETION(action_id) THEN
actual_result ← VERIFY_EXTERNAL_STATE(action_id)
IF actual_result.completed THEN
MARK_COMPLETED(state, action_id, actual_result)
ELSE
MARK_PENDING(state, action_id) // Will retry
END IF
ELSE
MARK_PENDING(state, action_id) // Will retry idempotently
END IF
ELSE
APPLY_COMPLETION(state, action_id, completion)
END IF
COMPLETE(action_id, result, ...):
APPLY_COMPLETION(state, action_id, result)
ABORT(action_id, error, ...):
MARK_FAILED(state, action_id, error)
END FOR
// Validate reconstructed state consistency
ASSERT state.is_consistent()
EMIT_METRIC("loop_resumed", {
loop_id: loop_id,
resumed_from_iteration: latest_cp.iteration,
phase: latest_cp.phase,
inflight_actions_recovered: COUNT(recovered)
})
RETURN state
END15.11.3 Consistency Guarantees#
The resumption protocol provides:
- At-most-once for irreversible actions: Idempotency keys prevent double-execution.
- At-least-once for retriable actions: Pending actions are re-dispatched on resume.
- Exactly-once for the loop state machine: The checkpoint + WAL combination ensures the loop state is reconstructed precisely.
The consistency model is formalized as:
where denotes sequential application of WAL entries to checkpoint state.
15.12 Exit Criteria: Measurable Quality Gates, Confidence Thresholds, and Human Approval Triggers#
15.12.1 Quality Gate Formalization#
The agent loop terminates via COMMIT only when all exit criteria are satisfied simultaneously:
where is the set of quality gates. Each gate is a predicate with a measurable threshold:
| Gate | Formal Condition | Threshold (typical) |
|---|---|---|
| Schema Validity | Exact (no tolerance) | |
| Correctness Score | ||
| Completeness Score | ||
| Coherence Score | ||
| Safety Score | ||
| Test Pass Rate | (all tests pass) | |
| Self-Consistency | ||
| Confidence |
15.12.2 Confidence Estimation#
The agent's confidence in its output is estimated through multiple signals:
where is the sigmoid function, are confidence features, and are learned weights:
| Feature | Description |
|---|---|
| Verification phase composite score | |
| Self-consistency score | |
| Evidence coverage (fraction of claims grounded) | |
| Number of repair iterations (negative signal) | |
| Average log-probability of generated tokens | |
| Critic agent's overall assessment score | |
| Test pass rate (for code tasks) |
The confidence threshold determines whether the output can be auto-committed or requires human review:
with typical thresholds , .
15.12.3 Human Approval Triggers#
Human approval is mechanically triggered, not optionally consulted:
| Trigger Condition | Justification |
|---|---|
| Confidence below auto-commit threshold | Insufficient automated assurance |
| Output involves irreversible state mutation | Safety-critical action |
| Task explicitly flagged as requiring approval | Policy requirement |
| Novel task pattern not seen in training data | Out-of-distribution detection |
| Budget exceeded with partial output | Partial result may require human judgment |
| Safety score below safety gate | Potential policy violation |
The human approval request is structured:
The system presents the human with the output, all quality scores, the risk assessment, and a recommended action (approve, reject, modify). The human's decision is recorded as part of the provenance and audit record.
15.12.4 Composite Exit Decision#
The complete exit decision integrates all gates, confidence, and approval:
ALGORITHM ExitDecision
INPUT:
output: Output
task: TaskSpec
evidence: EvidenceBundle
verification: VerificationVerdict
critique: CritiqueDiagnosis
config: ExitConfig
OUTPUT:
decision: COMMIT | CONTINUE_REPAIR | ESCALATE | FAIL
BEGIN:
// Check all quality gates
gates_passed ← TRUE
FOR EACH gate IN config.quality_gates DO
IF NOT gate.evaluate(output, task, evidence) THEN
gates_passed ← FALSE
RECORD_GATE_FAILURE(gate.name, gate.score, gate.threshold)
END IF
END FOR
// Compute confidence
confidence ← ESTIMATE_CONFIDENCE(output, verification, critique)
// Determine disposition
IF NOT gates_passed THEN
IF repair_budget_remaining() THEN
RETURN CONTINUE_REPAIR
ELSE
RETURN ESCALATE
END IF
END IF
// Gates passed; check confidence-based disposition
IF confidence ≥ config.p_auto THEN
RETURN COMMIT
ELSE IF confidence ≥ config.p_review THEN
approval ← REQUEST_HUMAN_APPROVAL(output, confidence)
IF approval.approved THEN
RETURN COMMIT
ELSE IF approval.action = MODIFY THEN
INJECT_HUMAN_FEEDBACK(approval.feedback)
RETURN CONTINUE_REPAIR
ELSE
RETURN FAIL
END IF
ELSE
// Low confidence, below review threshold
IF repair_budget_remaining() THEN
RETURN CONTINUE_REPAIR
ELSE
RETURN FAIL
END IF
END IF
END15.12.5 Terminal State Summary#
The agent loop has exactly three terminal states, each with well-defined postconditions:
| Terminal State | Postcondition | Artifact Produced |
|---|---|---|
| COMMIT | All quality gates passed, confidence ≥ threshold, approval obtained (if required) | AgentResult with full provenance and audit |
| FAIL | Budget exhausted, escalation exhausted, or unrecoverable error | FailureReport with diagnosis, partial output, and checkpoint for manual recovery |
| HALT | External interrupt (operator stop, system shutdown) | HaltRecord with resumable checkpoint |
Every terminal state produces a durable artifact. The system never silently drops work.
Summary: The Agent Loop as an Engineering Discipline#
The agent loop, as formalized in this chapter, is a bounded, instrumented, fault-tolerant control system with the following properties:
| Property | Mechanism |
|---|---|
| Deterministic termination | Iteration bounds, recursion depth limits, cost budgets |
| Measurable quality | Multi-dimensional quality gates, rubric-based scoring, confidence estimation |
| Fault tolerance | Checkpointing, WAL, resumable execution, circuit breakers |
| Correctness assurance | Independent verification, critic agents, self-consistency, test execution |
| Safe state mutation | Saga rollback, compensating actions, idempotency, human approval gates |
| Observability | Traces at every phase boundary, cost attribution, audit records |
| Cost control | Token budgets, repair budgets, PID-governed iteration, early termination on stall |
| Convergence | Control-theoretic stability analysis, noise floor estimation, strategy escalation |
The agent loop is not prompt engineering. It is systems engineering applied to stochastic generation, where every phase is typed, every transition is logged, every failure is recoverable, and every output is provenance-complete. Production-grade agentic systems demand this level of rigor; anything less is prototype-grade experimentation.
End of Chapter 15.