Agentic Notes Library

Chapter 15: The Agent Loop — Bounded Control, Verification, and Failure Recovery

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 propertie...

March 20, 2026 20 min read 4,218 words
Chapter 15MathRaw HTML

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:

LayerResponsibility
User Boundary (JSON-RPC)Task submission, approval gates, result delivery
Orchestration (Agent Loop)Plan, execute, verify, repair, commit
Retrieval EngineProvenance-tagged evidence assembly
Tool Infrastructure (MCP / gRPC)Typed, discoverable, auditable tool execution
Memory SubsystemWorking, session, episodic, semantic, procedural layers
Observation PlaneLogs, metrics, traces, test harnesses

15.1.2 Formal Loop Definition#

Let the agent loop be defined as a finite state machine L=(S,s0,Σ,δ,F)\mathcal{L} = (S, s_0, \Sigma, \delta, F) where:

  • S={PLAN,DECOMPOSE,RETRIEVE,ACT,VERIFY,CRITIQUE,REPAIR,COMMIT,FAIL,HALT}S = \{\texttt{PLAN}, \texttt{DECOMPOSE}, \texttt{RETRIEVE}, \texttt{ACT}, \texttt{VERIFY}, \texttt{CRITIQUE}, \texttt{REPAIR}, \texttt{COMMIT}, \texttt{FAIL}, \texttt{HALT}\}
  • s0=PLANs_0 = \texttt{PLAN}
  • Σ\Sigma = set of transition events (phase completion, error, timeout, budget exceeded, quality gate passed/failed)
  • δ:S×ΣS\delta: S \times \Sigma \rightarrow S = deterministic transition function
  • F={COMMIT,FAIL,HALT}F = \{\texttt{COMMIT}, \texttt{FAIL}, \texttt{HALT}\} = terminal states

The transition function enforces:

δ(VERIFY,quality_gate_fail)=CRITIQUE\delta(\texttt{VERIFY}, \text{quality\_gate\_fail}) = \texttt{CRITIQUE} δ(CRITIQUE,diagnosis_complete)=REPAIR\delta(\texttt{CRITIQUE}, \text{diagnosis\_complete}) = \texttt{REPAIR} δ(REPAIR,repair_success)=VERIFY\delta(\texttt{REPAIR}, \text{repair\_success}) = \texttt{VERIFY} δ(REPAIR,budget_exceeded)=FAIL\delta(\texttt{REPAIR}, \text{budget\_exceeded}) = \texttt{FAIL} δ(VERIFY,quality_gate_pass)=COMMIT\delta(\texttt{VERIFY}, \text{quality\_gate\_pass}) = \texttt{COMMIT}

15.1.3 Loop Invariants#

Every iteration of the agent loop must preserve the following invariants:

  1. Token Budget Monotonicity: Cumulative token consumption Tcum(k)TmaxT_{\text{cum}}(k) \leq T_{\max} at iteration kk.
  2. Recursion Depth Bound: Loop depth d(k)dmaxd(k) \leq d_{\max}, a hard constant.
  3. State Persistence: Every state transition sisjs_i \rightarrow s_j is durably logged before sjs_j begins execution.
  4. Idempotency of State Mutations: Every tool invocation or state write is idempotent or guarded by a deduplication key.
  5. 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)
  END

15.1.5 Phase Contract Types#

Every phase boundary is a typed contract. The following type signatures define the inter-phase data flow:

PhaseInput TypeOutput Type
PLANTaskSpec × ExecutionContextPlan
DECOMPOSEPlan × ExecutionContextList<SubTask>
RETRIEVEList<SubTask> × ExecutionContextEvidenceBundle
ACTList<SubTask> × EvidenceBundle × ExecutionContextExecutionOutputs
VERIFYExecutionOutputs × TaskSpec × EvidenceBundleVerificationVerdict
CRITIQUEExecutionOutputs × VerificationVerdict × TaskSpecCritiqueDiagnosis
REPAIRExecutionOutputs × CritiqueDiagnosis × ExecutionContextExecutionOutputs
COMMITExecutionOutputs × TaskSpec × ExecutionContextAgentResult

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:

  • r(k)r(k): Setpoint — the desired quality level at iteration kk, typically a fixed target quality score r[0,1]r^* \in [0, 1].
  • y(k)y(k): Plant output — the measured quality of the agent's current output at iteration kk, produced by the verification and critique phases.
  • e(k)=ry(k)e(k) = r^* - y(k): Error signal — the quality deficit.
  • u(k)u(k): Control input — the repair action intensity (ranging from minimal edit to full regeneration).
  • GG: Plant model — the stochastic transformation from control input to quality improvement, representing the LLM's repair capability.

The loop objective is:

mink  ksubject toe(k)ϵgate,kkmax,Ccum(k)Cmax\min_{k} \; k \quad \text{subject to} \quad e(k) \leq \epsilon_{\text{gate}}, \quad k \leq k_{\max}, \quad C_{\text{cum}}(k) \leq C_{\max}

where ϵgate\epsilon_{\text{gate}} is the quality gate threshold, kmaxk_{\max} is the maximum iteration bound, and Ccum(k)=i=0kc(i)C_{\text{cum}}(k) = \sum_{i=0}^{k} c(i) is the cumulative cost (tokens, latency, API calls).

15.2.2 Discrete Feedback Dynamics#

Model the quality evolution as:

y(k+1)=y(k)+Gu(k)+w(k)y(k+1) = y(k) + G \cdot u(k) + w(k)

where:

  • G(0,1]G \in (0, 1] is the repair gain — the expected fractional improvement per unit of repair effort. In practice, GG is estimated empirically from historical repair trajectories and varies by task class.
  • w(k)N(0,σw2)w(k) \sim \mathcal{N}(0, \sigma_w^2) is process noise representing the stochastic nature of LLM generation (non-determinism, hallucination variance, context sensitivity).

The control law governing repair intensity is:

u(k)=Kpe(k)+Kij=0ke(j)+Kd(e(k)e(k1))u(k) = K_p \cdot e(k) + K_i \cdot \sum_{j=0}^{k} e(j) + K_d \cdot \big(e(k) - e(k-1)\big)

This is a discrete PID controller adapted for agent loops:

ParameterRole in Agent LoopPractical Interpretation
KpK_p (proportional)Immediate repair intensity proportional to current deficitLarge quality gap → aggressive repair (full regeneration)
KiK_i (integral)Accumulated deficit drives escalationPersistent failure → escalate to human or alternate strategy
KdK_d (derivative)Rate of improvement determines continuationDiminishing returns → early termination or strategy switch

15.2.3 Stability Conditions#

The loop is BIBO-stable (bounded-input, bounded-output) if and only if:

1GKp<1|1 - G \cdot K_p| < 1

which yields the stability constraint:

0<Kp<2G0 < K_p < \frac{2}{G}

Violating this bound causes oscillation: the agent alternately over-repairs and under-repairs, consuming budget without convergence. In production, KpK_p is set conservatively:

Kpprod=1G(1α),α[0.2,0.5]K_p^{\text{prod}} = \frac{1}{G} \cdot (1 - \alpha), \quad \alpha \in [0.2, 0.5]

where α\alpha is a damping margin selected to absorb stochastic variance w(k)w(k).

15.2.4 Convergence Rate and Budget Consumption#

Under the proportional-only controller u(k)=Kpe(k)u(k) = K_p \cdot e(k), the error decays as:

e(k)=(1GKp)ke(0)e(k) = (1 - G \cdot K_p)^k \cdot e(0)

The number of iterations to reach the quality gate ϵgate\epsilon_{\text{gate}} from initial error e(0)e(0) is:

k=ln(ϵgate/e(0))ln(1GKp)k^* = \left\lceil \frac{\ln(\epsilon_{\text{gate}} / e(0))}{\ln(1 - G \cdot K_p)} \right\rceil

The cumulative cost is:

Ccum(k)=i=0kcbase(1+βu(i))C_{\text{cum}}(k^*) = \sum_{i=0}^{k^*} c_{\text{base}} \cdot (1 + \beta \cdot u(i))

where cbasec_{\text{base}} is the per-iteration base cost and β\beta scales the cost impact of repair intensity. This formula directly informs budget allocation: if Ccum(k)>CmaxC_{\text{cum}}(k^*) > C_{\max}, the system must either relax ϵgate\epsilon_{\text{gate}}, reduce KpK_p, or fail gracefully.

15.2.5 Stability Analysis Under Stochastic Noise#

When w(k)w(k) is non-negligible, the expected squared error at steady state under proportional control is:

E[e2]=σw21(1GKp)2\mathbb{E}[e_{\infty}^2] = \frac{\sigma_w^2}{1 - (1 - G \cdot K_p)^2}

This implies a noise floor: even with unlimited iterations, quality cannot exceed:

ymax=rE[e2]y_{\max} = r^* - \sqrt{\mathbb{E}[e_{\infty}^2]}

If ymax<rϵgatey_{\max} < r^* - \epsilon_{\text{gate}}, 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
  END

15.2.7 Multi-Dimensional Quality Setpoints#

In practice, quality is not scalar. Define a quality vector:

y(k)=[ycorrect(k)ycomplete(k)ycoherent(k)ysafe(k)][0,1]4\mathbf{y}(k) = \begin{bmatrix} y_{\text{correct}}(k) \\ y_{\text{complete}}(k) \\ y_{\text{coherent}}(k) \\ y_{\text{safe}}(k) \end{bmatrix} \in [0, 1]^4

with setpoint vector r\mathbf{r}^* and error:

e(k)=ry(k)\mathbf{e}(k) = \mathbf{r}^* - \mathbf{y}(k)

The quality gate is satisfied when:

e(k)ϵgatemaxi  ei(k)ϵgate\|\mathbf{e}(k)\|_{\infty} \leq \epsilon_{\text{gate}} \quad \Leftrightarrow \quad \max_i \; e_i(k) \leq \epsilon_{\text{gate}}

The LL_\infty 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 τ0\tau_0 into a tree of subtasks through method application. Formally:

H=(Tp,Ta,M,s0)\mathcal{H} = (\mathcal{T}_p, \mathcal{T}_a, \mathcal{M}, s_0)

where:

  • Tp\mathcal{T}_p = set of primitive tasks (directly executable actions: tool calls, LLM generations, retrieval queries)
  • Ta\mathcal{T}_a = set of abstract tasks (compound goals requiring decomposition)
  • M:Ta2Seq(TpTa)\mathcal{M}: \mathcal{T}_a \rightarrow 2^{\text{Seq}(\mathcal{T}_p \cup \mathcal{T}_a)} = method library mapping abstract tasks to ordered sequences of subtasks
  • s0s_0 = initial world state

A valid plan is a total ordering π=a1,a2,,an\pi = \langle a_1, a_2, \ldots, a_n \rangle of primitive tasks obtained by recursively decomposing τ0\tau_0 through methods in M\mathcal{M} until only primitive tasks remain.

Goal Decomposition Trees (GDT)#

A GDT is a directed tree G=(V,E)G = (V, E) where:

  • Root v0v_0 represents the top-level goal.
  • Internal nodes are conjunctive (AND) or disjunctive (OR) decompositions.
  • Leaves are primitive executable actions.

The tree satisfies:

Complete(v0){cchildren(v)Complete(c)if v is AND-nodecchildren(v)Complete(c)if v is OR-node\text{Complete}(v_0) \Leftrightarrow \begin{cases} \bigwedge_{c \in \text{children}(v)} \text{Complete}(c) & \text{if } v \text{ is AND-node} \\ \bigvee_{c \in \text{children}(v)} \text{Complete}(c) & \text{if } v \text{ is OR-node} \end{cases}

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 D=(Vtask,Edep)D = (V_{\text{task}}, E_{\text{dep}}) where:

(ti,tj)Edepti must complete before tj begins(t_i, t_j) \in E_{\text{dep}} \Rightarrow t_i \text{ must complete before } t_j \text{ begins}

The critical path length determines minimum sequential execution time:

Tmin=maxpath P in DtPduration(t)T_{\text{min}} = \max_{\text{path } P \text{ in } D} \sum_{t \in P} \text{duration}(t)

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)
  END

15.3.2 Plan Representation: Ordered Action Lists, Partial-Order Plans, Conditional Plans#

Ordered Action Lists (Total-Order Plans)#

The simplest representation: π=a1,a2,,an\pi = \langle a_1, a_2, \ldots, a_n \rangle where aia_i executes before ai+1a_{i+1}. Total-order plans are deterministic and easy to checkpoint but sacrifice parallelism.

Partial-Order Plans (POP)#

A POP is a tuple (A,,B,L)(\mathcal{A}, \prec, \mathcal{B}, \mathcal{L}):

  • A\mathcal{A}: set of actions
  • \prec: partial ordering constraints (aiaja_i \prec a_j means aia_i before aja_j)
  • B\mathcal{B}: binding constraints on variables
  • L\mathcal{L}: causal links (aipaj)(a_i \xrightarrow{p} a_j) meaning aia_i produces condition pp consumed by aja_j

A POP is threat-free when no action aka_k can interpose between a causal link (aipaj)(a_i \xrightarrow{p} a_j) and negate pp. Threat resolution adds ordering constraints to maintain causal integrity.

The flexibility of a POP is measured by the number of valid linearizations:

Flexibility()={topological sorts of (A,)}\text{Flexibility}(\prec) = |\{\text{topological sorts of } (\mathcal{A}, \prec)\}|

Higher flexibility enables more parallelism and more rescheduling options under failure.

Conditional Plans (Contingency Plans)#

For tasks with uncertain outcomes, plans include conditional branches:

πcond=a1;  IF ϕ THEN πtrue ELSE πfalse\pi_{\text{cond}} = a_1; \; \text{IF } \phi \text{ THEN } \pi_{\text{true}} \text{ ELSE } \pi_{\text{false}}

where ϕ\phi is an observation predicate evaluated at runtime. Conditional plans increase plan size exponentially in the worst case:

πcond=O(2b)|\pi_{\text{cond}}| = O(2^{b})

where bb is the number of branch points. In practice, branch depth is bounded to bbmaxb \leq b_{\max} (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 F:Plan×State{0,1}n\mathcal{F}: \text{Plan} \times \text{State} \rightarrow \{0, 1\}^n where each component Fi\mathcal{F}_i validates:

CheckFormal ConditionFailure Action
Tool Availabilityai:tool(ai)ToolRegistryactive\forall a_i: \text{tool}(a_i) \in \text{ToolRegistry}_{\text{active}}Substitute tool or fail
Pre-condition Satisfactionai:precond(ai)scurrent\forall a_i: \text{precond}(a_i) \subseteq s_{\text{current}}Reorder or add prerequisite actions
Token Budgetic^tokens(ai)TmaxTconsumed\sum_i \hat{c}_{\text{tokens}}(a_i) \leq T_{\max} - T_{\text{consumed}}Prune low-priority subtasks
Latency BudgetTcritical_pathLmaxT_{\text{critical\_path}} \leq L_{\max}Increase parallelism or reduce scope
Permission GatesaiAmutating:auth(ai)=GRANTED\forall a_i \in \mathcal{A}_{\text{mutating}}: \text{auth}(a_i) = \text{GRANTED}Request approval or defer
DAG AcyclicityDD is a directed acyclic graphReject plan, re-plan
Resource Concurrencymaxt{ai:ai active at t}Pmax\max_t \|\{a_i : a_i \text{ active at } t\}\| \leq P_{\max}Serialize or increase limits

The plan is valid if and only if:

F(π,s)=1n\mathcal{F}(\pi, s) = \mathbf{1}_n

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)
  END

15.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 aia_i is ready when all its predecessors have completed:

Ready(ai,k)(aj,ai)Edep:status(aj)=COMPLETED\text{Ready}(a_i, k) \Leftrightarrow \forall (a_j, a_i) \in E_{\text{dep}}: \text{status}(a_j) = \texttt{COMPLETED}

The ready set at tick kk is:

R(k)={aiA:Ready(ai,k)status(ai)=PENDING}\mathcal{R}(k) = \{a_i \in \mathcal{A} : \text{Ready}(a_i, k) \wedge \text{status}(a_i) = \texttt{PENDING}\}

Actions are dispatched from R(k)\mathcal{R}(k) subject to a concurrency limit PmaxP_{\max} and priority ordering:

dispatch_order(R)=sort(R,key=λa:(priority(a),downstream_count(a)))\text{dispatch\_order}(\mathcal{R}) = \text{sort}(\mathcal{R}, \text{key}=\lambda a: (-\text{priority}(a), -\text{downstream\_count}(a)))

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
  END

15.4.2 Tool Invocation with Timeout and Retry Policies#

Every tool invocation is governed by a typed invocation contract specifying:

InvocationPolicy(a)=(τtimeout,Rmax,backoff(),circuit_state,idempotency_key)\text{InvocationPolicy}(a) = \left(\tau_{\text{timeout}}, R_{\max}, \text{backoff}(\cdot), \text{circuit\_state}, \text{idempotency\_key}\right)
FieldTypeSemantics
τtimeout\tau_{\text{timeout}}DurationMaximum wall-clock time before abort
RmaxR_{\max}IntMaximum retry attempts
backoff(r)\text{backoff}(r)Duration → DurationDelay before retry rr
circuit_state\text{circuit\_state}`CLOSED \OPEN \
idempotency_key\text{idempotency\_key}StringDeduplication key for at-most-once semantics

Exponential Backoff with Jitter#

The backoff function prevents thundering herd effects:

backoff(r)=min(bmax,  b02r+Uniform(0,jmax))\text{backoff}(r) = \min\left(b_{\max}, \; b_0 \cdot 2^r + \text{Uniform}(0, j_{\max})\right)

where b0b_0 is the base delay, bmaxb_{\max} is the ceiling, and jmaxj_{\max} is the jitter range.

Circuit Breaker State Machine#

The circuit breaker prevents repeated invocations of a failing tool:

δcb(CLOSED,failure_countfthresh)=OPEN\delta_{\text{cb}}(\texttt{CLOSED}, \text{failure\_count} \geq f_{\text{thresh}}) = \texttt{OPEN} δcb(OPEN,cooldown_elapsed)=HALF_OPEN\delta_{\text{cb}}(\texttt{OPEN}, \text{cooldown\_elapsed}) = \texttt{HALF\_OPEN} δcb(HALF_OPEN,success)=CLOSED\delta_{\text{cb}}(\texttt{HALF\_OPEN}, \text{success}) = \texttt{CLOSED} δcb(HALF_OPEN,failure)=OPEN\delta_{\text{cb}}(\texttt{HALF\_OPEN}, \text{failure}) = \texttt{OPEN}
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)
  END

15.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#

Checkpoint(k)=(loop_id,k,phase,plan_state,completed_actions,pending_actions,outputs_so_far,token_consumption,timestamp)\text{Checkpoint}(k) = \left(\text{loop\_id}, k, \text{phase}, \text{plan\_state}, \text{completed\_actions}, \text{pending\_actions}, \text{outputs\_so\_far}, \text{token\_consumption}, \text{timestamp}\right)

Checkpoints are stored in an append-only, content-addressed store:

key(Checkpoint)=SHA256(loop_idk)\text{key}(\text{Checkpoint}) = \text{SHA256}(\text{loop\_id} \| k)

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):

  1. Write the intent record to the WAL: INTENT(action_id, tool, input, timestamp)
  2. Execute the action.
  3. Write the completion record: COMPLETE(action_id, result, timestamp) or ABORT(action_id, error, timestamp)

On recovery, the WAL is replayed:

  • Actions with INTENT but no COMPLETE or ABORT are in-flight: recheck via idempotency key, then retry or mark as failed.
  • Actions with COMPLETE are skipped.
  • Actions with ABORT trigger 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)
  END

15.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:

SchemaValid(o,σ)={1if oσ0otherwise\text{SchemaValid}(o, \sigma) = \begin{cases} 1 & \text{if } o \models \sigma \\ 0 & \text{otherwise} \end{cases}

where σ\sigma 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:

Vsem(o,E)=1CcC1[NLI(E,c){ENTAIL,NEUTRAL}]V_{\text{sem}}(o, E) = \frac{1}{|C|} \sum_{c \in C} \mathbb{1}\left[\text{NLI}(E, c) \in \{\texttt{ENTAIL}, \texttt{NEUTRAL}\}\right]

where CC is the set of claims extracted from output oo, EE 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:

Vfact(o)={cC:sSauth:supports(s,c)}CV_{\text{fact}}(o) = \frac{|\{c \in C : \exists s \in \mathcal{S}_{\text{auth}} : \text{supports}(s, c)\}|}{|C|}

where Sauth\mathcal{S}_{\text{auth}} is the set of authoritative sources (knowledge bases, verified databases, official documentation).

Composite Verification Score#

The overall verification verdict combines all levels:

V(o)={0if SchemaValid(o)=0wsemVsem(o,E)+wfactVfact(o)otherwiseV(o) = \begin{cases} 0 & \text{if } \text{SchemaValid}(o) = 0 \\ w_{\text{sem}} \cdot V_{\text{sem}}(o, E) + w_{\text{fact}} \cdot V_{\text{fact}}(o) & \text{otherwise} \end{cases}

The quality gate passes when V(o)VthresholdV(o) \geq V_{\text{threshold}}.

15.5.2 Test Execution: Unit, Integration, and Behavioral Test Harnesses#

For code-producing agents, verification includes executable test suites:

Test LevelScopeTriggerPass Criterion
UnitIndividual function/componentEvery action producing codeAll unit tests pass
IntegrationCross-component interactionAfter all code actions completeIntegration suite passes
BehavioralEnd-to-end user-facing behaviorBefore commitAll acceptance tests pass
RegressionPreviously fixed defectsBefore commitNo regressions introduced

The test harness is exposed to the agent runtime (Section 15.4.1) so the agent can observe failures directly:

TestResult=(passed:Int,failed:Int,errors:ListTestFailure)\text{TestResult} = \left(\text{passed}: \text{Int}, \text{failed}: \text{Int}, \text{errors}: \text{List}\langle\text{TestFailure}\rangle\right)

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 NN independent responses {o1,,oN}\{o_1, \ldots, o_N\} for the same task (with temperature T>0T > 0) and measures agreement.

Majority Voting#

For tasks with discrete answers:

o=argmaxo{o1,,oN}i=1N1[oi=o]o^* = \arg\max_{o \in \{o_1, \ldots, o_N\}} \sum_{i=1}^{N} \mathbb{1}[o_i = o]

The consistency score is:

Consistency(o)={i:oi=o}N\text{Consistency}(o^*) = \frac{|\{i : o_i = o^*\}|}{N}

If Consistency(o)<γmin\text{Consistency}(o^*) < \gamma_{\text{min}}, the result is flagged as low-confidence and routed to critique.

Semantic Similarity Voting#

For free-form outputs, pairwise semantic similarity replaces exact match:

Sˉ=2N(N1)i<jsim(oi,oj)\bar{S} = \frac{2}{N(N-1)} \sum_{i < j} \text{sim}(o_i, o_j)

where sim\text{sim} is cosine similarity of embeddings. The output with highest average similarity to all others is selected:

o=argmaxok1N1jksim(ok,oj)o^* = \arg\max_{o_k} \frac{1}{N-1} \sum_{j \neq k} \text{sim}(o_k, o_j)

Cost-Quality Trade-off#

Self-consistency scales linearly in cost: NN generations cost N×cbaseN \times c_{\text{base}}. The diminishing marginal value of additional samples follows:

ΔAccuracy(N)aNb,b[0.5,1.0]\Delta \text{Accuracy}(N) \approx \frac{a}{N^b}, \quad b \in [0.5, 1.0]

Typically N{3,5,7}N \in \{3, 5, 7\} provides sufficient consistency signal. Beyond N=7N = 7, 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
  END

15.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#

PropertyGenerator AgentCritic Agent
System PromptTask-execution orientedEvaluation-and-defect-detection oriented
Context WindowTask spec + plan + evidence + prior outputsTask spec + output under review + evaluation rubric
ModelPrimary generation modelEvaluation-specialized model (may differ)
Memory AccessFull working memoryRead-only access to task memory
ObjectiveMaximize task completion qualityMaximize defect detection recall

The critic receives the output oo, the original task specification τ\tau, the evidence bundle EE, and a structured evaluation rubric R\mathcal{R}, 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#

Critique:(o,τ,E,R)CritiqueDiagnosis\text{Critique}: (o, \tau, E, \mathcal{R}) \rightarrow \text{CritiqueDiagnosis}

where:

CritiqueDiagnosis=(scores:RR,  defects:ListDefect,  severity:Severity,  repair_hints:ListRepairHint)\text{CritiqueDiagnosis} = \left(\text{scores}: \mathbb{R}^{|\mathcal{R}|}, \; \text{defects}: \text{List}\langle\text{Defect}\rangle, \; \text{severity}: \text{Severity}, \; \text{repair\_hints}: \text{List}\langle\text{RepairHint}\rangle\right)

15.6.2 Rubric-Based Scoring: Correctness, Completeness, Coherence, Safety#

The evaluation rubric R\mathcal{R} defines dimensions and scoring criteria:

R={(di,wi,ϕi)}i=1m\mathcal{R} = \{(d_i, w_i, \phi_i)\}_{i=1}^{m}

where:

  • did_i = dimension name (correctness, completeness, coherence, safety, …)
  • wiw_i = weight such that iwi=1\sum_i w_i = 1
  • ϕi:(o,τ,E)[0,1]\phi_i: (o, \tau, E) \rightarrow [0, 1] = scoring function for dimension ii

The weighted aggregate score is:

Q(o)=i=1mwiϕi(o,τ,E)Q(o) = \sum_{i=1}^{m} w_i \cdot \phi_i(o, \tau, E)

Individual dimension gates enforce that no dimension falls below a minimum:

i:ϕi(o,τ,E)ϕimin\forall i: \phi_i(o, \tau, E) \geq \phi_i^{\min}

The critique passes if and only if:

Q(o)Qthresholdi:ϕi(o,τ,E)ϕiminQ(o) \geq Q_{\text{threshold}} \quad \wedge \quad \forall i: \phi_i(o, \tau, E) \geq \phi_i^{\min}
Dimensionϕi\phi_i DescriptionWeight (typical)Minimum
CorrectnessFactual accuracy, logical validity, code correctness0.400.80
CompletenessCoverage of all task requirements0.250.70
CoherenceInternal consistency, logical flow, no contradictions0.200.75
SafetyNo harmful content, no credential leaks, policy compliance0.150.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:

  1. Identify inputs or scenarios where the output would produce incorrect results.
  2. Find logical inconsistencies or unstated assumptions.
  3. Generate counterexamples that violate claimed properties.
  4. 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:

Eadv=AdversarialCritic(o,τ)={(xj,yjexpected)}j=1Emax\mathcal{E}_{\text{adv}} = \text{AdversarialCritic}(o, \tau) = \{(x_j, y_j^{\text{expected}})\}_{j=1}^{E_{\max}}

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
    )
  END

15.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:

RCA:DefectRootCauseClass\text{RCA}: \text{Defect} \rightarrow \text{RootCauseClass}
Root Cause ClassDescriptionTypical Repair Strategy
SCHEMA_VIOLATIONOutput structure does not match typeRe-format or re-generate structured output
FACTUAL_ERRORIncorrect claim contradicted by evidenceReplace claim with evidence-supported statement
LOGIC_ERRORInvalid reasoning or code bugTargeted fix at identified location
INCOMPLETENESSMissing required componentAdditive generation for missing parts
COHERENCE_FAILUREInternal contradictionRewrite contradictory sections
SAFETY_VIOLATIONPolicy-violating contentRemove or redact violating content
HALLUCINATIONClaim not grounded in any evidenceRemove or replace with grounded alternative
RETRIEVAL_GAPInsufficient evidence for taskRe-retrieve with expanded queries, then regenerate
TOOL_FAILURETool returned error or unexpected resultRetry tool, substitute tool, or adjust input

For code-producing agents, stack traces from failed tests are parsed to extract:

StackTraceAnalysis=(failing_function,line_number,error_type,input_values,expected_vs_actual)\text{StackTraceAnalysis} = \left(\text{failing\_function}, \text{line\_number}, \text{error\_type}, \text{input\_values}, \text{expected\_vs\_actual}\right)

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#

Strategy(d)={MINIMAL_EDITif d.severityseditd.location is preciseSECTION_REWRITEif d.severitysrewrited.scope is boundedFULL_REGENERATIONif d.severity>srewrited.scope=GLOBAL\text{Strategy}(d) = \begin{cases} \texttt{MINIMAL\_EDIT} & \text{if } d.\text{severity} \leq s_{\text{edit}} \wedge d.\text{location} \text{ is precise} \\ \texttt{SECTION\_REWRITE} & \text{if } d.\text{severity} \leq s_{\text{rewrite}} \wedge d.\text{scope} \text{ is bounded} \\ \texttt{FULL\_REGENERATION} & \text{if } d.\text{severity} > s_{\text{rewrite}} \vee d.\text{scope} = \texttt{GLOBAL} \end{cases}

Cost Model#

Cost(MINIMAL_EDIT)Cost(SECTION_REWRITE)Cost(FULL_REGENERATION)\text{Cost}(\texttt{MINIMAL\_EDIT}) \ll \text{Cost}(\texttt{SECTION\_REWRITE}) \ll \text{Cost}(\texttt{FULL\_REGENERATION})

Specifically:

Cost(MINIMAL_EDIT)ceditedit_region\text{Cost}(\texttt{MINIMAL\_EDIT}) \approx c_{\text{edit}} \cdot |\text{edit\_region}| Cost(SECTION_REWRITE)cgensection\text{Cost}(\texttt{SECTION\_REWRITE}) \approx c_{\text{gen}} \cdot |\text{section}| Cost(FULL_REGENERATION)cgenfull_output\text{Cost}(\texttt{FULL\_REGENERATION}) \approx c_{\text{gen}} \cdot |\text{full\_output}|

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:

Strategy=argminsCost(s)s.t.P(resolves,class(d))pmin\text{Strategy}^* = \arg\min_{s} \text{Cost}(s) \quad \text{s.t.} \quad P(\text{resolve} \mid s, \text{class}(d)) \geq p_{\min}

Minimal Edit Repair Procedure#

  1. Locate the defect within the output using Defect.location.
  2. Extract a bounded repair window around the defect site: [w,+w][\ell - w, \ell + w] where \ell is the defect line and ww is the window radius.
  3. Construct a repair prompt containing:
    • The repair window content
    • The defect description and root cause
    • The relevant evidence fragment
    • The repair hint from critique
  4. Generate a replacement for the repair window only.
  5. Splice the replacement back into the original output.
  6. 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:

RepairBudget=(Rmax,Cmaxrepair,Tmaxrepair)\text{RepairBudget} = \left(R_{\max}, C_{\max}^{\text{repair}}, T_{\max}^{\text{repair}}\right)

where:

  • RmaxR_{\max}: maximum number of repair iterations
  • CmaxrepairC_{\max}^{\text{repair}}: maximum token cost allocated to repairs
  • TmaxrepairT_{\max}^{\text{repair}}: maximum wall-clock time for repair phase

Escalation Policy#

When the repair budget is exhausted without achieving the quality gate:

Escalate(d,budget_status)={HUMAN_REVIEWif d.requires_domain_expertiseMODEL_UPGRADEif d.class=CAPABILITY_LIMITTASK_REDECOMPOSEif d.class=COMPLEXITY_EXCEEDEDPARTIAL_COMMITif partial_output_is_usefulFAIL_WITH_DIAGNOSISotherwise\text{Escalate}(d, \text{budget\_status}) = \begin{cases} \texttt{HUMAN\_REVIEW} & \text{if } d.\text{requires\_domain\_expertise} \\ \texttt{MODEL\_UPGRADE} & \text{if } d.\text{class} = \texttt{CAPABILITY\_LIMIT} \\ \texttt{TASK\_REDECOMPOSE} & \text{if } d.\text{class} = \texttt{COMPLEXITY\_EXCEEDED} \\ \texttt{PARTIAL\_COMMIT} & \text{if } \text{partial\_output\_is\_useful} \\ \texttt{FAIL\_WITH\_DIAGNOSIS} & \text{otherwise} \end{cases}
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)
  END

15.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:

Provenance(o)=(task_id,plan_hash,evidence_refs,tool_invocations,repair_history,verification_scores,model_id,timestamp,approver)\text{Provenance}(o) = \left(\text{task\_id}, \text{plan\_hash}, \text{evidence\_refs}, \text{tool\_invocations}, \text{repair\_history}, \text{verification\_scores}, \text{model\_id}, \text{timestamp}, \text{approver}\right)

This provenance record is:

  1. Immutable: Once committed, the provenance record cannot be altered.
  2. Content-addressed: hash(Provenance)=SHA256(canonical_serialization(Provenance))\text{hash}(\text{Provenance}) = \text{SHA256}(\text{canonical\_serialization}(\text{Provenance}))
  3. 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:

AuditRecord=(loop_trace,all_checkpoints,all_tool_traces,all_repair_logs,total_cost,total_latency,final_quality_scores)\text{AuditRecord} = \left(\text{loop\_trace}, \text{all\_checkpoints}, \text{all\_tool\_traces}, \text{all\_repair\_logs}, \text{total\_cost}, \text{total\_latency}, \text{final\_quality\_scores}\right)

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
  END

15.8.2 State Transition Logging and Checkpoint Commit#

The final checkpoint after commit marks the loop as terminally complete:

Checkpointfinal=Checkpoint(kfinal,COMMIT,result_hash,total_cost)\text{Checkpoint}_{\text{final}} = \text{Checkpoint}(k_{\text{final}}, \texttt{COMMIT}, \text{result\_hash}, \text{total\_cost})

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 k=0k=0 to kfinalk_{\text{final}} 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:

  1. Nested agent invocations: An agent invokes a sub-agent, which invokes another sub-agent.
  2. Repair-verify cycles: Repair produces output that fails verification, triggering another critique-repair cycle.
  3. Plan re-decomposition: A failed subtask triggers re-planning, which may generate new subtasks that themselves require planning.
  4. 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 dd as the nesting level of agent invocations:

d(root_agent)=0d(\text{root\_agent}) = 0 d(sub_agent)=d(parent)+1d(\text{sub\_agent}) = d(\text{parent}) + 1

The system enforces:

agent:d(agent)dmax\forall \text{agent}: d(\text{agent}) \leq d_{\max}

Similarly, define the repair cycle count rr within a single agent:

rRmaxr \leq R_{\max}

And the total iteration count across all recursion levels:

all agentskiKglobal_max\sum_{\text{all agents}} k_i \leq K_{\text{global\_max}}

Theorem (Termination Guarantee): Under the constraints ddmaxd \leq d_{\max}, rRmaxr \leq R_{\max} per agent, and Kglobal_maxK_{\text{global\_max}} as a global iteration cap, the agent loop terminates in at most Kglobal_maxK_{\text{global\_max}} iterations with a deterministic terminal state in {COMMIT,FAIL,HALT}\{\texttt{COMMIT}, \texttt{FAIL}, \texttt{HALT}\}.

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 RmaxR_{\max}), spawns a sub-agent (bounded by dmaxd_{\max}), or exhausts a budget (cost, iterations, time), each of which triggers a terminal transition. The product of all bounds is finite. \square

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:

LoopDetected(k)k<k:sim(ok,ok)>θloopsim(diagnosisk,diagnosisk)>θloop\text{LoopDetected}(k) \Leftrightarrow \exists k' < k: \text{sim}(o_{k'}, o_k) > \theta_{\text{loop}} \wedge \text{sim}(\text{diagnosis}_{k'}, \text{diagnosis}_k) > \theta_{\text{loop}}

When a loop is detected, the system breaks the cycle by:

  1. Switching the repair strategy (e.g., from minimal edit to full regeneration).
  2. Perturbing the generation temperature or prompt structure.
  3. Introducing fresh evidence via an expanded retrieval query.
  4. 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
  END

15.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 ClassDefinitionExamplesRollback Mechanism
PureNo side effectsLLM generation, embedding computationNo rollback needed
ReversibleSide effects can be undoneFile write (delete file), branch creation (delete branch)Compensating action
CompensableCannot undo, but can issue compensating actionPayment (refund), notification (correction notice)Compensating action with semantic difference
IrreversibleCannot be undone or compensatedExternal API call with permanent effect, data deletionMust gate with approval before execution

The plan must explicitly record the compensating action for every non-pure action:

aiAnon-pure:aˉi such that apply(aˉi,apply(ai,s))s\forall a_i \in \mathcal{A}_{\text{non-pure}}: \exists \bar{a}_i \text{ such that } \text{apply}(\bar{a}_i, \text{apply}(a_i, s)) \approx s

where \approx 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:

  1. Each action aia_i has a compensating action aˉi\bar{a}_i.
  2. Actions execute in order.
  3. If action aka_k fails, compensating actions aˉk1,aˉk2,,aˉ1\bar{a}_{k-1}, \bar{a}_{k-2}, \ldots, \bar{a}_1 execute in reverse order.
Saga(π)=a1,a2,,an\text{Saga}(\pi) = a_1, a_2, \ldots, a_n Compensate(k)=aˉk1,aˉk2,,aˉ1\text{Compensate}(k) = \bar{a}_{k-1}, \bar{a}_{k-2}, \ldots, \bar{a}_1
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
    )
  END

15.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:

apply(aˉi,apply(aˉi,s))=apply(aˉi,s)\text{apply}(\bar{a}_i, \text{apply}(\bar{a}_i, s)) = \text{apply}(\bar{a}_i, s)

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 ModeCauseRecovery Mechanism
Process CrashOOM, segfault, unhandled exceptionResume from last checkpoint
TimeoutLatency budget exceededResume with extended budget or reduced scope
Resource ExhaustionToken budget, API rate limit, storageResume after resource replenishment
Model ErrorAPI error, model overloadRetry with backoff or failover model
Tool FailureExternal service downCircuit breaker, retry, or substitute tool
Human InterruptOperator cancels or pausesPersist 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
  END

15.11.3 Consistency Guarantees#

The resumption protocol provides:

  1. At-most-once for irreversible actions: Idempotency keys prevent double-execution.
  2. At-least-once for retriable actions: Pending actions are re-dispatched on resume.
  3. Exactly-once for the loop state machine: The checkpoint + WAL combination ensures the loop state is reconstructed precisely.

The consistency model is formalized as:

Stateresumed=StatecheckpointWALreplay=Statepre-crash\text{State}_{\text{resumed}} = \text{State}_{\text{checkpoint}} \oplus \text{WAL}_{\text{replay}} = \text{State}_{\text{pre-crash}}

where \oplus 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:

EXITcommitgGg(o,τ,E)=PASS\text{EXIT}_{\text{commit}} \Leftrightarrow \bigwedge_{g \in \mathcal{G}} g(o, \tau, E) = \texttt{PASS}

where G\mathcal{G} is the set of quality gates. Each gate is a predicate with a measurable threshold:

GateFormal ConditionThreshold (typical)
Schema ValiditySchemaValid(o,σ)=1\text{SchemaValid}(o, \sigma) = 1Exact (no tolerance)
Correctness Scoreϕcorrect(o)ϕcorrectmin\phi_{\text{correct}}(o) \geq \phi_{\text{correct}}^{\min}0.850.85
Completeness Scoreϕcomplete(o)ϕcompletemin\phi_{\text{complete}}(o) \geq \phi_{\text{complete}}^{\min}0.800.80
Coherence Scoreϕcoherent(o)ϕcoherentmin\phi_{\text{coherent}}(o) \geq \phi_{\text{coherent}}^{\min}0.800.80
Safety Scoreϕsafe(o)ϕsafemin\phi_{\text{safe}}(o) \geq \phi_{\text{safe}}^{\min}0.980.98
Test Pass Ratepassedtotalρmin\frac{\text{passed}}{\text{total}} \geq \rho_{\min}1.01.0 (all tests pass)
Self-ConsistencyConsistency(o)γmin\text{Consistency}(o^*) \geq \gamma_{\min}0.700.70
ConfidenceP(correcto,E)pminP(\text{correct} \mid o, E) \geq p_{\min}0.900.90

15.12.2 Confidence Estimation#

The agent's confidence in its output is estimated through multiple signals:

p^(o)=σ(jαjfj(o))\hat{p}(o) = \sigma\left(\sum_{j} \alpha_j \cdot f_j(o)\right)

where σ\sigma is the sigmoid function, fjf_j are confidence features, and αj\alpha_j are learned weights:

Feature fjf_jDescription
fverifyf_{\text{verify}}Verification phase composite score
fconsistf_{\text{consist}}Self-consistency score
fevidencef_{\text{evidence}}Evidence coverage (fraction of claims grounded)
frepairf_{\text{repair}}Number of repair iterations (negative signal)
flogprobf_{\text{logprob}}Average log-probability of generated tokens
fcriticf_{\text{critic}}Critic agent's overall assessment score
ftestf_{\text{test}}Test pass rate (for code tasks)

The confidence threshold pminp_{\min} determines whether the output can be auto-committed or requires human review:

Disposition(o)={AUTO_COMMITif p^(o)pautoHUMAN_REVIEWif previewp^(o)<pautoREJECTif p^(o)<preview\text{Disposition}(o) = \begin{cases} \texttt{AUTO\_COMMIT} & \text{if } \hat{p}(o) \geq p_{\text{auto}} \\ \texttt{HUMAN\_REVIEW} & \text{if } p_{\text{review}} \leq \hat{p}(o) < p_{\text{auto}} \\ \texttt{REJECT} & \text{if } \hat{p}(o) < p_{\text{review}} \end{cases}

with typical thresholds pauto=0.95p_{\text{auto}} = 0.95, preview=0.70p_{\text{review}} = 0.70.

15.12.3 Human Approval Triggers#

Human approval is mechanically triggered, not optionally consulted:

Trigger ConditionJustification
Confidence below auto-commit thresholdInsufficient automated assurance
Output involves irreversible state mutationSafety-critical action
Task explicitly flagged as requiring approvalPolicy requirement
Novel task pattern not seen in training dataOut-of-distribution detection
Budget exceeded with partial outputPartial result may require human judgment
Safety score below safety gatePotential policy violation

The human approval request is structured:

ApprovalRequest=(output,provenance,quality_scores,confidence,risk_assessment,recommended_action)\text{ApprovalRequest} = \left(\text{output}, \text{provenance}, \text{quality\_scores}, \text{confidence}, \text{risk\_assessment}, \text{recommended\_action}\right)

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
  END

15.12.5 Terminal State Summary#

The agent loop has exactly three terminal states, each with well-defined postconditions:

Terminal StatePostconditionArtifact Produced
COMMITAll quality gates passed, confidence ≥ threshold, approval obtained (if required)AgentResult with full provenance and audit
FAILBudget exhausted, escalation exhausted, or unrecoverable errorFailureReport with diagnosis, partial output, and checkpoint for manual recovery
HALTExternal 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:

PropertyMechanism
Deterministic terminationIteration bounds, recursion depth limits, cost budgets
Measurable qualityMulti-dimensional quality gates, rubric-based scoring, confidence estimation
Fault toleranceCheckpointing, WAL, resumable execution, circuit breakers
Correctness assuranceIndependent verification, critic agents, self-consistency, test execution
Safe state mutationSaga rollback, compensating actions, idempotency, human approval gates
ObservabilityTraces at every phase boundary, cost attribution, audit records
Cost controlToken budgets, repair budgets, PID-governed iteration, early termination on stall
ConvergenceControl-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.