docs/guides/routing-guide.md
Back to README
In PicoClaw, routing has two user-facing parts:
This guide explains how to configure both for real deployments.
{
"agents": {
"list": [
{ "id": "main", "default": true },
{ "id": "support" }
],
"dispatch": {
"rules": [
{
"name": "telegram support group",
"agent": "support",
"when": {
"channel": "telegram",
"chat": "group:-1001234567890"
}
}
]
}
}
}
{
"agents": {
"list": [
{ "id": "main", "default": true },
{ "id": "support" }
],
"dispatch": {
"rules": [
{
"name": "slack mentions",
"agent": "support",
"when": {
"channel": "slack",
"space": "workspace:t001",
"mentioned": true
}
}
]
}
}
}
{
"model_list": [
{
"model_name": "gpt-main",
"provider": "openai",
"model": "gpt-5.4",
"api_keys": ["sk-main"]
},
{
"model_name": "flash-light",
"provider": "gemini",
"model": "gemini-2.0-flash-exp",
"api_keys": ["sk-light"]
}
],
"agents": {
"defaults": {
"model_name": "gpt-main",
"routing": {
"enabled": true,
"light_model": "flash-light",
"threshold": 0.35
}
}
}
}
Agent routing is configured with:
agents.dispatch.rules
Rules are evaluated from top to bottom. The first matching rule wins. If no rule matches, PicoClaw falls back to the default agent.
| Field | Meaning | Example |
|---|---|---|
channel | Channel name | telegram, slack, discord |
account | Normalized account ID | default, bot2 |
space | Workspace, guild, or similar container | workspace:t001, guild:123456 |
chat | Direct chat, group, or channel | direct:user123, group:-100123, channel:c123 |
topic | Thread or topic | topic:42 |
sender | Normalized sender identity | 12345, john |
mentioned | Whether the bot was explicitly mentioned | true |
Values must match the normalized runtime shape, not the raw incoming payload.
Put more specific rules before broader rules.
Good:
Bad:
In the bad ordering, the broad rule wins first and the VIP rule never runs.
Routing and sessions are related but different.
You can override the global session.dimensions value for one matched rule with session_dimensions.
Example:
{
"agents": {
"list": [
{ "id": "main", "default": true },
{ "id": "support" },
{ "id": "sales" }
],
"dispatch": {
"rules": [
{
"name": "vip in support group",
"agent": "sales",
"when": {
"channel": "telegram",
"chat": "group:-1001234567890",
"sender": "12345"
},
"session_dimensions": ["chat", "sender"]
},
{
"name": "support group",
"agent": "support",
"when": {
"channel": "telegram",
"chat": "group:-1001234567890"
},
"session_dimensions": ["chat"]
}
]
}
},
"session": {
"dimensions": ["chat"]
}
}
In this configuration:
salessupportsession.identity_links also affects routing when you match on sender.
Use it when the same real user may appear under multiple raw sender IDs.
Example:
{
"session": {
"identity_links": {
"john": ["slack:u123", "legacy-user-42"]
}
},
"agents": {
"dispatch": {
"rules": [
{
"name": "john goes to sales",
"agent": "sales",
"when": {
"sender": "john"
}
}
]
}
}
}
Model routing is configured under:
agents.defaults.routing
Current fields:
| Field | Meaning |
|---|---|
enabled | Turn model routing on or off |
light_model | model_name from model_list used for simple turns |
threshold | Complexity cutoff in [0, 1] |
Important behavior:
model_listThe current model router looks at structural signals such as:
This means a "simple" turn may still go to the primary model if it includes:
Recommended starting point:
{
"agents": {
"defaults": {
"routing": {
"enabled": true,
"light_model": "flash-light",
"threshold": 0.35
}
}
}
}
General rule:
Practical suggestions:
0.25 if you want safer routing with fewer light-model turns0.35 as the default starting point0.50+ only if your light model is already strong enough for most chat trafficCheck:
group:-100123 instead of just -100123space, topic, or mentionedThe most common cause is ordering. Remember: first match wins.
Check:
agents.defaults.routing.enabled is truelight_model exists in model_listThat can still happen when the turn includes:
Adjust session.dimensions globally or session_dimensions on the specific route.
Routing chooses the agent, but sessions decide context sharing.