# Advanced Agents From First Principles 19: Can Your Agent Explore in Parallel Without Creating Chaos? Use Speculative Execution and Early Cancellation # Book: Advanced Agents From First Principles # Chapter URL: https://programmer.ie/books/advanced-agents-from-first-principles/19-chapter/ # Prose fingerprint (sha256, normalized): 872cb7bc51923fb83e6ce9d9363cd97102f975b0cb46804276c609fbcef10a43 # 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. Advanced Agents From First Principles 19: Can Your Agent Explore in Parallel Without Creating Chaos? Use Speculative Execution and Early Cancellation A production agent often has more than one useful thing it could do next. It could: inspect repository state, run a targeted test, retrieve documentation, ask a second model to critique a candidate, generate an alternative implementation, probe an API, inspect a deployment, or verify an invariant. If those actions are independent, executing them one by one can be needlessly slow. The obvious response is: Run them in parallel. That sounds simple. It is not. Parallel agent work introduces a new set of failure modes: duplicated work, conflicting side effects, stale observations, branches consuming budget after they are already dominated, cancellation that arrives too late, hidden tail latency, resource contention, verifier starvation, and race conditions between speculative branches and the real environment. The central rule for this post is: Parallelism is useful only when speculative work is isolated, independently valuable, and cheap enough to cancel when new evidence makes it unnecessary. This is not merely a performance optimization. It changes how an advanced agent allocates evidence gathering and search. The sequential baseline Suppose an agent is debugging a failing service. A sequential runtime might do this: inspect logs ↓ inspect recent commit ↓ run failing test ↓ search documentation ↓ generate fix candidate ↓ verify candidate If each step takes several seconds, the whole critical path can become long even when the steps are mostly independent. But some of them may not depend on one another. For example: inspect logs / | \ / | \ inspect commit run test search docs \ | / \ | / evidence ↓ decision Now the decision may be reached as soon as enough evidence arrives. That is the first important distinction: Parallel systems optimize critical-path latency, not necessarily total work. Running three probes concurrently can consume more total compute than running one probe first. But if the probes are cheap and often decisive, latency may fall substantially. The architecture must therefore track both: wall-clock latency and: total resource consumption Do not confuse them. 1. Speculation should happen before commitment Parallelism is safest when branches are still informational or reversible. Good speculative actions include: read-only repository inspection, test execution in isolated environments, log retrieval, documentation lookup, candidate generation, scoring, static analysis, sandboxed execution, dry runs, simulations, read-only API queries. Dangerous speculative actions include: sending customer messages, deleting files, merging pull requests, changing production configuration, executing payments, restarting infrastructure, mutating shared databases, writing to shared external systems. The architecture should therefore separate: speculative phase ↓ selection ↓ commit phase For example: candidate A ── sandbox test ─┐ candidate B ── sandbox test ─┼─ select winner ── apply once ── verify candidate C ── sandbox test ─┘ Not: candidate A ── deploy candidate B ── deploy candidate C ── deploy That second architecture is not exploration. It is a race condition with production consequences. 2. Parallelism does not mean launch everything Once concurrency exists, there is a temptation to fan out aggressively. Suppose the agent can launch 20 observations at once. It should not automatically do so. Every branch has a cost: model calls + tool calls + CPU/GPU time + external API usage + memory + context processing + verification work The Step 16 budget scheduler still applies. The Step 18 value-of-information policy still applies. Parallelism changes only the scheduling geometry. Instead of asking: Which action should I run next? The scheduler now asks: Which small set of independent high-value actions should I run now? That means ranking candidate actions by something like: expected decision impact ------------------------ expected resource cost and then selecting a bounded concurrent frontier. 3. Build a speculative frontier A useful abstraction is a speculative frontier. Each frontier item represents work that may be worth doing concurrently. from dataclasses import dataclass, field from typing import Callable, Any @dataclass class SpeculativeTask: id: str kind: str expected_value: float expected_cost: float cancellable: bool side_effect_class: str dependencies: set[str] = field(default_factory=set) run: Callable[[], Any] | None = None The scheduler can then select tasks satisfying constraints such as: no unmet dependencies no unsafe speculative side effects within concurrency budget within resource budget sufficient expected value For example: def eligible(task, completed): return ( task.dependencies <= completed and task.side_effect_class in {"read_only", "sandboxed", "reversible"} ) This makes concurrency explicit rather than burying it inside asyncio.gather(). 4. Concurrency needs a budget of its own A system can satisfy the total token budget and still overload itself through parallelism. You therefore need both: total resource budget and: concurrency budget For example: @dataclass class Budget: max_model_calls: int max_tool_calls: int max_parallel_tasks: int max_cost: float max_wall_seconds: float Why separate concurrency? Because ten cheap tasks launched simultaneously can still: saturate rate limits, exhaust connection pools, overwhelm a local model server, spike memory, create queue contention, increase tail latency. The scheduler must understand that: cheap individually ≠ cheap concurrently 5. Measure the critical path Suppose three probes take: A = 2 seconds B = 8 seconds C = 5 seconds Sequential time: 2 + 8 + 5 = 15 seconds Parallel time if all are required: max(2, 8, 5) = 8 seconds But an advanced agent often does not need all results. Suppose A and C together are enough to make the decision after 5 seconds. Then B should be cancelled. The critical path becomes: 5 seconds while total work depends on how much of B was already consumed. Useful metrics include: critical_path_latency aggregate_compute_time cancelled_compute_time useful_result_latency wasted_speculation_cost These are not interchangeable. 6. Early cancellation is the real optimization Parallel fan-out without cancellation can easily become wasteful. The important capability is not merely: start many things It is: stop irrelevant things quickly Imagine three candidate fixes: A → tests fail immediately B → tests still running C → tests pass targeted checks Once A fails, cancel its downstream analysis. If C passes a strong acceptance cascade and dominates B under the selection policy, B may also be cancelled. This suggests a branch lifecycle: QUEUED ↓ RUNNING ↓ PARTIAL_RESULT ├─ CONTINUE ├─ CANCEL_DOMINATED ├─ CANCEL_BUDGET ├─ CANCEL_STALE └─ COMPLETE Cancellation should have explicit reasons. That matters for observability and later policy learning. 7. Dominance is stronger than “looks worse” Do not cancel branches merely because one internal score is slightly lower. A branch is safely dominated only when the evidence is strong enough. For example: candidate A verified targeted tests = PASS cost remaining = low candidate B same targeted tests = FAIL B is dominated. But: candidate A score = 0.73 candidate B score = 0.69 is weak evidence for cancellation unless the scorer is well calibrated. A practical dominance rule may combine: verified evidence + score margin + remaining expected value + remaining cost + uncertainty The scheduler should be conservative when cancellation could remove the only eventual successful branch. This is the parallel equivalent of pruning regret. Call it: cancellation regret 8. Cancellation regret Suppose branch B is cancelled early. Offline replay later shows B would have produced a verified success while the surviving branch failed. That is a scheduler failure. Define: cancellation regret = value of best cancelled branch - value of selected branch At the simplest level: cancelled eventual PASS + selected FAIL is a high-regret event. Track: cancelled branches reason for cancellation state when cancellation occurred available evidence counterfactual replay result Without this lineage, early cancellation policies cannot improve safely. 9. Stale observations become more dangerous in parallel systems Concurrency increases the chance that one observation is outdated by the time another branch uses it. Example: T0: branch A reads deployment state T1: branch B performs isolated simulation T2: external deployment changes T3: branch C reads new deployment state Now A and C disagree because they observed different states. Do not treat this as a reasoning disagreement. It may be a freshness problem. Every observation should therefore carry: state_id observed_at source scope freshness For mutable environments, results may need a validity condition: @dataclass class Observation: value: object state_id: str observed_at: float valid_until: float | None source: str The runtime must detect when speculative evidence belongs to different environment versions. 10. State snapshots reduce races When possible, run speculative work against an immutable snapshot. Coding-agent examples: git commit/tree hash worktree snapshot container image locked dependency graph Data-agent examples: dataset version snapshot timestamp transaction snapshot schema version Browser-agent examples: DOM snapshot page version request/response capture Research-agent examples: source IDs retrieval timestamp corpus version The invariant is: Branches being compared should ideally reason about the same underlying state. Otherwise branch comparison becomes contaminated by environmental drift. 11. Isolation is more important than concurrency primitives Using asyncio, threads, processes, or distributed workers is an implementation detail. The architectural question is isolation. Ask: Can branch A affect branch B? If yes, speculation is not clean. Isolation can come from: separate worktrees, containers, transactions, temporary databases, copy-on-write filesystems, dry-run APIs, read-only credentials, namespaces, mocked external systems. The more consequential the side effect, the stronger the isolation requirement. 12. Coding agent example: parallel repair branches Suppose tests fail after a refactor. The agent identifies three plausible strategies: A: fix import boundary B: revert interface change C: update dependent call sites A parallel architecture can create isolated worktrees: base commit ├── worktree A → patch → targeted tests ├── worktree B → patch → targeted tests └── worktree C → patch → targeted tests Then rank branches by external evidence: targeted test result full relevant test result lint/type checks patch size regressions Only the selected branch is applied to the real working tree. This is powerful because the speculative mutations are isolated. Do not run all three patches directly against the same checkout. 13. Research agent example: parallel evidence probes Suppose a claim depends on three possible evidence paths: official source primary paper independent dataset These can often run concurrently. claim ├── official source retrieval ├── primary paper retrieval └── dataset inspection As soon as one authoritative source resolves the claim, weaker redundant retrieval may be cancelled. But if the claim is contested, multiple independent sources may still have high value. Parallelism therefore depends on the decision requirement. The EVI policy from Step 18 remains the gating mechanism. 14. Browser agent example: parallel read-only discovery A browser agent may need to discover: shipping cost, return policy, availability, compatibility. Those are often independent read-only queries. Parallel retrieval can reduce latency. But the final checkout action must remain serial and verified. parallel discovery ↓ compare options ↓ select ↓ confirm exact item / price / address ↓ commit action ↓ verify outcome Speculation should not submit multiple purchases and cancel two afterwards. 15. Data-agent example: parallel validation probes Before applying a transformation, a data agent can concurrently inspect: schema compatibility null-rate changes distribution drift referential integrity sample output If referential integrity fails immediately, more expensive downstream simulation may be cancelled. This is a good example of early decisive evidence. The branch scheduler should favor checks with high failure-detection value and low cost. 16. DevOps example: parallel diagnosis, serial remediation During an incident, an agent might simultaneously inspect: service logs metrics recent deploys dependency health queue depth That is useful speculative observation. But remediation should generally remain gated: parallel diagnosis ↓ state synthesis ↓ proposed action ↓ precondition verification ↓ execute one remediation ↓ postcondition verification Do not let five diagnostic branches independently restart five services. 17. Multi-agent systems need shared cancellation Mixture-of-agents systems make this problem more visible. Suppose a router launches: code expert research expert critic frontier model If the code expert produces a verified deterministic solution in 800 ms, the other branches may no longer be worth their cost. That requires a shared cancellation controller. router ↓ launch specialists ↓ shared evidence bus ↓ selection / dominance policy ↓ cancel losers Without shared state, every specialist runs to completion and the architecture becomes an expensive fan-out machine. 18. Partial results can be useful A branch does not always need to finish before it becomes informative. Examples: test suite finds first deterministic failure search branch discovers authoritative source static analyzer reports invariant violation critic identifies a verified regression The runtime should surface partial results as events. @dataclass class BranchEvent: branch_id: str kind: str evidence: dict terminal: bool The scheduler can react before branch completion. That is what makes early cancellation effective. 19. Stragglers and tail latency Parallel systems are often dominated by the slowest branch. Suppose nine branches finish in 2 seconds and one takes 40 seconds. If the system waits for all ten, parallelism did not solve the latency problem. You need explicit policies for stragglers. Possible rules: cancel after decision becomes stable cancel if expected remaining value falls below threshold cancel if branch exceeds latency budget cancel if equivalent evidence already arrived elsewhere Do not use arbitrary timeouts as the only rule. A slow branch may still be the only branch capable of resolving verification uncertainty. Again, value matters more than elapsed time alone. 20. Hedged requests A related technique is the hedged request. If a tool or model endpoint occasionally has high tail latency, the runtime can start a second equivalent request after a delay. request A starts ↓ latency threshold crossed ↓ request B starts ↓ first valid result wins ↓ cancel loser This can reduce tail latency. But it doubles work in some cases. Use it only when: the action is read-only or idempotent, tail latency is materially expensive, duplicate execution is safe, cancellation works reliably. Never hedge non-idempotent side effects casually. 21. Async execution skeleton A minimal Python sketch: import asyncio from dataclasses import dataclass @dataclass class Result: branch_id: str score: float verified: bool payload: object async def run_branch(branch): return await branch() async def speculative_execute(branches, accept): tasks = { asyncio.create_task(run_branch(branch)): i for i, branch in enumerate(branches) } results = [] try: for future in asyncio.as_completed(tasks): result = await future results.append(result) if accept(result, results): for task in tasks: if not task.done(): task.cancel() return result, results return None, results finally: await asyncio.gather(*tasks, return_exceptions=True) This is deliberately simple. Production systems still need: resource accounting, cancellation reasons, branch lineage, state binding, side-effect classification, structured verification, timeout policy, retry policy, partial-result handling, cleanup. The point is the control loop, not asyncio itself. 22. Cancellation is cooperative unless enforced Calling task.cancel() does not magically stop every underlying operation. A model API request may already be executing remotely. A database query may continue. A subprocess may survive. A tool may ignore cancellation. Therefore distinguish: logical cancellation physical cancellation Logical cancellation means: do not use this result anymore Physical cancellation means: stop consuming underlying resources Track both. Useful fields: cancel_requested_at cancel_acknowledged_at work_stopped_at post_cancel_cost This reveals whether the cancellation mechanism actually saves resources. 23. Cleanup is part of correctness Speculative branches create temporary resources. Examples: worktrees containers temporary files database transactions browser tabs subprocesses GPU jobs network sessions A cancelled branch must clean them up. This suggests another invariant: Cancellation is incomplete until speculative resources are either released or explicitly transferred to the selected branch. Otherwise long-running agents accumulate invisible state. 24. Do not starve verification Parallel search can consume a budget extremely quickly. Suppose the system has 20 model-call credits. Launching 20 speculative candidates immediately leaves nothing for verification. The protected verification reserve from Step 16 becomes even more important under concurrency. For example: total budget = 20 verification reserve = 4 max speculative allocation = 16 But even that may be too permissive. The scheduler should also limit concurrent speculative consumption. max in-flight model calls = 4 This preserves both budget and responsiveness. 25. Parallel search vs beam search Parallel execution is not the same as beam search. Beam search defines a selection strategy over branches. Parallel execution defines an execution strategy. You can have: beam search executed sequentially or: beam search with parallel branch expansion Similarly, MCTS can parallelize rollouts. The distinction matters because benchmarking should isolate: search policy gain from: concurrency gain Do not claim a search algorithm is better when the real improvement came from parallel wall-clock execution. 26. Benchmark concurrency fairly Compare at least: A. sequential single best action B. sequential EVI-ranked actions C. fixed-width parallel speculation D. adaptive parallel speculation + cancellation Keep the same maximum resource envelope. Measure: verified success p50 latency p95 latency aggregate cost cost per verified success cancelled cost post-cancel cost cancellation regret verification starvation If parallelism reduces latency but doubles cost, that tradeoff should be visible. If adaptive cancellation recovers most of the latency gain while avoiding most of the extra work, that is evidence the policy is useful. 27. Measure useful concurrency A useful metric is: useful_concurrency_ratio = parallel tasks that contributed unique decision-relevant evidence / parallel tasks launched If the runtime launches eight branches and seven produce redundant evidence, concurrency is mostly waste. Also track: redundant_result_rate cancel_before_use_rate cancel_after_use_rate straggler_rate resource_contention_rate These metrics expose fan-out architectures that look sophisticated but contribute little. 28. Concurrency can reduce reliability Parallelism does not automatically preserve correctness. Potential failure modes include: comparing branches built from different state snapshots, selecting the first result rather than the best result, race-dependent output, non-deterministic shared memory updates, duplicate writes, cancellation before sufficient verification, stale partial evidence, conflicting cleanup. A useful test is: same task same state snapshot same policy vary scheduling order If outcomes change materially because completion order changed, the runtime has schedule sensitivity. Track it. 29. Schedule sensitivity Define: schedule sensitivity = probability that different valid completion orders produce different selected outcomes This is especially important when branch selection uses “first acceptable result wins”. A first-result policy may introduce hidden quality regressions. Compare: first acceptable best after short grace period best after evidence threshold best under fixed wall-clock budget The fastest policy may not be the best reliability-cost tradeoff. 30. Grace periods can improve selection Suppose candidate A passes after 900 ms. Candidate B is likely to finish within another 100 ms. Immediately cancelling B may save almost nothing while risking cancellation regret. A small grace period can sometimes improve selection: first strong result ↓ short grace window ↓ collect near-complete competitors ↓ select ↓ cancel rest Do not hard-code this universally. Learn or calibrate it from trajectory evidence. 31. Parallel verification can also help Verification itself may contain independent checks. A coding change might need: unit tests static analysis type checking security scan integration test Some checks can run concurrently. But verification policy still matters. For example: cheap deterministic checks first ↓ if they pass ↓ parallel expensive checks This can dominate launching every expensive verifier immediately. Again, concurrency should follow value and dependency structure. 32. Progressive fan-out One useful strategy is progressive fan-out. Start with a small number of high-value branches. Expand only when uncertainty remains high. launch 2 branches ↓ enough evidence? yes → decide no → launch 2 more ↓ reassess This avoids paying full fan-out cost on easy tasks. It also integrates naturally with Step 16’s scheduler and Step 17’s uncertainty decomposition. 33. Adaptive concurrency Instead of a fixed parallel width, choose width from the task state. Possible signals: candidate uncertainty route uncertainty expected branch diversity cost budget latency requirement model/tool capacity historical rescue rate Easy task: parallel width = 1 Ambiguous but cheap task: parallel width = 3 High-cost or high-risk task: parallel width = 1 or 2 verification reserve = high The scheduler should not confuse “hard” with “launch everything”. 34. Backpressure matters An agent can produce speculative work faster than workers can consume it. Without backpressure: planner generates branches ↓↓↓↓↓↓↓↓↓↓↓↓↓ queue grows indefinitely ↓ memory / cost / latency explode A production runtime needs queue limits. max queued branches max in-flight branches max unresolved partial results When the queue is full, the planner must stop generating more work. This is systems engineering, not prompt engineering. 35. Parallelism should preserve provenance Every result should retain: branch_id parent_state_id snapshot_id policy_version started_at completed_at cancelled_at resource_cost result_hash verification_status Otherwise the system cannot reconstruct which evidence came from which speculative state. This matters for: debugging, replay, attribution, policy learning, cancellation regret, safety audits. 36. A branch result is not automatically current truth A speculative branch may discover something valid about its snapshot. That does not mean the observation remains valid when the real environment changes. Before acting on speculative results, revalidate critical assumptions against current state. For example: candidate patch passed on snapshot X ↓ current main still equals X? ↓ if yes → apply if no → rebase / rerun / invalidate This preserves the source-of-truth discipline from earlier posts. 37. Speculation should reduce uncertainty, not create it A bad parallel architecture produces many conflicting outputs and leaves the runtime more confused. A useful speculative branch should do at least one of these: reduce uncertainty eliminate an option verify an invariant produce a candidate strengthen evidence expose a failure If branches consistently produce unstructured opinions that cannot be compared, parallelism only multiplies ambiguity. That is not useful exploration. 38. Failure taxonomy Useful failure labels include: SPECULATION_SIDE_EFFECT_LEAK SNAPSHOT_MISMATCH CANCELLATION_REGRET CANCELLATION_NOT_ENFORCED STRAGGLER_WAIT VERIFY_STARVED REDUNDANT_FANOUT RESOURCE_CONTENTION SCHEDULE_SENSITIVE_SELECTION STALE_PARTIAL_RESULT CLEANUP_FAILURE BACKPRESSURE_FAILURE These labels make concurrency failures measurable rather than anecdotal. 39. Failure injection Test the runtime deliberately. Inject cases where: one branch hangs, one branch returns a fast wrong answer, one branch ignores cancellation, two branches produce duplicate evidence, environment state changes mid-run, a cancelled branch leaks a temporary resource, a verifier becomes slow, one branch exceeds budget, a rate limit reduces effective concurrency. Then verify that the scheduler: cancels correctly preserves verification reserve avoids stale evidence cleans resources stays within budget returns UNKNOWN when necessary Do not wait for production to discover these races. 40. Parallelism changes observability Step 13’s trajectory observability now needs concurrency-aware events. For example: BRANCH_LAUNCHED BRANCH_PARTIAL_RESULT BRANCH_DOMINATED CANCEL_REQUESTED CANCEL_ACKNOWLEDGED BRANCH_COMPLETED BRANCH_DISCARDED_STALE RESOURCE_RELEASED And traces should be represented as a DAG, not a flat sequence. That allows you to reconstruct the true critical path. 41. Learn cancellation policy from verified trajectories Once these events are recorded, Step 14’s trajectory-learning loop can improve cancellation. Useful questions include: Which evidence patterns safely predicted domination? Which cancellations caused regret? Which branches rarely contributed unique evidence? Which tools ignored cancellation? Where did concurrency reduce p95 latency? Where did it only increase cost? Then candidate policies can be evaluated offline before promotion. Do not let the runtime self-modify cancellation thresholds directly from the latest run. Use the same held-out, shadow, canary, rollback discipline as other control policies. 42. The simplest parallelism often wins You do not need a distributed agent framework to benefit from this idea. A small runtime may only need: 2 concurrent diagnostic tool calls 1 isolated candidate branch 1 protected verifier That may deliver most of the latency benefit without introducing a large orchestration layer. Remember the series rule: Complexity is not a capability ladder. Parallelism must earn its operational cost. 43. Decision guide Use parallel speculation when: multiple actions are independent and results are comparable and side effects are isolated and latency matters and cancellation can save meaningful work Prefer sequential execution when: steps have strong dependencies or state mutates between actions or work is already cheap or side effects are difficult to isolate or rate limits make concurrency ineffective Use progressive fan-out when: task difficulty varies significantly Use hedging when: a read-only/idempotent dependency has expensive tail latency 44. The complete advanced control loop The architecture developed across the recent posts now looks like this: request ↓ observe exact state ↓ decompose uncertainty ↓ identify pending decision ↓ rank information/actions by expected value ↓ allocate shared compute budget ↓ launch bounded speculative frontier ↓ collect partial evidence ↓ cancel dominated / stale / low-value work ↓ select branch ↓ revalidate current state ↓ commit controlled side effect ↓ external verification ↓ PASS / FAIL / UNKNOWN ↓ trajectory log ↓ offline policy learning That is a very different architecture from: prompt → LLM → tool → repeat The intelligence is increasingly in the runtime control structure. 45. Final principle The temptation with parallel agents is to equate concurrency with capability. Do not. The useful question is: Which independent work can I safely speculate on now, and how quickly can I stop paying for branches once evidence says they no longer matter? That gives us the governing rule: Parallelize independent, low-risk, high-value work; isolate speculative state; cancel aggressively when evidence is strong; and commit side effects only after selection and revalidation. The next stage is a natural consequence of this architecture: distributed coordination and leases — what happens when speculative branches and agent workers no longer live inside one process and cancellation, ownership, retries, idempotency, and exactly-once illusions become distributed-systems problems.