消息路由:Bindings 规则引擎与八级优先级匹配算法
第十章:消息路由:Bindings 规则引擎与八级优先级匹配算法
10.1 为什么需要确定性路由
10.1.1 多 Agent 多渠道场景的混乱问题
设想一个典型的企业 OpenClaw 部署:
渠道:
Discord 服务器 "Engineering"
├── #general(通用讨论)
├── #code-review(代码审查请求)
├── #ops-alerts(运维告警)
└── 私信(DM)
Agents:
agent_code_reviewer - 专门做代码审查
agent_ops_assistant - 专门处理运维问题
agent_general - 通用对话助手
agent_vip - VIP 用户专属助手(更好的模型)
如果没有确定性路由,每条消息可能被任意一个 Agent 处理:
- #code-review 频道的消息被 agent_general 处理(功能不匹配)
- VIP 用户的 DM 被 agent_general 处理(体验降级)
- 运维告警消息被 agent_code_reviewer 处理(语境错误)
更严重的问题:如果路由规则有歧义,同一条消息可能被多个 Agent 同时处理,导致重复回复、状态冲突。
10.1.2 确定性路由的三个保证
OpenClaw 的 Bindings 路由引擎提供三个核心保证:
- 唯一性:每条消息只路由给一个 Agent(即使多条规则匹配,也只选最高优先级)
- 确定性:相同的消息在相同的配置下,永远路由到相同的 Agent
- 可预测性:通过查看配置即可预测任意消息的路由结果,无需观察运行时行为
10.2 八级优先级匹配算法
Bindings 的优先级从高到低排列,匹配时从 Level 1 开始向下评估,一旦找到匹配即停止:
优先级层级(从高到低):
Level 1: Peer match(精确 DM / 群组 ID 匹配)
Level 2: Parent peer match(线程继承父消息的路由)
Level 3: Guild ID + 角色(Discord 服务器内的角色匹配)
Level 4: Guild ID(Discord 服务器级别匹配)
Level 5: Team ID(团队级别匹配)
Level 6: Account ID(账号级别匹配)
Level 7: Channel 级别(渠道类型匹配)
Level 8: 默认 Agent 回退(兜底路由)
10.2.1 Level 1:Peer Match(精确 ID 匹配)
匹配逻辑: 消息的来源方(peer)与 Binding 配置的精确 ID 完全相同。
peer 的定义:
- Discord DM:发送者的用户 ID(
user:123456789) - Discord 频道消息:频道 ID(
channel:987654321) - Slack DM:发送者的用户 ID
- API 调用:显式指定的 peer ID
配置示例:
bindings:
- name: "VIP Alice - Claude Opus"
match:
peer: "user:alice_discord_id_123" # 精确匹配 Alice 的 Discord ID
agent: agent_vip
priority: 1 # 显式标注,确保排在最前
- name: "Code Review Channel"
match:
peer: "channel:code_review_channel_id" # 精确匹配代码审查频道
agent: agent_code_reviewer
应用场景:
- 为特定 VIP 用户配置专属 Agent 和模型
- 为特定频道配置专门的 Agent(代码审查、客服等)
- 为特定机器人账号配置特殊处理逻辑
10.2.2 Level 2:Parent Peer Match(线程继承机制)
匹配逻辑: 消息是某个已有路由的消息的线程回复,继承父消息的路由结果。
为什么需要线程继承?
考虑以下场景:
Discord 对话:
[原始消息] Alice → #general:"帮我审查这段代码"
└─ 被路由到 agent_code_reviewer
[线程回复] Bob → #general(回复 Alice 的消息):"我也想让它帮我看看"
└─ 应该路由到哪里?
没有线程继承:Bob 的消息会走通用匹配,可能路由到 agent_general,而不是已经在处理这个代码审查对话的 agent_code_reviewer。
有线程继承:Bob 的消息识别出它是对 Alice 消息的回复,继承 Alice 消息的路由结果,也路由到 agent_code_reviewer。这保证了同一线程内的对话语境一致性。
实现机制:
function resolveParentPeerMatch(message: IncomingMessage): Binding | null {
// 查找父消息 ID
const parentMessageId = message.replyToId || message.threadParentId;
if (!parentMessageId) return null;
// 查找父消息的路由结果
const parentRouting = routingCache.get(parentMessageId);
if (!parentRouting) return null;
// 继承父消息的 Binding
return parentRouting.binding;
}
配置: 线程继承是默认行为,无需显式配置。如需禁用:
bindings:
- name: "General Channel"
match:
peer: "channel:general_id"
agent: agent_general
threadInheritance: false # 禁用线程继承
10.2.3 Level 3:Guild ID + 角色
匹配逻辑: 消息来自特定 Discord 服务器(Guild),且发送者具有特定角色。
配置示例:
bindings:
- name: "Engineering Team - Code Agent"
match:
guildId: "guild:my_company_discord"
roles:
- "role:engineer"
- "role:senior_engineer"
- "role:tech_lead"
agent: agent_code_reviewer
- name: "Management - Executive Agent"
match:
guildId: "guild:my_company_discord"
roles:
- "role:manager"
- "role:director"
- "role:vp"
agent: agent_executive_assistant
角色匹配策略:
rolesMatchMode: "any" # 发送者拥有列表中任意一个角色即匹配(默认)
rolesMatchMode: "all" # 发送者必须拥有所有列出的角色才匹配
Discord 应用场景: 这是 OpenClaw 在 Discord 生态中最重要的路由机制。一个企业 Discord 服务器可以通过角色实现精细的 Agent 分流:
Discord 服务器 "My Company"
角色: engineer → agent_code
角色: sales → agent_crm
角色: support → agent_helpdesk
角色: @everyone → agent_general(Level 4 的兜底)
10.2.4 Level 4:Guild ID(服务器级别)
匹配逻辑: 消息来自特定 Discord 服务器,不限制角色。
bindings:
- name: "Partner Company Discord"
match:
guildId: "guild:partner_company_discord"
agent: agent_partner_support
- name: "Internal Company Discord"
match:
guildId: "guild:my_company_discord"
agent: agent_internal_general
典型用途:
- 不同 Discord 服务器对应不同的 Agent 实例
- 合作伙伴社区与内部社区路由到不同 Agent
- 多租户部署:每个租户有独立的 Discord 服务器
10.2.5 Level 5:Team ID(团队级别)
匹配逻辑: 消息来源归属于特定 Team(OpenClaw 内部的团队概念,跨渠道组织单元)。
bindings:
- name: "Frontend Team"
match:
teamId: "team:frontend"
agent: agent_frontend
- name: "Backend Team"
match:
teamId: "team:backend"
agent: agent_backend
Team 的概念: Team 是 OpenClaw 中跨渠道的组织单元。一个用户可以属于多个 Team,Team 成员可以来自不同渠道(Discord、Slack、API)。
# config/teams.yaml
teams:
- id: team:frontend
name: "Frontend Engineering"
members:
- { type: "discord-user", id: "user:alice" }
- { type: "slack-user", id: "U01234567" }
- { type: "email", id: "[email protected]" }
10.2.6 Level 6:Account ID(账号级别)
匹配逻辑: 消息来源归属于特定账号(跨渠道身份关联后的统一账号)。
bindings:
- name: "Enterprise Customer Alice"
match:
accountId: "account:enterprise_alice"
agent: agent_enterprise
model: "claude-opus-4-5" # 更好的模型
- name: "Free Tier Users"
match:
accountId: "account:free_*" # 支持通配符
agent: agent_free
model: "claude-3-5-haiku" # 经济型模型
账号 vs 渠道身份的关系:
统一账号(Account)
├── Discord: user:123
├── Slack: U04567
└── Email: [email protected]
一个用户可能通过多个渠道联系 OpenClaw,Account ID 将这些身份统一,实现跨渠道一致的路由策略。
10.2.7 Level 7:Channel 级别(渠道类型匹配)
匹配逻辑: 根据消息的渠道类型(而非特定 ID)进行匹配。
bindings:
- name: "All Discord DMs"
match:
channel:
type: "discord-dm"
agent: agent_dm_assistant
- name: "All Slack DMs"
match:
channel:
type: "slack-dm"
agent: agent_slack_assistant
- name: "All API Calls"
match:
channel:
type: "api"
agent: agent_api
常见渠道类型:
discord-dm - Discord 私信
discord-channel - Discord 频道消息
discord-thread - Discord 线程
slack-dm - Slack 私信
slack-channel - Slack 频道
slack-thread - Slack 线程
api - REST/WebSocket API 调用
webhook - 外部 Webhook 触发
10.2.8 Level 8:默认 Agent 回退
匹配逻辑: 前 7 级均未匹配时的兜底路由。
bindings:
- name: "Default Fallback"
match: "*" # 匹配所有
agent: agent_general
isDefault: true # 标记为默认回退
如果没有配置默认 Agent:
{
"type": "res",
"ok": false,
"error": {
"code": "NO_ROUTE_FOUND",
"message": "No binding matched the incoming message and no default agent is configured",
"details": {
"messageId": "msg_001",
"source": { "channelType": "discord-dm", "userId": "user:unknown" }
}
}
}
最佳实践: 始终配置一个默认回退,避免路由失败让用户看到错误消息。
10.3 完整的路由匹配流程
收到新消息
│
▼
提取消息属性:
peer, parentId, guildId, roles[], teamId, accountId, channelType
│
▼
Level 1: Peer Match
│
├── 找到匹配的 Binding ──────────────→ 路由到对应 Agent,结束
│
└── 未找到
│
▼
Level 2: Parent Peer Match
│
├── 有父消息 & 父消息有路由记录 ──────→ 继承路由,结束
│
└── 无父消息或父消息无记录
│
▼
Level 3: Guild ID + Roles
│
├── 找到匹配(guildId AND role匹配) ─→ 路由到对应 Agent,结束
│
└── 未找到
│
▼
Level 4: Guild ID Only
│
├── 找到匹配(仅 guildId 匹配) ──────→ 路由到对应 Agent,结束
│
└── 未找到
│
▼
Level 5: Team ID
│
├── 找到匹配 ───────────────────────→ 路由到对应 Agent,结束
│
└── 未找到
│
▼
Level 6: Account ID
│
├── 找到匹配 ───────────────────────→ 路由到对应 Agent,结束
│
└── 未找到
│
▼
Level 7: Channel Type
│
├── 找到匹配 ───────────────────────→ 路由到对应 Agent,结束
│
└── 未找到
│
▼
Level 8: Default Fallback
│
├── 有默认 Agent ──────────────────→ 路由到默认 Agent,结束
│
└── 无默认 Agent ──────────────────→ 返回 NO_ROUTE_FOUND 错误
10.4 Bindings 配置示例
10.4.1 按渠道分流
# config/bindings.yaml
bindings:
# Discord 代码审查频道 → 代码审查 Agent
- name: "discord-code-review"
match:
peer: "channel:${DISCORD_CODE_REVIEW_CHANNEL_ID}"
agent: agent_code_reviewer
responseMode: "thread" # 在线程中回复,保持频道整洁
# Discord 运维告警频道 → 运维 Agent(静默模式,只在必要时回复)
- name: "discord-ops-alerts"
match:
peer: "channel:${DISCORD_OPS_CHANNEL_ID}"
agent: agent_ops
responseMode: "reaction-only" # 只用表情反应确认
# Discord 所有 DM → 通用助手
- name: "discord-dm-general"
match:
channel:
type: "discord-dm"
agent: agent_general
# API 调用 → 无状态 API Agent
- name: "api-requests"
match:
channel:
type: "api"
agent: agent_api
sessionMode: "ephemeral" # 不持久化会话
10.4.2 按账号分流(多层次服务)
bindings:
# 企业账号 → 高端 Agent + 最佳模型
- name: "enterprise-tier"
match:
accountId: "account:enterprise_*"
agent: agent_enterprise
model: "claude-opus-4-5"
rateLimit:
requestsPerHour: 500
# 专业账号 → 标准 Agent
- name: "pro-tier"
match:
accountId: "account:pro_*"
agent: agent_pro
model: "claude-3-5-sonnet"
rateLimit:
requestsPerHour: 100
# 免费账号 → 轻量 Agent + 速度优先模型
- name: "free-tier"
match:
accountId: "account:free_*"
agent: agent_free
model: "claude-3-5-haiku"
rateLimit:
requestsPerHour: 20
# 默认回退
- name: "default"
match: "*"
agent: agent_general
model: "claude-3-5-haiku"
10.4.3 多 Agent 的 Discord 场景
bindings:
# CTO 专属(最高优先级)
- name: "cto-personal"
match:
peer: "user:${CTO_DISCORD_ID}"
agent: agent_executive
model: "claude-opus-4-5"
# 工程角色(Level 3:guildId + role)
- name: "engineers-code-agent"
match:
guildId: "guild:${COMPANY_DISCORD}"
roles: ["role:engineer", "role:senior-engineer"]
agent: agent_code
# 产品角色
- name: "product-team"
match:
guildId: "guild:${COMPANY_DISCORD}"
roles: ["role:product-manager", "role:designer"]
agent: agent_product
# 公司 Discord 所有成员(Level 4:仅 guildId)
- name: "company-general"
match:
guildId: "guild:${COMPANY_DISCORD}"
agent: agent_company_general
# 公开 Discord 服务器
- name: "public-discord"
match:
guildId: "guild:${PUBLIC_COMMUNITY_DISCORD}"
agent: agent_community
# 默认回退
- name: "default"
match: "*"
agent: agent_general
10.5 Session Freshness 评估
路由到 Agent 后,系统需要决定是使用现有 Session 还是创建新 Session。这由 Session Freshness 评估决定。
10.5.1 Freshness 评估标准
function isSessionFresh(session: Session, config: FreshnessConfig): boolean {
const now = Date.now();
// 标准 1:空闲超时
const idleMs = now - session.lastActiveAt;
if (idleMs > config.idleTimeoutMs) {
return false; // 会话已超时
}
// 标准 2:每日重置(默认凌晨 4 点)
const sessionDate = new Date(session.lastActiveAt);
const currentDate = new Date(now);
const resetHour = config.dailyResetHour ?? 4; // 默认凌晨 4 点
// 判断是否跨越了今天的重置时间点
const sessionDateResetTime = new Date(sessionDate);
sessionDateResetTime.setHours(resetHour, 0, 0, 0);
if (sessionDateResetTime < sessionDate) {
// 重置时间在昨天,加一天
sessionDateResetTime.setDate(sessionDateResetTime.getDate() + 1);
}
if (now > sessionDateResetTime.getTime()) {
return false; // 已经过了今天的重置时间
}
return true; // 会话仍然新鲜
}
10.5.2 Freshness 配置
# config/sessions.yaml
sessionFreshness:
idleTimeoutMs: 3600000 # 1 小时空闲超时(毫秒)
dailyResetHour: 4 # 每天凌晨 4 点重置
# 按 Agent 覆盖
agentOverrides:
agent_code:
idleTimeoutMs: 7200000 # 代码任务允许 2 小时空闲
agent_general:
idleTimeoutMs: 1800000 # 通用对话 30 分钟空闲超时
10.5.3 Freshness 失效后的处理
Session Freshness 评估:
现有 Session 存在?
│
是 ──→ Session 是否 fresh?
│ │
│ 是 ──→ 复用现有 Session(携带历史上下文)
│ │
│ 否 ──→ 归档旧 Session,创建新 Session
│
否 ──→ 创建新 Session
归档的会话保留在 data/transcripts/ 中,用户可以通过 TUI 浏览历史会话。
10.6 路由失败的处理
10.6.1 无匹配规则
{
"type": "event",
"event": "session.error",
"payload": {
"code": "NO_ROUTE_FOUND",
"message": "No binding matched the incoming message",
"suggestions": [
"Check if a default binding is configured",
"Verify the channel ID is correct in your bindings config"
]
}
}
10.6.2 Agent 不可用
{
"type": "event",
"event": "session.error",
"payload": {
"code": "AGENT_UNAVAILABLE",
"message": "The matched agent 'agent_code' is not running",
"fallbackAgent": "agent_general",
"usedFallback": true
}
}
10.6.3 路由调试工具
# 模拟路由决策(不实际发送消息)
openclaw route simulate \
--peer "user:alice_discord_123" \
--guild "guild:company_discord" \
--roles "engineer,senior-engineer" \
--channel-type "discord-dm"
# 输出:
# Level 1 (Peer Match): agent_vip ← 匹配!停止评估
# Final Route: agent_vip
# Session: sess_01HXYZ (fresh, last active 5 minutes ago)
# 查看所有 Binding 的匹配统计
openclaw bindings stats --period 24h
10.7 Bindings 的高级特性
10.7.1 条件路由(Context-based Routing)
bindings:
- name: "Code Review - Working Hours"
match:
peer: "channel:code_review_id"
conditions:
# 只在工作时间(9-18点)路由到专业 Agent
- timeRange:
start: "09:00"
end: "18:00"
timezone: "Asia/Shanghai"
agent: agent_code_reviewer
- name: "Code Review - After Hours"
match:
peer: "channel:code_review_id"
# 下班后路由到轻量模型
agent: agent_code_reviewer_lite
model: "claude-3-5-haiku"
10.7.2 负载均衡路由
bindings:
- name: "Load Balanced Agents"
match:
channel:
type: "api"
agents:
- agent: agent_api_1
weight: 50
- agent: agent_api_2
weight: 50
loadBalancing: "weighted-random"
10.7.3 路由优先级数值化
当多个 Level 相同的 Binding 同时匹配时,通过 priority 数值决定最终选择:
bindings:
- name: "Alice VIP"
match:
peer: "user:alice"
agent: agent_vip
priority: 100 # 越高越优先(同 Level 内)
- name: "Alice Normal"
match:
peer: "user:alice"
agent: agent_general
priority: 10 # 这条永远不会被选中(Alice 会被 priority:100 的规则匹配)
本章小结
Bindings 规则引擎是 OpenClaw 多 Agent 编排能力的基础:
- 确定性路由保证每条消息只路由给一个 Agent,消除多 Agent 竞争和重复响应
- 8 级优先级算法 从精确到模糊逐级降级,覆盖所有可能的路由场景
- Level 1(Peer Match) 用于 VIP 用户和特定频道的精确路由
- Level 2(Parent Peer Match) 通过线程继承保证对话语境的一致性
- Level 3(Guild ID + 角色) 是 Discord 场景中最重要的路由维度
- Level 4-6(Guild/Team/Account) 提供从服务器到团队到个人的分层路由
- Level 7(Channel 类型) 按渠道形态(DM/频道/API)分流
- Level 8(默认回退) 兜底防止路由失败
- Session Freshness 通过空闲超时和每日重置决定是复用现有会话还是创建新会话
- 路由失败处理 包括无匹配错误、Agent 不可用回退、调试工具支持
至此,《OpenClaw 完全指南》第六章至第十章已完整呈现了从 Gateway 控制平面、命令队列、Pi 框架到消息路由的完整技术链路。这五章构成了理解 OpenClaw 架构的核心知识骨干。