# Advanced Agents From First Principles 17: What Is Your Agent Actually Uncertain About? # Book: Advanced Agents From First Principles # Chapter URL: https://programmer.ie/books/advanced-agents-from-first-principles/17-chapter/ # Prose fingerprint (sha256, normalized): 1133c18c4239ffc26860bff15ea3e1a04497ccc3b728f446c44e8e8ef20c8635 # Snapshot generated at build time by Hugo. This is the complete rendered prose # of the chapter; it is not a transcript of the source file. What Is Your Agent Actually Uncertain About? An agent reaches a difficult point in a task. It is not sure what to do next. A common implementation responds like this: uncertain ↓ call the model again ↓ still uncertain ↓ call a stronger model ↓ still uncertain ↓ search more That is not a reasoning strategy. It is a spending strategy. The system is using more computation without identifying what information is actually missing. The word confidence hides too much. An agent can be uncertain because: the user request is ambiguous, the evidence is weak, two routes look equally plausible, the current environment state may be stale, a tool result may be incomplete, multiple candidate solutions look similar, the verifier does not cover an important requirement, or the system simply does not know whether additional computation will help. Those are different problems. They should not trigger the same response. The central rule of this post is: Before spending more computation, identify what the system is uncertain about. The previous post introduced dynamic budget scheduling. This post gives the scheduler a better signal. Instead of one scalar: confidence = 0.63 we want something closer to: interpretation_uncertainty = 0.10 evidence_uncertainty = 0.82 route_uncertainty = 0.18 state_uncertainty = 0.76 candidate_uncertainty = 0.31 verification_uncertainty = 0.67 Now the system can make a more useful decision. If evidence uncertainty is high, retrieve evidence. If state uncertainty is high, inspect the environment. If route uncertainty is high, compare routes. If candidate uncertainty is high, search or generate alternatives. If verification uncertainty is high, improve verification coverage. Calling the model again becomes only one possible action. Confidence Is Not Uncertainty These two ideas are often collapsed. A model may report: I am 92% confident. That does not tell us: whether the task was interpreted correctly, whether the evidence was complete, whether the environment changed, whether the best route was considered, or whether the acceptance criteria were actually checked. A single confidence score can be useful in narrow calibrated prediction problems. But an advanced agent is not one prediction. It is a sequence of decisions over changing state. A better mental model is: state ↓ uncertainty decomposition ↓ which uncertainty matters most? ↓ which action can reduce it? ↓ is that reduction worth the cost? ↓ act / inspect / search / verify / stop The uncertainty model belongs in the control plane. It should help determine where the agent spends compute. Seven Useful Types of Agent Uncertainty There are many ways to classify uncertainty. For production agents, the useful classification is the one that changes what the runtime should do next. We will use seven categories. 1. interpretation uncertainty 2. evidence uncertainty 3. route uncertainty 4. state uncertainty 5. tool uncertainty 6. candidate uncertainty 7. verification uncertainty These are not philosophical categories. They are operational ones. 1. Interpretation Uncertainty The agent may not understand what the task actually requires. Examples: "Fix the cache problem." Which cache? "Make this faster." Latency? Throughput? Startup time? Cost? "Update the deployment." Which environment? Which service? Which version? Interpretation uncertainty usually means the system lacks a stable task specification. The correct next action may be: inspect repository context, inspect ticket metadata, retrieve prior decisions, infer constraints from state, or ask a clarification question when necessary. It is usually not: generate five more solutions More candidate generation does not resolve an ambiguous objective. Signals Possible interpretation-uncertainty signals include: multiple plausible task parses, missing required entities, conflicting constraints, unresolved references, large disagreement between task parsers, low retrieval support for the inferred objective. Useful action interpretation uncertainty ↓ resolve task specification ↓ then plan or act 2. Evidence Uncertainty The task may be understood perfectly while the supporting evidence is weak. A research agent may know exactly what claim it needs to evaluate but have only one secondary source. A coding agent may suspect a race condition but have no reproduction. A support agent may know which account is involved but not have the current billing state. Evidence uncertainty means: The system knows what question it is trying to answer but lacks enough reliable information to answer it. The right next action is often retrieval, inspection, measurement, or experiment. Not more verbal reasoning. Example claim: deployment failed because database migration timed out known evidence: - deployment marked failed - one log line mentions timeout missing evidence: - migration duration - database lock state - retry history - actual failing step The agent should buy evidence. inspect logs query deployment state inspect database lock metrics not: ask another model what it thinks happened 3. Route Uncertainty The objective and evidence may be clear, but there may be several plausible strategies. For example: failing test ↓ route A: fix parser route B: fix fixture route C: change schema Or: research question ↓ route A: primary documentation route B: source code inspection route C: benchmark experiment Route uncertainty is where routing policies, planners, or shallow search can help. Signals top routes have similar predicted success, router margin is small, multiple specialists are plausible, historical route regret is high for this task class, previous attempts disagree about the appropriate route. Useful action route uncertainty high ↓ compare routes cheaply ↓ select one ↓ commit more budget This is where Best-of-N or beam-style search can be useful. But only if the branches represent meaningfully different strategies. 4. State Uncertainty The agent may know what to do but not know the actual current environment state. This is extremely common. Examples: Which Git commit is currently checked out? Did the previous write succeed? Is the deployment already live? Has another process changed the file? Is the browser still authenticated? Did the database transaction commit? Has the external API state changed since the last observation? State uncertainty should usually trigger an observation, not more reasoning. state uncertain ↓ inspect authoritative state ↓ update runtime state ↓ continue This is one of the biggest differences between an agent and a chatbot. The environment is not static. The model’s previous belief about it may be wrong. Source-of-truth rule When exact state can be read directly, do not reconstruct it from conversation history or semantic memory. For coding agents: git status git diff current commit test result are stronger evidence than: "I remember editing that file earlier." 5. Tool Uncertainty Sometimes the agent knows which tool family it needs but does not know whether the tool result is trustworthy or complete. Examples: API call returned partial data, subprocess exited zero but wrote an error artifact, browser action clicked an element but page state did not change, search endpoint returned only the first page, test runner skipped tests, command timed out after partial side effects. Tool uncertainty is not the same as tool-selection uncertainty. The route may have been correct. The problem is whether the observation is reliable. Useful signals timeout, partial page, truncated response, retryable status, missing expected fields, stale timestamp, inconsistent follow-up observation, tool success without postcondition evidence. Useful action tool result uncertain ↓ validate result ↓ re-observe state ↓ retry or recover only if needed 6. Candidate Uncertainty Candidate uncertainty appears when the system has multiple plausible outputs but does not know which is better. This is where techniques such as: self-consistency, Best-of-N, pairwise ranking, Tree of Thoughts, beam search, MCTS, evolutionary search, may become relevant. But candidate uncertainty should not be confused with evidence uncertainty. If the real problem is missing evidence, generating more candidates can create confident nonsense faster. Example three patch strategies A: smallest change, passes tests B: cleaner abstraction, passes tests C: broader refactor, passes tests Now candidate comparison is meaningful. The environment has produced enough evidence to distinguish alternatives. Useful signals close evaluator scores, inconsistent pairwise ranking, high selection regret historically, diverse candidates with similar verification status, objective checks pass for multiple alternatives. 7. Verification Uncertainty An agent may produce an apparently strong result but still not know whether the actual goal was achieved. This is verification uncertainty. Examples: tests cover only part of the requested behavior, deployment succeeded but health checks are incomplete, research claim has sources but no independent corroboration, browser form submitted but confirmation was not observed, migration completed but data invariants were not checked. Verification uncertainty should trigger stronger acceptance evidence. Not another revision. candidate looks good ↓ verification incomplete ↓ run missing acceptance checks ↓ PASS / FAIL / UNKNOWN This uncertainty deserves special treatment because it controls whether the system is allowed to claim completion. Build an Uncertainty Vector Instead of one confidence number, represent the current uncertainty explicitly. from dataclasses import dataclass @dataclass(frozen=True) class UncertaintyVector: interpretation: float evidence: float route: float state: float tool: float candidate: float verification: float Values might be normalized into [0, 1]. Do not pretend they are perfectly probabilistic unless they are calibrated as probabilities. They can simply be comparable risk or uncertainty scores. The important part is that they are typed. Uncertainty Should Be Attached to Evidence A score without provenance is weak. Instead of: route_uncertainty = 0.71 prefer something like: @dataclass(frozen=True) class UncertaintySignal: kind: str score: float evidence_ids: tuple[str, ...] reason: str policy_version: str Now the runtime can answer: Why was route uncertainty high? Example: route uncertainty = 0.71 because: - top two route scores differ by 0.03 - historical routing regret is high for migration tasks - no exact repository ownership signal was found That is debuggable. Map Uncertainty to Actions The point of decomposition is not to produce more dashboards. It is to choose better next actions. A simple mapping might be: interpretation uncertainty → resolve task constraints evidence uncertainty → retrieve / inspect / measure route uncertainty → compare routes / planner / router state uncertainty → observe authoritative state tool uncertainty → validate tool output / re-observe candidate uncertainty → generate / search / rank alternatives verification uncertainty → run acceptance checks Notice how few of these require another generic model call. A Small Action Selector The action policy can begin as ordinary code. from enum import Enum class NextAction(str, Enum): RESOLVE_TASK = "resolve_task" GATHER_EVIDENCE = "gather_evidence" COMPARE_ROUTES = "compare_routes" OBSERVE_STATE = "observe_state" VALIDATE_TOOL = "validate_tool" SEARCH_CANDIDATES = "search_candidates" VERIFY = "verify" CONTINUE = "continue" def choose_action(u: UncertaintyVector) -> NextAction: scores = { NextAction.RESOLVE_TASK: u.interpretation, NextAction.GATHER_EVIDENCE: u.evidence, NextAction.COMPARE_ROUTES: u.route, NextAction.OBSERVE_STATE: u.state, NextAction.VALIDATE_TOOL: u.tool, NextAction.SEARCH_CANDIDATES: u.candidate, NextAction.VERIFY: u.verification, } action, score = max(scores.items(), key=lambda item: item[1]) if score < 0.35: return NextAction.CONTINUE return action This implementation is intentionally simple. It is not the final scheduler. It demonstrates the control principle: Different uncertainty should purchase different computation. Use Expected Uncertainty Reduction The previous post introduced expected value of computation. We can now refine it. For each possible action, estimate: expected reduction in relevant uncertainty ------------------------------------------ expected cost For example: inspect git status cost: tiny expected state uncertainty reduction: high versus: call frontier model cost: high expected state uncertainty reduction: almost zero The correct action becomes obvious. Example scheduler input action: git_status reduces: state uncertainty expected reduction: 0.70 cost: 0.001 action: generate_more_patches reduces: candidate uncertainty expected reduction: 0.20 cost: 0.12 action: frontier_model_review reduces: candidate uncertainty expected reduction: 0.30 cost: 0.45 If state uncertainty is currently the dominant problem, git_status wins decisively. Do Not Collapse Everything Back Into One Magic Score A common mistake is to decompose uncertainty and then immediately compute: overall_uncertainty = 0.2 * interpretation + 0.2 * evidence + 0.2 * route + ... Now we are back where we started. A single scalar can be useful for dashboards or escalation summaries. But the scheduler should preserve which uncertainty is high because that determines which action is useful. The identity of the uncertainty matters more than the aggregate magnitude. Uncertainty Can Be Correlated The categories are not independent. Poor task interpretation can create route uncertainty. Poor evidence can create candidate uncertainty. Stale state can create apparent tool uncertainty. Weak verification can make candidate ranking unreliable. So the system should preserve dependencies. Example: state stale ↓ tool output seems inconsistent ↓ candidate evaluator disagrees The wrong fix would be: run more candidate search The correct fix may simply be: refresh state This is why causal lineage from the previous observability post matters. Use the Cheapest Source of Truth The best uncertainty-reduction action is often deterministic. Examples: uncertainty: did file change? action: git diff uncertainty: is deployment healthy? action: query health endpoint uncertainty: did transaction commit? action: query database uncertainty: does API support field X? action: inspect schema/docs uncertainty: does patch compile? action: run compiler This reinforces a recurring rule of the series: Use deterministic evidence when deterministic evidence exists. Do not spend LLM inference to answer a question that a command, schema, test, or state query can answer directly. Coding Agent Example Suppose a coding agent receives: Fix the intermittent checkout failure. It finds two suspicious modules and one failing test. Initial uncertainty: interpretation = 0.15 evidence = 0.80 route = 0.55 state = 0.20 tool = 0.10 candidate = 0.30 verification = 0.75 The naive strategy might generate several patches immediately. The uncertainty-aware strategy sees that evidence uncertainty dominates. So it purchases diagnostics: run failing test repeatedly inspect timestamps inspect concurrent access trace shared mutable state After evidence gathering: interpretation = 0.10 evidence = 0.25 route = 0.30 state = 0.15 tool = 0.10 candidate = 0.65 verification = 0.60 Now candidate uncertainty dominates. The agent can generate two repair strategies. After both compile and pass focused tests: candidate = 0.20 verification = 0.82 The next spend should be broader verification. run full regression suite stress test intermittent scenario check invariant This is a much more efficient trajectory than repeatedly asking a model to reconsider the problem. Research Agent Example A research agent is asked: Did company X actually reduce inference cost by 70%? The claim is clear. interpretation = 0.05 But the only evidence is a company blog post. evidence = 0.90 More reasoning will not help. The agent needs: benchmark methodology, baseline definition, model version, hardware, workload, independent reproduction if available. After evidence retrieval, the problem may become verification uncertainty: verification = 0.75 because the claimed comparison cannot be reproduced from published details. The correct result may be: UNKNOWN not: call three more models and average their opinions Browser Agent Example A browser agent submits a purchase form. The click succeeds. Does that mean the purchase succeeded? No. Current uncertainty: state = 0.80 verification = 0.85 The right action is: observe confirmation page inspect order status look for transaction identifier not: click submit again Typed uncertainty prevents destructive retries. Data Agent Example A data agent runs a transformation pipeline. The process exits successfully. But row counts changed dramatically. Possible uncertainty vector: tool = 0.15 state = 0.20 candidate = 0.10 verification = 0.92 The execution mechanism is not the problem. The postconditions are uncertain. The scheduler should buy: schema checks row-count invariants null distribution key uniqueness sample reconciliation not another transformation attempt. DevOps Agent Example A deployment agent sees elevated latency after rollout. There are three possibilities: application regression infrastructure saturation external dependency degradation This may initially be route uncertainty. But the cheapest next move may be evidence gathering: compare service latency inspect CPU / memory inspect dependency timing compare previous deployment The uncertainty decomposition might change from: route = 0.75 evidence = 0.80 into: route = 0.20 evidence = 0.25 verification = 0.70 Now the task is no longer diagnosis. It is proving that the remediation actually restored service. Multi-Agent Systems Need Typed Uncertainty Too A multi-agent system often mistakes disagreement for useful diversity. Three agents may disagree because: the task is ambiguous, they saw different evidence, the router gave them different context, they are genuinely exploring different strategies, or one agent is simply wrong. Before launching a debate, classify the disagreement. disagreement ↓ what is uncertain? If evidence uncertainty is high, give the agents better evidence. If route uncertainty is high, debate may help. If state uncertainty is high, inspect the environment. If verification uncertainty is high, improve the verifier. Debate should not become the universal response to disagreement. Measure Uncertainty Reduction If uncertainty drives spend, you need to test whether actions actually reduce the uncertainty they target. For every scheduler action, log: uncertainty_before chosen_action expected_reduction actual_evidence_obtained uncertainty_after cost verified_outcome Now you can compute: actual uncertainty reduction ---------------------------- cost and compare action classes. This becomes a calibration problem. Metrics Useful metrics include: Uncertainty identification accuracy Did the system correctly identify the dominant failure source? Action targeting accuracy Did the chosen action address the uncertainty that was actually blocking progress? Uncertainty reduction per unit cost Δ uncertainty / cost Misallocated-compute rate How often did the system spend compute on a mechanism unrelated to the dominant uncertainty? Examples: search while evidence was missing model escalation while state was stale revision while verification was incomplete Verification-starvation rate Did speculative uncertainty reduction consume resources needed for final proof? Route flip rate How often does route selection change after cheap evidence gathering? A high route-flip rate may indicate that the router is operating before it has enough evidence. Candidate-search waste How often does candidate search occur before the task and evidence are stable? State-refresh rescue rate How often does refreshing state resolve an apparent reasoning failure? This can be surprisingly high in production systems. Calibrate Each Uncertainty Separately Do not assume that a score of 0.7 means the same thing across categories. For route uncertainty: 0.7 might mean: 70% chance the current route is suboptimal if you actually calibrate it that way. For state uncertainty it might simply be a heuristic indicating stale observations. Each score should have its own interpretation and validation dataset. Possible methods: reliability curves, Brier score, expected calibration error, threshold precision/recall, outcome-stratified confusion matrices. But only use probabilistic terminology when the score is actually calibrated probabilistically. Unknown Is Different From Uncertain Another important distinction: uncertain ≠ unavailable Sometimes the system can reduce uncertainty by spending more compute. Sometimes the needed evidence is simply unavailable. Example: required production log was deleted No amount of MCTS fixes that. The correct state may be: UNKNOWN The scheduler needs a terminal condition for irreducible uncertainty. uncertainty high ↓ relevant evidence unavailable ↓ no safe inference path ↓ UNKNOWN This is a feature, not a failure. Do Not Let the Agent Manufacture Evidence When evidence uncertainty is high, a dangerous failure mode is to reduce the score by generating a plausible explanation. That is not uncertainty reduction. That is narrative completion. The evidence store should distinguish: external observation primary source structured tool result deterministic test model-generated hypothesis model-generated summary A model-generated hypothesis should not carry the same evidential weight as an external observation. Keep Safety Outside the Uncertainty Policy An uncertainty-aware scheduler should not decide whether safety constraints apply. Examples that stay deterministic: authorization boundaries, prohibited tools, credential scopes, sandbox requirements, tenant isolation, mandatory approval gates, required verification for consequential actions. High confidence should never bypass them. Low uncertainty should never bypass them. The adaptive policy decides where to spend allowed computation. It does not redefine what is allowed. A Better Scheduler Interface The scheduler from the previous post can now receive typed uncertainty. from dataclasses import dataclass from typing import Sequence @dataclass(frozen=True) class CandidateAction: name: str cost: float targets: tuple[str, ...] expected_reduction: float @dataclass(frozen=True) class SchedulerDecision: action: str targeted_uncertainty: str expected_reduction: float expected_cost: float reason: str policy_version: str def choose_next_action( uncertainty: UncertaintyVector, actions: Sequence[CandidateAction], policy_version: str, ) -> SchedulerDecision: values = { "interpretation": uncertainty.interpretation, "evidence": uncertainty.evidence, "route": uncertainty.route, "state": uncertainty.state, "tool": uncertainty.tool, "candidate": uncertainty.candidate, "verification": uncertainty.verification, } dominant = max(values, key=values.get) applicable = [a for a in actions if dominant in a.targets] if not applicable: return SchedulerDecision( action="stop_unknown", targeted_uncertainty=dominant, expected_reduction=0.0, expected_cost=0.0, reason="No permitted action is expected to reduce dominant uncertainty", policy_version=policy_version, ) best = max( applicable, key=lambda a: a.expected_reduction / max(a.cost, 1e-9), ) return SchedulerDecision( action=best.name, targeted_uncertainty=dominant, expected_reduction=best.expected_reduction, expected_cost=best.cost, reason=f"Highest expected reduction per cost for {dominant}", policy_version=policy_version, ) Again, the point is not this exact formula. The point is the architecture. observe ↓ decompose uncertainty ↓ identify dominant uncertainty ↓ consider actions that can reduce it ↓ choose the best expected reduction per cost ↓ act ↓ measure actual reduction Test the Scheduler Against Fixed Policies Do not assume this architecture is better. Benchmark it. Use the same task set and maximum resource envelope. Compare: A. fixed max_steps B. fixed beam width C. always escalate on low confidence D. always run critic E. typed uncertainty scheduler Measure: verified success false success UNKNOWN rate cost per verified success p50 / p95 latency model calls tool calls search nodes verification spend misallocated-compute rate The uncertainty scheduler earns its place only if it improves the production frontier. Failure Injection A good benchmark should deliberately create uncertainty of different types. Interpretation injection Remove a key task constraint. Does the system detect ambiguity rather than inventing one? Evidence injection Hide an important source. Does it gather evidence instead of over-reasoning? Route injection Create two plausible repair paths. Does it compare routes? State injection Change the environment between observations. Does it refresh state? Tool injection Return a partial or stale tool result. Does it validate the observation? Candidate injection Provide multiple viable solutions. Does it compare them effectively? Verification injection Remove an acceptance criterion. Does it refuse to claim PASS? This turns uncertainty decomposition into a testable runtime mechanism. The Production Trace Should Explain the Spend A useful trajectory event might look like: { "event": "scheduler_decision", "dominant_uncertainty": "state", "score": 0.81, "action": "refresh_git_state", "expected_reduction": 0.72, "expected_cost": 0.001, "actual_reduction": 0.68, "policy_version": "uncertainty-v3" } Now when a run becomes expensive, you can answer: Where did the budget go? Why did we buy that action? What uncertainty was it supposed to reduce? Did it actually reduce it? That is much better than: The agent used 47 model calls. The Bigger Architectural Shift We started the basic series with an agent loop: observe ↓ decide ↓ act ↓ observe The advanced runtime is becoming something more structured: observe ↓ construct state ↓ decompose uncertainty ↓ choose which uncertainty matters ↓ select uncertainty-reducing action ↓ allocate budget ↓ act ↓ verify effect ↓ update state and uncertainty ↓ continue / verify / stop This is still an agent loop. But now the runtime understands why it is spending computation. That is the important part. Practical Decision Rules Do not use one generic confidence score for every control decision. Interpretation uncertainty should be resolved before expensive planning. Evidence uncertainty should purchase evidence, not more narrative. State uncertainty should purchase observation from the authoritative source. Route uncertainty is where routing comparison and shallow search are useful. Candidate uncertainty is where Best-of-N, search and ranking become useful. Verification uncertainty should purchase stronger acceptance evidence. Measure whether each action actually reduces the uncertainty it targets. Preserve UNKNOWN when required evidence cannot be obtained. Keep safety and authorization outside adaptive uncertainty policies. Final Principle The most expensive mistake in an advanced agent is often not choosing the wrong answer. It is spending computation on the wrong problem. A system with high evidence uncertainty does not need more imagination. A system with stale state does not need a bigger model. A system with weak verification does not need another revision. A system with ambiguous requirements does not need deeper search. So the rule is: Do not ask only, “How uncertain is the agent?” Ask, “What is the agent uncertain about, and what is the cheapest reliable action that can reduce that uncertainty?” That turns uncertainty from a vague feeling into a control signal. And once uncertainty becomes a control signal, the next stage becomes possible: selecting information-gathering actions by expected value of information rather than by model confidence or fixed orchestration rules.