Router
from loomflow.architecture import Router, RouterRouteRouter reads the request, decides which one specialist Agent
should handle it, and hands it over. Use it when each request belongs
to one clear domain (billing, technical support, sales). It is the
cheapest multi-agent pattern: one classifier call plus one specialist
run, with no combining step at the end.
For the conceptual page see Router.
Class signature
class Router:
name: str = "router"
def __init__(
self,
*,
routes: list[RouterRoute],
fallback_route: str | None = None,
require_confidence_above: float = 0.0,
classifier_prompt: str | None = None,
decider: Any | None = None,
worker_registry: dict[str, Any] | None = None,
role_to_worker_id: dict[str, str] | None = None,
conversation_scope: Literal["per_route", "shared"] = "per_route",
) -> None: ...Constructor parameters
routes
| Type | list[RouterRoute] |
| Default | required |
The specialists. Must be non-empty; route names must be unique.
Raises ValueError if violated.
fallback_route
| Type | str | None |
| Default | None |
Name of a route to use when (a) the classifier emits an unknown
route name, or (b) require_confidence_above is non-zero and the
classifier’s confidence is below threshold. Must reference a route
in routes or raises ValueError.
When unset, an unknown route or a low-confidence classification ends
the run with a router.unresolved event and an output that starts
with "Could not route this request." (followed by the parsed route
and confidence).
require_confidence_above
| Type | float |
| Default | 0.0 |
Minimum classifier confidence in [0.0, 1.0]. Classifications below
this score are treated as failures and routed to fallback_route
(or fail if no fallback). 0.0 (default) accepts any confidence.
classifier_prompt
| Type | str | None |
| Default | None (uses built-in default) |
Override the classifier’s system prompt. The default takes the route
descriptions and asks for a route: <name> line plus a
confidence: <0..1> line. The prompt is filled with
str.format(route_descriptions=...), so include
{route_descriptions} and write any literal braces as {{ and }}.
decider
| Type | DecisionModel | Model | str | None |
| Default | None (LLM classifier) |
Classify with a decision model instead of an LLM
classifier call: one choice question over the routes. Its confidence
goes through the same require_confidence_above / fallback_route
checks.
conversation_scope
| Type | Literal["per_route", "shared"] |
| Default | "per_route" |
"per_route" gives each route its own conversation (session id
{parent}__route_{name}), which suits isolated domains. "shared"
runs every route under the parent session, so a chat user sees one
continuous conversation no matter which specialist answered.
worker_registry / role_to_worker_id
Internal wiring set by Team.router
when persistent_subagents=True (the default), so repeat routes to the
same specialist reuse its conversation (one per user). You normally
don’t pass these yourself.
Methods
declared_workers
def declared_workers(self) -> dict[str, Agent]:
return {r.name: r.agent for r in self._routes}Returns the route name → specialist mapping for AgentGraph and
introspection.
run
- Classify. One model call against
deps.model(or onedeciderquestion) with the route descriptions. Parses theroute:andconfidence:lines, ignoring markdown, backticks and quotes (**Route:** "tech support") and keeping multi-word route names whole. A percentage confidence (85%, or a bare85) is scaled to 0.85; a missing confidence line counts as0.0whenrequire_confidence_aboveis set. Emitsrouter.classified. - Resolve. If
confidence < require_confidence_above, usefallback_route, or stop withrouter.unresolved. Otherwise match the parsed name to a route, ignoring case and treating spaces, hyphens and underscores alike (“Tech Support” reachestech_support). An unknown name also goes tofallback_route, or stops withrouter.unresolved. - Dispatch. Emits
router.dispatched, then runs the chosen specialist’sagent.run(prompt)to completion and forwards every event. The specialist’sRunResult.outputbecomes the Router’ssession.output, androuter.completedis emitted.
Related types
RouterRoute
@dataclass(frozen=True)
class RouterRoute:
name: str
agent: Agent
description: str = ""| Field | Type | Default | Description |
|---|---|---|---|
name | str | required | What the classifier emits in its route: line. Must be unique within a Router. Use simple identifiers (billing, tech_support); the classifier’s answer is matched ignoring case and treating spaces, hyphens and underscores alike (Tech Support → tech_support). |
agent | Agent | required | The specialist Agent that handles this route. |
description | str | "" | Shown to the classifier alongside name. Be specific and distinguishing: include both what the route handles AND what it doesn’t (e.g. "Handle billing, but NOT refunds (those go to support)"). |
Cost model
1 classifier call (typically a small fast model like Haiku) + 1 specialist run (typically your main production model). The
classifier is the cost knob. Use a small fast model for
classification and reserve big-model spend for specialists.
Roughly 1.2× a single-agent ReAct run.
Compared to Supervisor
- Cheaper: one classification plus one specialist run, with no combining step.
- More predictable: a single specialist owns the task end to end.
- Less flexible: it can’t split a task across domains, and a routing mistake sends the whole task to the wrong specialist.
When the request might span multiple domains (research + write +
review), use Supervisor instead.
Example
from loomflow import Agent
from loomflow.architecture import RouterRoute
from loomflow.team import Team
billing = Agent("Handle billing questions.", model="claude-opus-4-7", tools=[...])
tech = Agent("Handle technical questions.", model="claude-opus-4-7", tools=[...])
sales = Agent("Handle sales questions.", model="claude-opus-4-7")
team = Team.router(
routes=[
RouterRoute(name="billing", agent=billing,
description="Charges, refunds, invoicing, plan changes."),
RouterRoute(name="tech", agent=tech,
description="Errors, debugging, integration help."),
RouterRoute(name="sales", agent=sales,
description="Pricing, demos, contracts, partnerships."),
],
instructions="You are the customer support entry point.",
model="claude-haiku-4-5", # small fast classifier
fallback_route="tech", # default to tech for ambiguous requests
)
result = await team.run("My credit card was charged twice.")
# → routed to billingSource
loomflow/architecture/router.py
Route descriptions are the prompt. The classifier sees each
route’s name + description; the description is what makes
routing correct or wrong. Spend time on descriptions; routing errors
trace directly back to ambiguous descriptions.