picoclaw

Routing Guide

Back to README

In PicoClaw, routing has two user-facing parts:

This guide explains how to configure both for real deployments.

Quick Start

Route one Telegram group to a support agent

{
  "agents": {
    "list": [
      { "id": "main", "default": true },
      { "id": "support" }
    ],
    "dispatch": {
      "rules": [
        {
          "name": "telegram support group",
          "agent": "support",
          "when": {
            "channel": "telegram",
            "chat": "group:-1001234567890"
          }
        }
      ]
    }
  }
}

Route only Slack mentions in one workspace

{
  "agents": {
    "list": [
      { "id": "main", "default": true },
      { "id": "support" }
    ],
    "dispatch": {
      "rules": [
        {
          "name": "slack mentions",
          "agent": "support",
          "when": {
            "channel": "slack",
            "space": "workspace:t001",
            "mentioned": true
          }
        }
      ]
    }
  }
}

Use a light model for simple turns

{
  "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

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.

Supported Match Fields

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.

Rule Ordering

Put more specific rules before broader rules.

Good:

  1. VIP sender inside one group
  2. all traffic for that group
  3. channel-wide fallback

Bad:

  1. all traffic for that group
  2. VIP sender inside the same group

In the bad ordering, the broad rule wins first and the VIP rule never runs.

Session Interaction

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:

session.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

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:

What Affects The Complexity Score

The current model router looks at structural signals such as:

This means a “simple” turn may still go to the primary model if it includes:

Choosing A Threshold

Recommended starting point:

{
  "agents": {
    "defaults": {
      "routing": {
        "enabled": true,
        "light_model": "flash-light",
        "threshold": 0.35
      }
    }
  }
}

General rule:

Practical suggestions:

Troubleshooting

A rule is not matching

Check:

The wrong agent handles a message

The most common cause is ordering. Remember: first match wins.

The light model is never used

Check:

The primary model is still chosen for short messages

That can still happen when the turn includes:

Routing works, but the conversation memory is still too shared

Adjust session.dimensions globally or session_dimensions on the specific route. Routing chooses the agent, but sessions decide context sharing.