Advanced Agents From First Principles 05: Is One Model Doing Everything? Build a Mixture of Experts at the Agent Level
A common agent architecture starts simply:
request
↓
model
↓
action
That simplicity is valuable.
It should be your default.
But eventually you may notice something strange.
The same model is being asked to do everything:
- classify the task,
- search documentation,
- reason about code,
- write SQL,
- review a patch,
- summarize logs,
- judge another model,
- decide whether a deployment is safe,
- and answer simple questions that did not require an expensive model in the first place.
At that point the problem may no longer be:
How do I make the model smarter?
It may be:
Why am I sending every problem to the same expert?
That is where an agent-level Mixture of Experts becomes useful.
But the phrase is easy to misunderstand.
This post is not primarily about the Mixture-of-Experts layers used inside large neural networks.
It is about something much simpler and much more directly useful when building agent systems:
task
↓
router
├──→ local model
├──→ frontier model
├──→ code specialist
├──→ retrieval specialist
├──→ deterministic tool
├──→ critic
└──→ verifier
The important idea is not that more experts are automatically better.
The important idea is that different work should be routed to the mechanism most suited to doing it.
And, as with every technique in this series, that additional complexity has to earn its place.
The Search Problem This Post Solves
People building production agents eventually search for questions like:
- How do I route tasks between multiple LLMs?
- How do I use a local model and GPT/Claude together?
- How do I choose the best model for each prompt?
- Why is my agent sending easy tasks to an expensive model?
- Why does my multi-agent router choose the wrong specialist?
- How do I build a mixture of agents?
- How do I route between tools and models?
- How do I use specialist AI agents?
- Should I use multiple AI models in one application?
- How do I reduce LLM costs without reducing quality?
- Why does my expert router collapse onto one model?
- How do I know whether agent specialization actually helps?
Those look like different questions.
Architecturally, they are usually the same problem:
many possible capabilities
↓
one incoming task
↓
which capability should receive it?
That is a routing problem.
Neural MoE and Agent MoE Are Different Things
Before building anything, we need to separate two ideas that share a name.
Neural Mixture of Experts
Inside a neural network, a router may decide which learned expert layers process a token or representation:
input representation
↓
router
/ | \
↓ ↓ ↓
E1 E2 E3
\ | /
↓
network output
The experts are parts of one learned model.
The routing happens inside the neural architecture.
Agent-Level Mixture of Experts
At the software-system level, our experts can be entirely different components:
user task
↓
router
├── local LLM
├── frontier LLM
├── code model
├── retrieval system
├── SQL specialist
├── deterministic calculator
├── browser agent
└── verifier
These experts may differ in:
- model family,
- model size,
- prompt,
- tools,
- memory,
- cost,
- latency,
- context window,
- domain specialization,
- or even whether they are an LLM at all.
This post is about the second architecture.
The Simplest Possible Router
We do not need machine learning to begin.
Suppose our application receives four kinds of work:
calculation
coding
research
ordinary conversation
The first router could literally be ordinary Python:
from dataclasses import dataclass
from typing import Literal
ExpertName = Literal[
"calculator",
"code_model",
"research_model",
"general_model",
]
@dataclass
class RouteDecision:
expert: ExpertName
reason: str
def route(task: str) -> RouteDecision:
lower = task.lower()
if "calculate" in lower or "sum" in lower:
return RouteDecision(
expert="calculator",
reason="Task appears deterministic and arithmetic.",
)
if "python" in lower or "bug" in lower or "code" in lower:
return RouteDecision(
expert="code_model",
reason="Task appears to require code reasoning.",
)
if "research" in lower or "sources" in lower:
return RouteDecision(
expert="research_model",
reason="Task appears to require evidence gathering.",
)
return RouteDecision(
expert="general_model",
reason="No specialist route matched.",
)
That is already a mixture-of-experts system.
Nothing about MoE requires a neural router.
This matters because the cheapest router that works is usually the best starting point.
Why Routing Can Beat One Giant Generalist
Imagine every task is currently sent to your strongest model.
The architecture is:
all tasks
↓
expensive frontier model
That can be perfectly reasonable.
But suppose your workload looks like this:
55% straightforward classification
20% retrieval
15% code reasoning
8% difficult reasoning
2% genuinely hard edge cases
Using the most expensive model for 100% of requests may be wasteful.
A router could instead create:
request
↓
router
├─ 55% → cheap classifier/local model
├─ 20% → retrieval path
├─ 15% → code specialist
├─ 8% → medium model
└─ 2% → frontier model
The potential gain is not only financial.
Specialists can also have:
- narrower prompts,
- fewer irrelevant tools,
- tighter schemas,
- smaller contexts,
- stronger deterministic checks,
- and more appropriate verification.
So routing may improve both efficiency and reliability.
But only if the router is good enough.
The Router Is Now Part of Your Failure Surface
Once we add routing, we have created a new subsystem that can fail.
Before routing:
model failure
After routing:
routing failure
or
expert failure
or
verification failure
This distinction is critical.
Suppose a coding task gets sent to the research specialist and fails.
The code expert may be excellent.
The system still fails because the router never reached it.
So we need to measure:
routing quality
separately from
expert quality
Define Experts by Capability, Not Personality
One common mistake in multi-agent systems is defining roles like:
The Architect
The Skeptic
The Genius
The Researcher
The Visionary
Those names sound interesting.
They tell the runtime almost nothing useful.
Prefer capability contracts.
For example:
from dataclasses import dataclass, field
@dataclass
class ExpertSpec:
name: str
capabilities: set[str]
cost_tier: int
latency_tier: int
tools: set[str] = field(default_factory=set)
notes: str = ""
Then:
experts = [
ExpertSpec(
name="local_classifier",
capabilities={"classify", "route", "extract"},
cost_tier=1,
latency_tier=1,
),
ExpertSpec(
name="code_specialist",
capabilities={"code", "debug", "review"},
cost_tier=2,
latency_tier=2,
tools={"read_file", "search_code", "run_tests"},
),
ExpertSpec(
name="research_specialist",
capabilities={"research", "retrieve", "synthesize"},
cost_tier=2,
latency_tier=2,
tools={"search", "open_source", "extract_evidence"},
),
ExpertSpec(
name="frontier_reasoner",
capabilities={"reason", "ambiguous", "hard_case"},
cost_tier=4,
latency_tier=4,
),
]
Now the router has something concrete to reason over.
Routing Is Classification Over Capabilities
At its simplest, routing is classification:
input task
↓
features
↓
classify required capability
↓
select compatible expert
For example:
"Fix the failing pytest test"
↓
capability = code/debug/test
↓
code_specialist
Or:
"What was our revenue last quarter?"
↓
capability = structured_data_query
↓
SQL / analytics expert
Or:
"What is 17.4 × 8.2?"
↓
capability = deterministic_math
↓
calculator
Notice the final example.
The best expert is not necessarily an LLM.
That is one of the most useful ideas in agent-level MoE.
Deterministic Experts Belong in the Mixture Too
A mixture of experts can contain:
LLMs
retrievers
calculators
SQL engines
static analyzers
compilers
test runners
rule engines
search systems
specialized ML models
If a deterministic system can answer a question more reliably, it should often win the route.
For example:
def preferred_expert(capability: str) -> str:
deterministic = {
"arithmetic": "calculator",
"sql": "database",
"syntax_check": "parser",
"tests": "test_runner",
}
if capability in deterministic:
return deterministic[capability]
return "llm"
This is not less agentic.
It is better systems engineering.
A Router Should Return More Than a Name
A production route decision should be inspectable.
For example:
from dataclasses import dataclass
@dataclass
class RouteDecision:
expert: str
confidence: float
required_capabilities: list[str]
alternatives: list[str]
reason: str
Then the runtime can distinguish:
high-confidence route
from
ambiguous route
That enables adaptive behavior.
Low Confidence Should Change Control Flow
Suppose the router returns:
RouteDecision(
expert="code_specialist",
confidence=0.96,
required_capabilities=["code", "debug"],
alternatives=[],
reason="Explicit request to debug Python code.",
)
That is probably safe to route directly.
But:
RouteDecision(
expert="research_specialist",
confidence=0.52,
required_capabilities=["research", "code"],
alternatives=["code_specialist"],
reason="Task requires both repository inspection and external evidence.",
)
That uncertainty should matter.
Possible responses include:
route to multiple experts
or
ask a stronger router
or
use a generalist
or
split the task
or
route sequentially
Uncertainty is not merely metadata.
It should affect the architecture.
Hard Routing
The simplest MoE architecture chooses exactly one expert:
request
↓
router
↓
expert B
↓
result
This is hard routing.
It is cheap and easy to understand.
Its main failure mode is obvious:
wrong route = potentially lost task
If the router chooses the wrong expert, the correct expert never gets a chance.
Top-K Routing
Instead of selecting one expert, we can select several:
request
↓
router
├→ expert B
└→ expert D
↓
aggregate / verify
This is useful when:
- route confidence is low,
- two capabilities are genuinely required,
- specialists provide complementary evidence,
- or the cost of a wrong route is high.
But it also increases:
- calls,
- latency,
- aggregation complexity,
- and correlated failure risk.
Do not make k=3 your default merely because it feels safer.
Measure it.
Sequential Routing
Some tasks are not best solved by parallel experts.
They require a sequence.
For example:
research specialist
↓
extract evidence
↓
code specialist
↓
implement change
↓
verifier
This is closer to a routed workflow than classical MoE.
But the same expert-selection principle applies.
The important question is:
Which capability should receive the current state next?
That means routing can happen repeatedly during one trajectory.
Routing Is Not the Same as Multi-Agent Conversation
A mixture of experts does not require experts to talk to each other.
This is perfectly valid:
router
↓
expert
↓
verifier
No committee.
No debate.
No agent group chat.
That simplicity is often an advantage.
We should only introduce agent-to-agent communication when information genuinely has to flow between specialists.
The Local Model + Frontier Model Pattern
One of the most useful real-world MoE patterns is:
cheap/local model first
↓
can it solve task confidently?
├─ yes → verify → return
└─ no → frontier model
This architecture can dramatically reduce expensive calls if many tasks are easy.
A simple escalation policy might be:
def choose_model(
complexity: float,
uncertainty: float,
previous_failure: bool,
) -> str:
if previous_failure:
return "frontier"
if complexity > 0.75:
return "frontier"
if uncertainty > 0.60:
return "frontier"
return "local"
But remember:
model confidence
≠
correctness
The escalation should ideally use external verification too.
Verification-Driven Escalation
A stronger design is:
local expert
↓
verification
├─ PASS → return
├─ FAIL → escalate
└─ UNKNOWN → escalate
Now the system does not need to trust the local model’s self-assessment.
For example:
def route_after_local(result, verification):
if verification.status == "PASS":
return "accept"
if verification.status in {"FAIL", "UNKNOWN"}:
return "frontier_model"
This connects directly to the verification architecture from the core agents series.
Cheap Experts First Is a Compute Policy
We can think of MoE routing as compute allocation.
Suppose:
local model = 1 unit
specialist model = 3 units
frontier model = 12 units
Then the router is effectively deciding:
How much inference compute does this task deserve?
That connects agent-level MoE to the broader theme of the advanced series:
Advanced agent architecture is often really compute allocation under uncertainty.
Cost-Aware Routing
We can explicitly include cost in route selection.
For example:
@dataclass
class CandidateRoute:
expert: str
predicted_success: float
estimated_cost: float
estimated_latency_ms: float
def utility(route: CandidateRoute) -> float:
return (
route.predicted_success
- 0.03 * route.estimated_cost
- 0.0001 * route.estimated_latency_ms
)
Then:
chosen = max(routes, key=utility)
This is intentionally simplistic.
The important idea is that route selection can optimize more than raw quality.
Production systems care about:
- success,
- cost,
- latency,
- privacy,
- locality,
- tool access,
- context limits,
- and safety constraints.
Capability Constraints Should Come Before Preference Scores
Do not let a cheap expert win a route it cannot perform.
First filter by hard constraints:
def compatible(expert: ExpertSpec, required: set[str]) -> bool:
return required.issubset(expert.capabilities)
Then rank compatible experts.
Architecture:
all experts
↓
hard capability filter
↓
compatible experts
↓
soft scoring
↓
selected route
Hard constraints should not be traded away by a fuzzy score.
Experts Need Sharp Boundaries
Suppose we define these experts:
code_expert
software_expert
programming_expert
engineering_expert
technical_expert
What should the router do?
These experts overlap heavily.
The router now has an unnecessarily difficult classification problem.
Prefer boundaries like:
repository_retrieval
patch_generation
test_diagnosis
architecture_review
security_review
Or, at model level:
cheap_generalist
code_specialist
long_context_researcher
frontier_reasoner
The experts should have a reason to exist.
Expert Collapse
A common MoE failure is expert collapse.
The router discovers one expert that performs reasonably well and sends nearly everything there.
You intended:
25% A
25% B
25% C
25% D
You get:
2% A
3% B
92% C
3% D
This is not automatically wrong.
Perhaps C really is best.
But it is worth investigating.
Measure expert utilization:
from collections import Counter
counts = Counter(route.expert for route in route_history)
Then inspect:
- task distribution,
- expert quality,
- router calibration,
- expert overlap,
- and whether some experts are unnecessary.
Do not force equal traffic simply to make the architecture look balanced.
Redundant Experts Are Another Failure Mode
Suppose two experts consistently produce nearly identical outputs.
Then the second expert may not be providing meaningful specialization.
Measure marginal contribution.
For expert E:
system success with E
-
system success without E
If the difference is near zero, ask why E exists.
This is a powerful ablation.
Routing Accuracy Is Not Enough
Imagine your router selects the “correct” expert 95% of the time according to labels.
That sounds excellent.
But what if the expert itself performs poorly?
We need several metrics.
routing accuracy
expert success conditional on route
overall verified success
cost
latency
The real objective is not:
correct expert label
It is:
verified task success
Routing accuracy is only an intermediate metric.
Build a Routing Confusion Matrix
Suppose the expected classes are:
code
research
data
general
Then track:
expected → selected
Example:
selected
expected code research data general
----------------------------------------
code 91 4 2 3
research 7 84 4 5
data 5 3 89 3
general 6 4 2 88
Immediately we can see:
research → code
is a recurring confusion.
That gives us something concrete to fix.
Why the Router Makes Mistakes
Common causes include:
1. Overlapping expert descriptions
code expert: handles technical programming tasks
research expert: handles technical investigation
Many tasks match both.
2. Missing task context
The router receives:
"Fix this"
instead of repository state, failure type, and user objective.
3. Too many experts
Every added route increases the classification space.
4. Bad labels
Your benchmark may disagree with what actually produces the best verified outcome.
5. Stale expert capabilities
The router thinks an expert supports a tool or context size it no longer supports.
6. Cost dominates too strongly
The optimizer repeatedly selects a cheap but inadequate expert.
Route by State, Not Just by User Prompt
Agent routing becomes much more powerful when the router sees runtime state.
For example:
@dataclass
class AgentState:
task: str
step: int
failures: list[str]
evidence: list[str]
last_expert: str | None
verification_status: str | None
Then routing can change during the trajectory.
Example:
initial task
↓
local code model
↓
tests still fail
↓
debug specialist
↓
verification unknown
↓
frontier reasoner
This is much more interesting than one-time prompt classification.
Failure-Aware Routing
Repeated failure is useful routing evidence.
Suppose the same expert has failed twice.
Do not simply send the task back unchanged.
def should_escalate(state: AgentState) -> bool:
return len(state.failures) >= 2
Then:
same expert fails repeatedly
↓
change expert / model / strategy
This connects routing to the loop-control ideas from the earlier series.
A Full Minimal Agent-Level MoE Runtime
Here is a small provider-independent skeleton.
from dataclasses import dataclass, field
from typing import Callable, Any
@dataclass
class Expert:
name: str
capabilities: set[str]
run: Callable[[dict], Any]
cost_tier: int = 1
@dataclass
class RouteDecision:
expert: str
confidence: float
alternatives: list[str] = field(default_factory=list)
reason: str = ""
@dataclass
class ExpertResult:
expert: str
output: Any
verified: bool | None = None
class ExpertRegistry:
def __init__(self):
self._experts: dict[str, Expert] = {}
def register(self, expert: Expert) -> None:
self._experts[expert.name] = expert
def get(self, name: str) -> Expert:
return self._experts[name]
def compatible(self, capabilities: set[str]) -> list[Expert]:
return [
expert
for expert in self._experts.values()
if capabilities.issubset(expert.capabilities)
]
class AgentMoE:
def __init__(self, registry, router, verifier):
self.registry = registry
self.router = router
self.verifier = verifier
def run(self, task: dict) -> ExpertResult:
required = set(task.get("required_capabilities", []))
candidates = self.registry.compatible(required)
if not candidates:
raise RuntimeError(
f"No expert supports capabilities: {sorted(required)}"
)
decision = self.router(task, candidates)
expert = self.registry.get(decision.expert)
output = expert.run(task)
verification = self.verifier(task, output)
if verification == "PASS":
return ExpertResult(
expert=expert.name,
output=output,
verified=True,
)
if verification in {"FAIL", "UNKNOWN"}:
for alternative_name in decision.alternatives:
alternative = self.registry.get(alternative_name)
output = alternative.run(task)
verification = self.verifier(task, output)
if verification == "PASS":
return ExpertResult(
expert=alternative.name,
output=output,
verified=True,
)
return ExpertResult(
expert=expert.name,
output=output,
verified=False,
)
This gives us the essential architecture:
capability filter
↓
router
↓
expert
↓
verifier
↓
fallback / escalation
Routing Can Be Deterministic
Do not immediately train a router.
A rule-based router can be excellent when the domains are clear.
Example:
def deterministic_router(task, candidates):
capabilities = set(task["required_capabilities"])
if "arithmetic" in capabilities:
target = "calculator"
elif "code" in capabilities:
target = "code_specialist"
elif "research" in capabilities:
target = "research_specialist"
else:
target = "general_model"
alternatives = [
expert.name
for expert in candidates
if expert.name != target
]
return RouteDecision(
expert=target,
confidence=1.0,
alternatives=alternatives,
reason="Deterministic capability rule.",
)
If this solves 95% of your routing problem, keep it.
LLM Router
When task boundaries are fuzzy, an LLM can classify the request.
But the router itself should have a constrained output schema.
For example:
{
"required_capabilities": ["code", "debug"],
"selected_expert": "code_specialist",
"confidence": 0.83,
"alternatives": ["frontier_reasoner"]
}
Then validate that:
- the expert exists,
- the expert supports the capabilities,
- confidence is within bounds,
- alternatives exist,
- the route is authorized.
The router should not be allowed to invent experts.
Learned Router
Eventually you may have enough routing evidence to train a small classifier.
Inputs might include:
task embedding
repository type
tool requirements
context length
failure history
cost budget
latency budget
Output:
P(expert | task, state)
This can be valuable at high volume.
But it creates a new model that must itself be evaluated, versioned, calibrated, and monitored.
Do not build it because “MoE should have a learned router.”
Build it because the routing dataset shows deterministic and prompt-based routing have become the bottleneck.
Router Training Data Should Come From Outcomes
A subtle point:
The best training label is not necessarily:
which expert sounds appropriate?
It is closer to:
which expert produced the best verified outcome
for this task under the relevant cost/latency constraints?
That means routing data should ideally contain:
@dataclass
class RoutingEvidence:
task_id: str
expert: str
verified_success: bool
cost: float
latency_ms: float
failure_type: str | None
Now router improvement becomes evidence-driven.
Expert Selection Can Be Contextual Bandit-Like
Once the system gathers outcome evidence, routing begins to resemble a contextual bandit problem:
context
↓
choose expert
↓
observe reward/outcome
↓
update routing policy
That does not mean you should immediately deploy online learning.
But it gives us a useful conceptual model:
routing is a decision under uncertainty
with observable downstream consequences
This becomes important later when we discuss adaptive agents.
Specialist Judges Are Experts Too
Suppose we generate code with one model.
We might route the result to specialists:
patch
↓
syntax verifier
↓
test runner
↓
security reviewer
↓
performance reviewer
These are not necessarily candidate generators.
They are expert evaluators.
So an agent MoE can exist on both sides:
generation experts
and
evaluation experts
This is especially useful when failure classes differ significantly.
But Do Not Build a Reviewer Army Without Evidence
Imagine:
security critic
style critic
architecture critic
correctness critic
performance critic
maintainability critic
simplicity critic
That looks thorough.
It may simply multiply cost.
Ask:
What additional verified defects does each reviewer find?
Measure marginal defect discovery.
If two reviewers find the same issues almost every time, one may be redundant.
Expert Diversity Should Be Functional
Useful diversity comes from different capabilities or evidence channels.
For example:
LLM reviewer
static analyzer
test runner
security scanner
runtime benchmark
Those are genuinely different experts.
Less useful:
same model + "be a reviewer"
same model + "be a critic"
same model + "be skeptical"
Prompt-role diversity can help.
It should not be confused with independent evidence.
Agent MoE for Coding Software
Coding agents are a natural application.
A useful mixture might look like:
engineering task
↓
router
├→ repository retrieval
├→ code generator
├→ test diagnosis
├→ architecture reviewer
├→ security reviewer
└→ frontier escalation
Example route:
"Why is CI failing?"
↓
CI/log specialist
↓
identify failing test
↓
code specialist
↓
patch
↓
test verifier
The application is not “several agents talking.”
It is specialized engineering capabilities connected by explicit routing.
Agent MoE for Research Software
A research system may have:
query classifier
web retrieval specialist
paper retrieval specialist
source-quality evaluator
claim extractor
synthesis model
citation verifier
A route might be:
question
↓
requires current evidence?
├─ no → internal/general model
└─ yes → retrieval specialist
↓
evidence
↓
synthesis
↓
citation verifier
Again, specialization is driven by the work.
Agent MoE for Customer Support
A support architecture might route by case type:
incoming case
↓
classifier
├→ billing
├→ shipping
├→ technical support
├→ account access
└→ human escalation
Each route can have different:
- tools,
- permissions,
- policies,
- verification,
- and risk limits.
This is a strong reason to route: not every specialist should have access to every action.
Agent MoE for Data and Analytics
A data agent might use:
question
↓
router
├→ SQL generator
├→ dataframe analyst
├→ statistical model
├→ chart generator
└→ narrative summarizer
Important distinction:
"calculate revenue by quarter"
should probably route toward deterministic data execution.
Whereas:
"explain why revenue changed"
may require synthesis after the computation.
One request can therefore cross multiple experts sequentially.
Agent MoE for DevOps and Incident Response
An incident system could contain:
log specialist
metrics specialist
configuration specialist
network specialist
deployment specialist
rollback verifier
A route might depend on evidence:
latency spike
↓
metrics specialist
↓
DB saturation detected
↓
database specialist
↓
remediation candidate
↓
verification
This is state-dependent expert routing.
Agent MoE for Browser Automation
A browser system may divide work between:
page-state parser
navigation planner
form specialist
extraction specialist
visual fallback model
Most pages may be handled cheaply from DOM structure.
Only difficult visual states may need the expensive vision model.
That gives us:
DOM-first
↓
verification
↓
vision escalation only when necessary
A classic adaptive-compute MoE pattern.
Application Matrix
| Software | Possible experts | Useful routing signal | Strong verification |
|---|---|---|---|
| Coding agent | retrieval, generator, debugger, reviewer | task/failure type | tests, build, static analysis |
| Research agent | search, source evaluator, synthesis | evidence need/source type | source support/citations |
| Support agent | billing, technical, account, escalation | case classification | backend state/policy checks |
| Data agent | SQL, stats, dataframe, narrative | data operation required | query results/invariants |
| DevOps agent | logs, metrics, DB, deployment | observed failure signal | health checks/metrics |
| Browser agent | DOM, navigation, forms, vision | page state | resulting DOM/page state |
The important pattern is:
route based on task/state
verify using the environment
The Router Should Not Be the Final Judge
Suppose the router says:
expert = code_specialist
confidence = 0.98
That does not prove the code specialist will succeed.
The architecture still needs:
route
↓
execute
↓
verify
Routing confidence is about routing.
It is not task-success confidence.
Expert Confidence Is Also Not Enough
The expert may say:
"I am 95% confident the issue is fixed."
The test suite may say:
FAIL
The test suite wins.
The evidence hierarchy remains:
environment evidence
>
model confidence
Fallback Should Be Explicit
Production routing needs a fallback policy.
For example:
route expert A
↓
verification FAIL
↓
expert B
↓
verification UNKNOWN
↓
frontier generalist
↓
verification
Avoid unlimited fallback chains.
Set budgets:
MAX_EXPERT_ATTEMPTS = 3
MAX_TOTAL_COST = 2.50
MAX_LATENCY_SECONDS = 30
The system must eventually stop.
Track Why Escalation Happened
Do not log only:
model = frontier
Log:
initial_expert = local_code
reason_for_escalation = verification_failed
failed_check = test_user_creation
frontier_expert = frontier_code
This makes expensive routes explainable.
It also lets you improve the cheap path later.
Routing Telemetry
At minimum, record:
@dataclass
class RouteTrace:
task_id: str
selected_expert: str
confidence: float
alternatives: list[str]
route_reason: str
verified_success: bool | None
cost: float
latency_ms: float
escalated: bool
escalation_reason: str | None
Now you can ask:
- Which expert gets the most traffic?
- Which expert has the best verified-success rate?
- Which routes fail most often?
- Where does escalation happen?
- Which expert is expensive but rarely necessary?
- Which specialist contributes unique wins?
Metrics That Actually Matter
Useful metrics include:
Router metrics
routing accuracy
routing calibration
route entropy
expert utilization
Expert metrics
verified success by expert
failure type by expert
latency by expert
cost by expert
System metrics
overall verified success
cost per verified success
p50/p95 latency
escalation rate
fallback success rate
Specialization metrics
marginal success contribution
unique defect discovery
expert redundancy
These tell us whether MoE is actually doing useful work.
Route Entropy
One simple diagnostic is route entropy.
If every request goes to the same expert, route entropy is low.
That may mean:
- expert collapse,
- the router is biased,
- or one expert genuinely dominates.
If every request is distributed almost uniformly, route entropy is high.
That may mean:
- tasks are genuinely diverse,
- or the router is uncertain/noisy.
The metric is diagnostic, not an objective.
Do not optimize entropy for its own sake.
When More Experts Make the System Worse
Adding experts can reduce reliability because it increases:
routing ambiguity
configuration complexity
observability burden
fallback complexity
benchmarking surface
Suppose success is:
one model = 89%
3 experts = 92%
8 experts = 91%
16 experts = 88%
The sophisticated architecture lost.
More specialists did not create more specialization.
They created a harder routing problem.
Do Not Add an Expert Without a Failure It Owns
A useful rule:
Every expert should own a recognizable failure class or capability boundary.
For example:
Expert: test_diagnosis
Failure it addresses:
General model changes code before understanding the failing test.
Or:
Expert: long_context_retrieval
Failure it addresses:
General model loses relevant repository context in large codebases.
Or:
Expert: frontier_escalation
Failure it addresses:
Local model fails on the small subset of genuinely difficult tasks.
If you cannot write that sentence, you may not need the expert.
Ablate Experts One at a Time
A simple experiment:
full system
full system - expert A
full system - expert B
full system - expert C
Measure:
verified success
cost
latency
failure coverage
If removing an expert changes nothing, that is strong evidence it may be unnecessary.
Compare Against the One-Model Baseline
The most important experiment is still:
one strong model
vs
routed mixture
A useful table:
| Architecture | Verified success | Avg cost | p95 latency |
|---|---|---|---|
| One strong model | 91% | $0.18 | 4.2s |
| Local only | 78% | $0.02 | 1.1s |
| Local → frontier escalation | 92% | $0.07 | 3.0s |
| 5-expert router | 93% | $0.10 | 4.8s |
These numbers are illustrative.
The question is not:
Which architecture sounds advanced?
It is:
Which architecture produces the best verified outcome
under our actual constraints?
Run an Oracle Routing Experiment
One particularly useful experiment is oracle routing.
For every benchmark task, run all candidate experts offline.
Then determine:
which expert actually produced the best verified result?
Now you can estimate an upper bound:
oracle expert selection
Compare:
router-selected success
vs
oracle-selected success
If oracle routing is only slightly better than one model, specialization itself has limited value.
If oracle routing is dramatically better but your router performs poorly, the bottleneck is routing.
This separates two questions beautifully:
Does specialization help?
and
Can we route to it reliably?
Router Regret
We can define a simple routing regret concept:
oracle outcome
-
selected-route outcome
For score-based tasks:
routing_regret = oracle_score - selected_score
For binary verified success, track how often:
a successful expert existed
but the router chose a failing expert
That is one of the most useful routing metrics you can collect.
Hard Cases Should Generate Training Evidence
When routing fails:
store the task
store the selected route
store candidate expert outcomes
store verification
store cost/latency
Now routing errors become future evidence.
That is how a static router can eventually become an adaptive one.
But that is a later mechanism.
Memory of outcomes is not yet learning.
Do Not Confuse Memory With Adaptive Routing
A system may retrieve:
"Last time this kind of error went to the DB specialist."
That is memory.
If the routing policy itself changes because repeated outcomes demonstrate that the DB specialist works better, that is learning/adaptation.
We will return to that distinction later in this series.
MoE and Search Can Be Combined
The architectures in this series are composable.
For example:
MCTS node
↓
router
├→ code expert
├→ research expert
└→ verifier
Different branches could use different experts.
Or:
router
↓
expert
↓
Tree of Thoughts inside expert
But composition multiplies cost and failure surfaces quickly.
Build one mechanism at a time.
Benchmark every escalation.
MoE and Self-Consistency Can Be Combined
Instead of sampling one model five times:
same model × 5
we might sample specialists:
code model
frontier generalist
local model
static analyzer
That can produce more functional diversity.
But aggregation still needs strong evidence.
Expert disagreement does not become truth through voting.
MoE and Verification Are Natural Partners
A powerful architecture is:
cheap specialist
↓
verification
├─ PASS → finish
└─ FAIL/UNKNOWN
↓
stronger expert
↓
verification
This creates an evidence-driven compute cascade.
Instead of predicting difficulty perfectly in advance, the runtime uses failed verification as evidence that more compute is required.
That is often easier to engineer than a perfect complexity classifier.
Safety and Permissions Belong in Expert Definitions
Experts should not all have the same permissions.
Example:
@dataclass
class ExpertPolicy:
expert: str
allowed_tools: set[str]
can_write: bool
can_deploy: bool
can_access_secrets: bool
A research expert might have:
read/search only
A deployment expert may have:
production actions
Routing therefore affects authorization.
The runtime must validate both:
Is this expert suitable?
and
Is this expert allowed to perform this action?
Never Let the Router Invent Authority
Even if the router chooses:
deployment_expert
that does not automatically grant production access.
Authorization must be independent.
Architecture:
router proposes expert
↓
authorization layer
↓
allowed capability subset
↓
execution
The model does not get to increase its own permissions by changing the route.
Production Debugging Checklist
If your routed agent system performs badly, inspect it in this order.
1. Does specialization actually help?
Run oracle routing.
If every expert performs similarly, the architecture may not need specialists.
2. Is the router choosing the wrong expert?
Inspect confusion matrix and routing regret.
3. Are expert boundaries overlapping?
Merge or sharpen ambiguous experts.
4. Is one expert receiving almost everything?
Inspect expert collapse, but do not force balance without evidence.
5. Are cheap experts failing and escalating constantly?
The cheap tier may be too weak or the route threshold too permissive.
6. Are expensive experts called unnecessarily?
Tighten escalation and deterministic routes.
7. Do specialists produce unique wins?
Ablate each expert.
8. Is the router using stale capability metadata?
Version expert specs.
9. Are verification failures being mistaken for routing failures?
Separate subsystem telemetry.
10. Does the whole mixture beat one strong model?
If not, simplify.
A Controlled Experiment
Suppose we are building a coding assistant.
Benchmark these architectures:
A: frontier model for every task
B: local model for every task
C: rule router → local/code/frontier
D: learned/LLM router → specialists
E: C + verification-driven escalation
Record:
verified task success
routing regret
oracle routing success
local-model utilization
frontier escalation rate
model calls
latency
cost
Then introduce failure buckets:
simple Q&A
repository retrieval
bug diagnosis
patch generation
test repair
architecture review
Now you can see where specialization actually helps.
The Result We Want
The goal is not:
more agents
It is closer to:
cheap mechanism for easy work
specialist mechanism for specialized work
frontier model for genuinely hard work
deterministic tools where deterministic tools are stronger
verification after every consequential outcome
That is a much more useful interpretation of mixture of experts.
A Practical Escalation Ladder
Start here:
one model
If cost is the problem:
cheap model
↓
verification-driven frontier escalation
If domain failures are concentrated:
router
├→ domain specialist A
├→ domain specialist B
└→ generalist
If route uncertainty matters:
top-k experts
↓
strong aggregation / verification
If routing itself becomes the bottleneck:
learned router
Only add the next stage when the previous architecture produces evidence that it is needed.
The Core Principle
The essential insight is simple:
A mixture of experts is not a collection of agents. It is a policy for assigning work to the capability most likely to solve it under your cost, latency, safety and verification constraints.
That policy can be:
- deterministic,
- model-based,
- learned,
- state-dependent,
- failure-aware,
- cost-aware,
- or verification-driven.
The architecture becomes useful when specialization creates real differences in outcome.
If every expert behaves the same, you do not have a meaningful mixture.
If routing adds more failure than specialization removes, simplify.
If one cheap expert solves almost everything, let it.
And if only a small fraction of requests truly require frontier-level inference, do not make every request pay for it.
Where We Are in the Series
We now have:
00 When do advanced agent architectures help?
↓
01 Chain of thought as intermediate computation
↓
02 Self-consistency and disagreement
↓
03 Tree of Thoughts
↓
04 Monte Carlo Tree Search
↓
05 Agent-level Mixture of Experts
So far we have explored two major advanced-agent dimensions:
search
and
routing / specialization
The next step is to combine specialized responsibilities into a deliberate control architecture.
That means moving beyond one router selecting one expert and asking:
What if planning, execution, criticism and verification should be separate roles with explicit contracts?
That is the subject of the next post:
Advanced Agents From First Principles 06: Does One Agent Plan, Execute and Judge Its Own Work? Build a Planner–Executor–Critic Architecture.