Skip to Content

Router

from loomflow.architecture import Router, RouterRoute

Router 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

Typelist[RouterRoute]
Defaultrequired

The specialists. Must be non-empty; route names must be unique. Raises ValueError if violated.

fallback_route

Typestr | None
DefaultNone

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

Typefloat
Default0.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

Typestr | None
DefaultNone (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

TypeDecisionModel | Model | str | None
DefaultNone (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

TypeLiteral["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

  1. Classify. One model call against deps.model (or one decider question) with the route descriptions. Parses the route: and confidence: lines, ignoring markdown, backticks and quotes (**Route:** "tech support") and keeping multi-word route names whole. A percentage confidence (85%, or a bare 85) is scaled to 0.85; a missing confidence line counts as 0.0 when require_confidence_above is set. Emits router.classified.
  2. Resolve. If confidence < require_confidence_above, use fallback_route, or stop with router.unresolved. Otherwise match the parsed name to a route, ignoring case and treating spaces, hyphens and underscores alike (“Tech Support” reaches tech_support). An unknown name also goes to fallback_route, or stops with router.unresolved.
  3. Dispatch. Emits router.dispatched, then runs the chosen specialist’s agent.run(prompt) to completion and forwards every event. The specialist’s RunResult.output becomes the Router’s session.output, and router.completed is emitted.

RouterRoute

@dataclass(frozen=True) class RouterRoute: name: str agent: Agent description: str = ""
FieldTypeDefaultDescription
namestrrequiredWhat 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).
agentAgentrequiredThe specialist Agent that handles this route.
descriptionstr""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 billing

Source

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.

Last updated on