The 3 Questions Every AI Agent Architect Must Answer About Execution Patterns
A zero-LLM heuristic classifier that picks an execution pattern in under 5ms, why asyncio.gather beats sub-agents until it does not, and when to expose pattern control to users.

On this page
First written in February 2026, when Savra ran a multi-agent architecture. We have since moved to a single agent with dynamically activated skills, and the reasons are in Don't Go Multi-Agent. The tier-one advice below is the part that held: reach for concurrent tools long before you reach for concurrent agents.
Your agent works. Then users start asking for comparisons, multi-source research and parallel lookups, and you have to decide how the system responds to that. Let the agent work it out? Add an orchestrator to classify queries? Spin up sub-agents?
The answer is all three, in a priority order most teams get backwards. Three questions decide it.
Question 1: who decides the pattern?
Three options, and the cheapest one is also the most reliable.
1Agent self-decides2 Query → agent reasons about the pattern → picks → executes3 Costs nothing extra, and varies run to run45Orchestrator classifies6 Query → LLM classifier → pattern type → executes with it7 Reliable, and pays a model call on every request89Heuristics10 Query → regex and keyword check → matched pattern11 → no match, default sequential12 Reliable, and effectively free
Approach | Latency added | Token cost | Reliability |
|---|---|---|---|
Agent self-decides | 0ms | 0 | Inconsistent |
Orchestrator LLM | +300-500ms | +500 tokens | Reliable |
Heuristics | <5ms | 0 | Reliable |
The reason heuristics win is that most queries announce themselves. Words like compare, all, each and difference between are strong signals, and they do not need a language model to be recognised. Save the model call for reasoning, which is the thing only it can do.
1import re2from enum import Enum34class ExecutionPattern(Enum):5 SEQUENTIAL = "sequential" # 80% of queries6 PARALLEL_TOOLS = "parallel_tools" # 15% of queries7 MULTI_AGENT = "multi_agent" # 5% of queries89def classify_pattern(user_message: str) -> ExecutionPattern:10 """11 Zero-LLM-call pattern classifier. Runs in under 5ms.12 """13 message_lower = user_message.lower()1415 # Comparison queries → parallel tools16 comparison_patterns = [17 r"compare\s+.+\s+(?:and|vs|versus|with)\s+",18 r"difference(?:s)?\s+between",19 r"(?:which|what)\s+is\s+better",20 ]21 if any(re.search(p, message_lower) for p in comparison_patterns):22 return ExecutionPattern.PARALLEL_TOOLS2324 # Multi-item queries → parallel tools25 multi_item_patterns = [26 r"(?:all|each|every)\s+(?:of\s+)?(?:the|my|our)\s+\w+",27 r"(\d+)\s+(?:different|separate|distinct)",28 r"list\s+(?:all|every)",29 ]30 if any(re.search(p, message_lower) for p in multi_item_patterns):31 return ExecutionPattern.PARALLEL_TOOLS3233 # Heavy research → multi-agent34 heavy_research_patterns = [35 r"(?:deep|comprehensive|thorough)\s+(?:research|analysis|audit)",36 r"audit\s+\d+\s+(?:competitors|companies|websites)",37 r"research\s+(?:multiple|several|all)\s+",38 ]39 if any(re.search(p, message_lower) for p in heavy_research_patterns):40 return ExecutionPattern.MULTI_AGENT4142 # Default43 return ExecutionPattern.SEQUENTIAL444546assert classify_pattern("What is our pricing?") == ExecutionPattern.SEQUENTIAL47assert classify_pattern("Compare product A and B") == ExecutionPattern.PARALLEL_TOOLS48assert classify_pattern("Audit 5 competitors") == ExecutionPattern.MULTI_AGENT
Being wrong here is cheap, which is the other reason not to spend a model call on it. A query misclassified as sequential still gets answered; it is just answered a little slower.
Question 2: parallel tools or parallel agents?
This is where the over-engineering happens. There are two tiers, and they are not close in cost.
1Tier 1: parallel tools Tier 2: parallel agents2(reach for this first) (reach for this rarely)34 Single agent Orchestrator5 │ ├──► Sub-agent 1 ─┐6 asyncio.gather() ├──► Sub-agent 2 ─┼──► Synthesis7 ├──► Tool 1 ─┐ └──► Sub-agent 3 ─┘ agent8 ├──► Tool 2 ─┼──► Combine9 └──► Tool 3 ─┘
Metric | Tier 1: parallel tools | Tier 2: parallel agents |
|---|---|---|
LLM calls | 1 | 4+ (orchestrator, agents, synthesis) |
Token cost | ~1.2x a single query | ~5x a single query |
Latency | The slowest tool | The sum of the LLM calls |
Complexity | Low | High |
Use for | 2-5 parallel lookups | 5+ complex independent tasks |
A factor of four in cost and a large step up in complexity, for a capability that only matters when the parallel work involves independent reasoning rather than independent fetching.
Tier one is a semaphore and a gather:
1import asyncio2from typing import Any34async def parallel_search(5 queries: list[str],6 search_fn: callable,7 max_concurrent: int = 58) -> list[Any]:9 """10 Run multiple searches in parallel with rate limiting.11 Three KB searches that took 3s sequentially finish in about 1s.12 """13 semaphore = asyncio.Semaphore(max_concurrent)1415 async def limited_search(query: str) -> Any:16 async with semaphore:17 return await search_fn(query)1819 results = await asyncio.gather(*[20 limited_search(q) for q in queries21 ])22 return results232425async def compare_products(26 ctx: RunContext,27 product_names: list[str]28) -> str:29 """Compare multiple products side by side."""3031 results = await parallel_search(32 queries=[f"product info: {name}" for name in product_names],33 search_fn=ctx.deps.search_kb,34 max_concurrent=335 )3637 comparison_table = format_comparison(results)38 return comparison_table
Tier two exists for cases where each branch has to think, not just look something up:
1from langgraph.prebuilt import create_react_agent23async def parallel_competitor_audit(4 competitors: list[str]5) -> str:6 """7 Heavy research requiring parallel LLM reasoning.8 Only for 5+ complex independent tasks.9 """1011 async def audit_one(competitor: str) -> dict:12 agent = create_react_agent(13 model=get_model(),14 tools=[web_search, crawl_url, analyze_page],15 prompt=f"Audit {competitor}'s pricing, features, and positioning."16 )17 result = await agent.invoke({"messages": [...]})18 return {"competitor": competitor, "audit": result}1920 audits = await asyncio.gather(*[21 audit_one(c) for c in competitors22 ])2324 synthesis = await synthesize_audits(audits)25 return synthesis
The test in one line:
1How many parallel operations?2 1 → sequential, no parallelism needed3 2-5 → simple lookups? yes → Tier 1 (asyncio.gather)4 no → ask the next question5 5+ → separate reasoning? yes → Tier 2 (parallel agents)6 no → Tier 1
Question 3: should users control the pattern?
Not at first.
Users care about answers, not about execution strategy, and every explicit control you add costs cognitive load for the large majority who will never touch it. You also do not yet know which patterns matter, because you have no production data on which queries people actually send.
The evolution that works:
1Phase 1 (MVP) Fully automatic. The system detects, users neither know nor care.2Phase 2 Natural-language hints. "do a thorough comparison", "quick summary".3Phase 3 Explicit controls. Mode selection, API parameters. If ever.
Phase two is cheap once you have the data, because the hints are just more keywords:
1SPEED_HINTS = {2 "quick": ExecutionPattern.SEQUENTIAL,3 "fast": ExecutionPattern.SEQUENTIAL,4 "brief": ExecutionPattern.SEQUENTIAL,5}67QUALITY_HINTS = {8 "thorough": ExecutionPattern.PARALLEL_TOOLS,9 "comprehensive": ExecutionPattern.PARALLEL_TOOLS,10 "detailed": ExecutionPattern.PARALLEL_TOOLS,11 "deep": ExecutionPattern.MULTI_AGENT,12}1314def detect_user_hint(message: str) -> ExecutionPattern | None:15 """Detect explicit user preferences from natural language."""16 message_lower = message.lower()1718 for hint, pattern in {**SPEED_HINTS, **QUALITY_HINTS}.items():19 if hint in message_lower:20 return pattern2122 return None # no hint, fall through to auto-classification
What that buys is narrow but real. "Do a thorough market analysis" contains no comparison keywords, so automatic classification sends it sequential; the hint catches it.
The order to build in
Stage | Deliverable | What it gets you |
|---|---|---|
1 | One agent, skills, sequential | Covers ~80% of traffic |
2 | Parallel tools via | Large speedup on ~15% |
3 | Heuristic pattern classifier | The choice happens by itself |
4 | Parallel agents, if needed | Heavy research |
That order is the same one the decision matrix for multi-agent versus single agent arrives at from the other direction, and stage two is parallel tool execution in about twenty lines.
Notice that stage four is conditional and everything before it is not. Most products never need it, and the ones that do usually discover it from a specific workload rather than from an architecture diagram.
The through-line across all three questions: do not spend a model call on a decision that keyword matching makes correctly, do not spend an agent on work a concurrent tool call does, and do not spend a user's attention on a control they did not ask for.
FAQ
Should an LLM decide which execution pattern to use?
Almost never. A classifier call costs 300 to 500ms and several hundred tokens to answer what regex answers in under 5ms.
What is the difference between parallel tools and parallel agents?
Parallel tools run lookups concurrently in one model call, about 1.2x cost. Parallel agents run independent reasoning chains plus orchestration and synthesis, about 5x.
When should users be able to choose the pattern?
Later than you think. Automatic first, natural-language hints second, explicit controls rarely.
What order should you build these in?
Sequential single agent, then parallel tools, then the heuristic classifier, then parallel agents only if a real workload demands them.
Keep reading

Don't Go Multi-Agent: What 2026 Actually Recommends
Multi-agent burns about 15x the tokens by design and carries 14 catalogued failure modes. Why one agent with dynamic skills, curated context and tiered memory is the 2026 default, and the read-write test for the exception.

Stop Debating Multi-Agent vs Single Agent. Here's the Actual Decision Matrix.
Three execution patterns, chosen per query rather than per system: unified sequential for 80%, unified with parallel tools for 15%, multi-agent parallel for 5%. Plus why skills are orthogonal to all three.

3x Faster AI Responses with Parallel Tool Execution
Sequential tool calls make an agent wait on I/O it could overlap. How asyncio.gather cuts a three-lookup response from 1.5s to 0.5s, with rate limiting that keeps it safe.
See if your brand sounds like itself.
Run the free 90-second Brand Genome audit. No card, just your score.