Universal Router
Purpose and Scope
The Universal Router is the intelligent request classification and routing engine at the core of Automatos AI's orchestrator. It receives incoming requests from multiple channels (chat, webhooks, triggers) and determines the optimal execution path: routing directly to a specific agent, invoking a workflow, or initiating a full orchestration decomposition for complex tasks.
This document covers the router's four-tier routing strategy, configuration, and integration points. For information about agent execution after routing decisions, see Agents. For workflow execution patterns, see Workflows & Recipes. For details on the API endpoints that accept routed requests, see Agent API Reference and Workflow API Reference.
Sources: orchestrator/core/routing/engine.py:1-16
Architecture Overview
The Universal Router implements a tiered cascade strategy designed to minimize LLM API costs while maintaining routing accuracy. Each tier represents a progressively more expensive routing method:
Tier 0 (User Overrides): Explicit
override_agent_idoroverride_workflow_idparameters bypass all routing logicTier 1 (Cache Lookup): Redis-backed cache returns previous routing decisions for identical requests
Tier 2 (Rule-Based Routing): Three sub-tiers check workspace-specific rules, trigger subscriptions, and intent keywords
Tier 3 (LLM Classification): Fallback to LLM-powered agent selection when all other tiers fail
Requests flow through each tier sequentially until a routing decision is made. If all tiers fail to produce a decision, the request is stored as an UnroutedEvent for later analysis.
Sources: orchestrator/core/routing/engine.py:78-144, orchestrator/config.py:140-145
Request Envelope and Routing Decision
RequestEnvelope
The RequestEnvelope model encapsulates all information needed for routing decisions:
id
str
Unique request identifier
workspace_id
UUID
Workspace context for rule filtering
source
ChannelSource
Origin channel (e.g., CHAT, JIRA_TRIGGER, COMPOSIO_WEBHOOK)
content
str
User message or event payload
metadata
Dict
Additional context (trigger names, webhook signatures)
override_agent_id
Optional[int]
Tier 0: explicit agent routing
override_workflow_id
Optional[int]
Tier 0: explicit workflow routing
raw_payload
Optional[Dict]
Full webhook/trigger payload for audit trail
Sources: orchestrator/core/routing/engine.py:32-40
RoutingDecision
The RoutingDecision model represents the router's output:
route_type
str
One of: "agent", "workflow", "orchestrate"
agent_id
Optional[int]
Target agent ID (if route_type is "agent" or "orchestrate")
workflow_id
Optional[int]
Target workflow ID (if route_type is "workflow")
confidence
float
Routing confidence score (0.0 to 1.0)
reasoning
str
Human-readable explanation of routing logic
intent_category
Optional[str]
Detected intent category from Tier 2c
cached
bool
True if decision came from Tier 1 cache
Route type semantics:
"agent": Direct execution on the specified agent (high confidence)"workflow": Execute the specified workflow recipe"orchestrate": Low-confidence LLM result; trigger full task decomposition before executing on agent
Sources: orchestrator/core/routing/engine.py:32-40
Core Components and Code Entities
Sources: orchestrator/core/routing/engine.py:57-586
Tier 0: User Overrides
Purpose: Allow explicit routing when the caller knows the desired agent or workflow.
When a RequestEnvelope contains either override_agent_id or override_workflow_id, the router immediately returns a decision with confidence=1.0 and reasoning="User override". This bypasses all downstream routing logic, cache, and LLM calls.
Implementation: orchestrator/core/routing/engine.py:150-165
Use cases:
Frontend chat interface with explicit agent selection dropdown
API clients specifying target agents via query parameters
Workflow steps that explicitly invoke sub-agents
Sources: orchestrator/core/routing/engine.py:150-165
Tier 1: Cache Lookup
Purpose: Return cached routing decisions for identical requests to avoid redundant classification.
The RoutingCache uses Redis to store routing decisions keyed by (workspace_id, normalized_content, source). Cache entries have a configurable TTL (default 24 hours via ROUTING_CACHE_TTL_HOURS).
Cache key construction:
Content is normalized: lowercased, whitespace trimmed, special characters removed
Key format:
routing:{workspace_id}:{content_hash}:{source}Content hash is SHA-256 hex (first 16 characters for brevity)
Implementation: orchestrator/core/routing/engine.py:171-176
Cache population: When Tier 3 (LLM classification) succeeds, the decision is automatically stored in the cache to benefit future identical requests. See orchestrator/core/routing/engine.py:421-427.
Cache invalidation: The cache has no explicit invalidation mechanism beyond TTL expiration. Routing rules, agent descriptions, and tool assignments that change mid-TTL period will not affect cached decisions until they expire.
Sources: orchestrator/core/routing/engine.py:171-176, orchestrator/core/routing/cache.py:1-50 (referenced but not shown), orchestrator/config.py:143
Tier 2: Rule-Based Routing
Tier 2 consists of three sub-tiers that check increasingly sophisticated rule-based patterns.
Tier 2a: Routing Rules (Source Pattern Match)
Purpose: Route based on workspace-specific rules that match the request source.
Queries the routing_rules table for active rules in the request's workspace, ordered by priority DESC. The first rule where source_pattern matches envelope.source.value (or where source_pattern is NULL/empty, meaning "match any source") determines the routing decision.
Database schema: routing_rules
id
int
Primary key
workspace_id
UUID
Workspace isolation
source_pattern
str
Source to match (e.g., "CHAT", "JIRA_TRIGGER"); NULL matches any
intent_keywords
List[str]
Keywords for Tier 2c intent matching
target_agent_id
int
Agent to route to (nullable)
target_workflow_id
int
Workflow to route to (nullable)
priority
int
Higher priority rules evaluated first
is_active
bool
Enable/disable without deletion
Implementation: orchestrator/core/routing/engine.py:182-214
Example rule: "All requests from JIRA_TRIGGER source → Agent #5 (Jira Bug Triager)"
Sources: orchestrator/core/routing/engine.py:182-214
Tier 2b: Trigger Subscriptions (Jira Trigger)
Purpose: Route Composio trigger events (specifically jira_trigger) based on explicit subscriptions.
When envelope.source == ChannelSource.JIRA_TRIGGER:
Resolve
workspace_id→entity_idviacomposio_entitiestableQuery
trigger_subscriptionsfor active subscription with matchingentity_id(and optionallytrigger_namefromenvelope.metadata)Return routing decision with
confidence=0.95
Database schema: trigger_subscriptions
id
int
Primary key
entity_id
int
Links to composio_entities.id
trigger_name
str
Composio trigger identifier (e.g., "JIRA_NEW_ISSUE")
agent_id
int
Agent to route to (nullable)
workflow_id
int
Workflow to route to (nullable)
is_active
bool
Enable/disable subscription
Implementation: orchestrator/core/routing/engine.py:220-278
Use case: Automate workflows when Jira issues are created, updated, or transitioned.
Sources: orchestrator/core/routing/engine.py:220-278
Tier 2c: Intent Classifier (Keyword Matching)
Purpose: Use lightweight keyword-based intent classification to match against routing rules.
IntentClassifier.classify(envelope.content)returns anIntentClassificationwith category and confidenceIf classification confidence < 0.4, skip this tier
Query
routing_rulesfor rules whereintent_keywordscontains the classified category (case-insensitive)First matching rule determines routing decision
Implementation: orchestrator/core/routing/engine.py:284-326
Example flow:
User message: "Can you help me analyze this sales data?"
IntentClassifier detects category: "data_analysis" (confidence 0.7)
Routing rule with
intent_keywords = ["data_analysis", "analytics"]matchesRoutes to Data Analyst agent
Sources: orchestrator/core/routing/engine.py:284-326, orchestrator/core/services/intent_classifier.py:1-100 (referenced but not shown)
Tier 3: LLM Classification
Purpose: Use workspace-configured LLM to classify requests when rule-based routing fails.
This is the most expensive tier (in terms of API cost and latency) and serves as the ultimate fallback when all other tiers produce no match.
LLM Classification Process
Sources: orchestrator/core/routing/engine.py:332-433
Agent Description Format
For each active agent in the workspace, the router constructs a description including:
agent_id: Integer ID for routingname: Human-readable agent namedescription: Agent's description field (or empty string)apps: List of Composio app names assigned viaagent_app_assignments
Implementation: orchestrator/core/routing/engine.py:435-458
Classification Prompt Structure
The prompt sent to the LLM follows this template (with optional override from PromptRegistry with slug "routing-classifier"):
Prompt customization: Administrators can override the default prompt via the system_prompts table (slug: routing-classifier). See System Prompt Management.
Implementation: orchestrator/core/routing/engine.py:460-493
Sources: orchestrator/core/routing/engine.py:460-493
Response Parsing
The router expects a JSON response from the LLM:
Parsing logic:
Strip markdown code fences (
```jsonand```) if presentParse JSON
Extract
agent_id(int) andconfidence(float, default 0.0)Validate
agent_idis in the workspace's active agent listClamp confidence to [0.0, 1.0]
Fallback: If parsing fails or agent_id is invalid, return (None, 0.0), which causes the router to store an UnroutedEvent.
Implementation: orchestrator/core/routing/engine.py:495-533
Sources: orchestrator/core/routing/engine.py:495-533
Confidence Threshold and Orchestration
The ROUTING_LLM_CONFIDENCE_THRESHOLD environment variable (default 0.5) controls the routing outcome:
>= 0.5
"agent"
Direct execution on classified agent
< 0.5
"orchestrate"
Low-confidence result triggers full task decomposition workflow
Orchestrate mode: Instead of directly executing on the agent, the system initiates a multi-step decomposition workflow that breaks the task into smaller subtasks, executes each, and synthesizes results. This ensures complex or ambiguous requests are handled correctly despite low routing confidence.
Implementation: orchestrator/core/routing/engine.py:390-410
Sources: orchestrator/core/routing/engine.py:390-410, orchestrator/config.py:144
Configuration
Environment Variables
ROUTING_CACHE_TTL_HOURS
int
24
Redis cache TTL for routing decisions
ROUTING_LLM_CONFIDENCE_THRESHOLD
float
0.5
Minimum confidence for direct agent routing (below triggers orchestration)
COMPOSIO_WEBHOOK_SECRET
str
-
Secret for verifying Composio webhook signatures
Configuration file: orchestrator/config.py:140-145
Workspace-Specific Configuration
Routing rules are workspace-scoped, allowing each workspace to define custom routing logic via the routing_rules table. Rules can be created, updated, and deleted via the Routing API (see API Endpoints).
Sources: orchestrator/config.py:140-145
Decision Logging and Observability
Routing Decision Records
Every routing decision (including cache hits) is logged to the routing_decisions table for audit and analytics.
Database schema: routing_decisions
id
int
Primary key
request_id
str
Links to RequestEnvelope.id
envelope_hash
str
SHA-256 hash of request content (first 16 chars)
workspace_id
UUID
Workspace context
source
str
Request source channel
content
str
First 2000 characters of request content
route_type
str
"agent", "workflow", or "orchestrate"
agent_id
int
Target agent (nullable)
workflow_id
int
Target workflow (nullable)
confidence
float
Routing confidence score
cached
bool
True if decision came from Tier 1 cache
created_at
DateTime
Timestamp
Implementation: orchestrator/core/routing/engine.py:561-585
Sources: orchestrator/core/routing/engine.py:561-585
Unrouted Events
When all routing tiers fail to produce a decision, the request is stored in the unrouted_events table for later analysis and rule creation.
Database schema: unrouted_events
id
int
Primary key
workspace_id
UUID
Workspace context
source
str
Request source channel
content
str
Full request content
raw_payload
JSON
Complete webhook/trigger payload
reason
str
Explanation (e.g., "All routing tiers exhausted")
created_at
DateTime
Timestamp
Use case: Administrators can query unrouted events to identify gaps in routing coverage and create new rules or train agents to handle previously unhandled request types.
Implementation: orchestrator/core/routing/engine.py:539-555
Sources: orchestrator/core/routing/engine.py:539-555
API Endpoints
The Routing API (mounted at /api/routing) provides CRUD operations for routing rules and observability endpoints for routing decisions.
Router registration: orchestrator/main.py:79, orchestrator/main.py:602
Key Endpoints
GET
/api/routing/rules
List routing rules for authenticated workspace
POST
/api/routing/rules
Create new routing rule
PUT
/api/routing/rules/{id}
Update routing rule
DELETE
/api/routing/rules/{id}
Delete routing rule
GET
/api/routing/decisions
Query routing decision logs
GET
/api/routing/unrouted
List unrouted events for analysis
API implementation: orchestrator/api/routing.py:1-500 (file referenced but not shown in provided files)
Sources: orchestrator/main.py:79, orchestrator/main.py:602
Integration with Agent Factory
After the UniversalRouter produces a RoutingDecision, the calling code (typically a chat endpoint or webhook handler) passes the decision to the AgentFactory for execution.
Execution flow:
AgentFactory integration: See Agent Lifecycle & Status for details on agent activation and execution.
Workflow execution: See Recipe Execution for workflow execution patterns.
Sources: orchestrator/core/routing/engine.py:78-144
Performance Characteristics
Routing Latency by Tier
Tier 0 (Override)
< 1 ms
0
Tier 1 (Cache)
1-5 ms
0
Tier 2a (Rules)
5-15 ms
0
Tier 2b (Trigger)
5-15 ms
0
Tier 2c (Intent)
10-50 ms
0
Tier 3 (LLM)
500-2000 ms
1 LLM call
Cost optimization strategy: The tiered architecture ensures 95%+ of requests are handled by Tiers 0-2 (zero LLM cost), with LLM classification reserved for novel or ambiguous requests.
Cache hit rate: In production workloads with repetitive request patterns, Tier 1 cache hit rates typically exceed 70%, further reducing Tier 3 invocations.
Sources: orchestrator/core/routing/engine.py:78-144
Error Handling and Fallbacks
LLM Classification Failures
When Tier 3 LLM classification fails (network error, timeout, invalid response), the router:
Logs the exception:
[router] Tier 3: LLM classification failedStores an
UnroutedEventwith reason: "All routing tiers exhausted (including LLM)"Returns
Noneto the caller
Caller responsibility: The calling code (e.g., chat endpoint) must handle None routing decisions by either:
Returning an error to the user
Falling back to a default agent
Queueing the request for manual review
Implementation: orchestrator/core/routing/engine.py:431-433
Redis Cache Unavailability
If Redis is unavailable, the RoutingCache gracefully degrades:
Tier 1 returns
Noneimmediately (cache miss)Router proceeds to Tier 2 without errors
Tier 3 LLM decisions are NOT cached (no crash)
Graceful degradation: All routing logic remains functional; only caching is disabled.
Sources: orchestrator/core/routing/engine.py:171-176, orchestrator/core/routing/cache.py:1-100 (referenced but not shown)
Future Enhancements
Planned Features
Multi-agent routing: Allow routing decisions to specify multiple agents for parallel execution
Confidence-based retries: Automatically retry LLM classification with different prompts when confidence is near threshold
A/B testing: Route requests to multiple agents and compare results to improve routing quality
Feedback loop: Learn from user corrections when routing decisions are wrong (click "This should have gone to Agent X")
Sources: Based on system architecture patterns
Last updated

