Skip to Content
DocsModelsPer-role model routing

Per-role model routing

Model routing — the right model for the right task — is the single biggest lever on an agent bill. Planning and judging need your best model; typing out code, scoring a thought 0–1, or classifying a request into a bucket does not. Output tokens cost ~5× input tokens, and execution is output-heavy while planning is input-heavy — so putting the frontier model where judgment lives and a cheap model where typing lives routinely cuts 60–90% of a run’s cost.

Loomflow gives you this at two levels.


Between agents — every Team seat has its own model

Each worker in a team is a full Agent with its own model=. The coordinator plans and reviews (short outputs — cheap even on a frontier model); the workers execute (long outputs — where the cheap model saves real money):

from loomflow import Agent from loomflow.team import Team team = Team.supervisor( model="claude-fable-5", # plans, delegates, reviews workers={ "coder": Agent( "Implement exactly what the delegation says.", model="claude-sonnet-4-6", # cheap executor tools=[read_tool, write_tool, bash_tool], ), "tester": Agent( "Run tests, report failures verbatim.", model="claude-haiku-4-5", # mechanical task tools=[bash_tool], ), }, budget=StandardBudget(BudgetConfig(max_cost_usd=5.0)), )

The coordinator’s delegate(worker, instructions) calls are the spec-writing; worker spend decrements the same budget; one audit log covers the whole tree. This has always worked — every seat is just an Agent.


Inside one agent — <role>_model= on the reasoning architectures

The single-agent architectures make several kinds of internal call on what used to be one model. Each role now takes its own model — spec string, Model instance, or None (= the agent’s main model, exactly the previous behavior):

from loomflow import Agent from loomflow.architecture import PlanAndExecute, TreeOfThoughts # Plan with a frontier model, execute the steps with a cheap one. agent = Agent( "You build features.", model="claude-sonnet-4-6", # synthesis + default architecture=PlanAndExecute( planner_model="claude-fable-5", # thinks executor_model="claude-haiku-4-5", # types ), ) # ToT's evaluator makes branch_factor × beam_width × depth "rate this # thought 0-1" calls per run — pure cheap-model territory. agent = Agent( "You solve hard problems.", model="claude-opus-4-8", # proposals + synthesis architecture=TreeOfThoughts( evaluator_model="claude-haiku-4-5", # ~12 scoring calls/run ), )

The full kwarg map:

ArchitectureRole kwargsWhat each role does
PlanAndExecuteplanner_model, executor_model, synthesizer_modeldecompose · run each step · final answer
TreeOfThoughtsproposer_model, evaluator_model, synthesizer_modelpropose thoughts · score 0–1 · final answer
ReWOOplanner_model, solver_modelplan tool steps · solve over results
Reflexionevaluator_model, reflector_modelpass/fail check · one-sentence lesson
SelfRefinecritic_model, refiner_modelcritique the draft · apply the critique

The base attempt / initial draft / main tool loop always stays on the agent’s model= — that is where quality lives, and keeping the hot path on one model preserves its prompt-cache prefix.

Role models are not auto-wrapped with retries (same convention as the run_until checker). Pass a wrapped instance for resilience: evaluator_model=RetryingModel(AnthropicModel("claude-haiku-4-5")) — or a whole FallbackModel chain.


The cheap-classifier router

Team.router’s own model does exactly one thing: classify the request. Its workers each carry their own model — so the frontier never sees the routing decision at all:

team = Team.router( model="claude-haiku-4-5", # ONLY classifies workers={ "billing": Agent(..., model="claude-sonnet-4-6"), "escalation": Agent(..., model="claude-fable-5"), }, )

Sidecar models (already routable)

The framework’s helper calls have accepted their own cheap models since before this release — same idea, smaller scopes:

  • Agent(run_until={"checker": "claude-haiku-4-5", ...}) — the goal-met check
  • Tuning(tool_result_summarizer=...) — tool-output compression
  • Tuning(auto_compact_summariser=...) — context compaction
  • LLMJudge(model=...) in the eval harness
  • ModerationGuard(model=...) in guardrails

Routing is explicit by design — loomflow never silently swaps models on you. Every call’s serving model is visible on its telemetry span (gen_ai.request.model), so you can verify exactly where tokens went. Automatic heuristic routing (“auto mode”) is deliberately out of scope until it can be validated against the eval harness.

See examples/31_model_routing.py for a runnable offline demo that proves which model served which call.

Last updated on