Routing
Routing happens at the agent layer. Each agent points at exactly one provider; channels point at agents.
Two layers of decisions:
- Per-call backend selection — “use the cheap model unless this prompt looks like reasoning.” Each routing target is its own
[agents.<alias>]entry with its ownmodel_provider. Channels are routed to whichever agent should handle their traffic. - Provider reliability — vendor-redundancy lives behind a single first-class provider. Configure OpenRouter (or an equivalent) as one provider and let it handle vendor fan-out at its endpoint.
Per-agent dispatch
Define each routing target as its own agent, then point channels at the agent that should handle their traffic.
[providers.models.anthropic.sonnet]
model = "claude-sonnet-4-6"
api_key = "sk-ant-..."
[providers.models.anthropic.haiku]
model = "claude-haiku-4-5-20251001"
api_key = "sk-ant-..."
[providers.models.deepseek.reasoner]
model = "deepseek-reasoner"
api_key = "sk-..."
[providers.models.gemini.vision]
model = "gemini-2.5-pro"
api_key = "..."
[channels.telegram.home]
bot_token = "..."
[channels.slack.engineering]
bot_token = "..."
[channels.slack.research]
bot_token = "..."
[channels.discord.media]
bot_token = "..."
[agents.fast]
model_provider = "anthropic.haiku"
risk_profile = "hardened"
runtime_profile = "tight" # snappy public replies
channels = ["telegram.home"]
[agents.deep]
model_provider = "anthropic.sonnet"
risk_profile = "hardened"
runtime_profile = "deep" # extended engineering tasks
channels = ["slack.engineering"]
[agents.reasoner]
model_provider = "deepseek.reasoner"
risk_profile = "hardened"
runtime_profile = "deep" # research-style reasoning chains
channels = ["slack.research"]
[agents.eyes]
model_provider = "gemini.vision"
risk_profile = "hardened"
runtime_profile = "tight" # quick image-bearing replies
channels = ["discord.media"]
[risk_profiles.hardened]
level = "supervised"
workspace_only = true
require_approval_for_medium_risk = true
block_high_risk_commands = true
[runtime_profiles.tight]
max_tool_iterations = 5
max_actions_per_hour = 30
[runtime_profiles.deep]
max_tool_iterations = 50
max_actions_per_hour = 200
Each channel binds to one agent. Channels move between agents by editing channels = [...] on the agent that should pick them up; Config::validate() makes sure references resolve.
For ad-hoc multi-step routing inside a single conversation, the spawn_subagent tool lets an agent run an ephemeral child under its own identity. The child inherits the parent’s permissions envelope (see [risk_profiles.<alias>].allowed_tools) and returns its final response to the parent’s tool loop.
Hint-based model routes
A narrower mechanism: [[model_routes]] lets an agent override the configured model_provider for prompts marked with a hint string. Useful when one agent should occasionally reach for a different model without spinning up a second agent.
[[model_routes]]
hint = "reasoning"
model_provider = "deepseek"
model = "deepseek-reasoner"
Routes only fire when a prompt explicitly carries the matching hint. The default request path uses the agent’s primary model_provider.
可观测性
Per-agent dispatch decisions are visible in tracing logs:
INFO channel=telegram.home routed to agent=fast
INFO agent=fast model_provider=anthropic.haiku turn_id=...
INFO model_provider=anthropic.haiku stream complete tokens={input=512, output=128}
对于生产部署,将日志输出连接到 Loki / Grafana。请参阅 运维 → 日志与可观测性。
另见
- Overview — provider model and per-agent dispatch
- 配置 — 完整的
[providers.*]模式 - Provider catalog — every canonical slot