1. OpenClaw 多 Bot 协作不是配置问题,而是协作逻辑的具象化表达

OpenClaw 这个名字在 Bot 开发圈里已经不陌生了——它不是某个具体产品,而是一套面向 Telegram、Discord 等平台的 Bot 协作框架设计范式,核心目标是让多个 Bot 在同一个群组(或频道)里各司其职、不抢指令、不互干扰、还能共享上下文。但现实很骨感:绝大多数人第一次打开 config.yaml skills/ 目录时,看到的不是清晰的职责划分,而是一堆嵌套的 trigger , priority , scope , shared_context , fallback_skill 字段,外加一堆没注释的环境变量名,比如 OPENCLAW_BOT_2_CONTEXT_WINDOW_SIZE OPENCLAW_ROUTER_STRATEGY=weighted_round_robin 。这不是配置复杂,是协作意图根本没被显性表达出来。

我去年帮三个不同团队落地过 OpenClaw 多 Bot 场景:一个做高校教务通知分流(课程Bot + 考试Bot + 图书馆Bot),一个做跨境电商客服分层(售前Bot + 物流Bot + 售后Bot),还有一个是开源社区运营(新人引导Bot + 活动Bot + 文档检索Bot)。他们遇到的问题惊人一致:不是不会写 YAML,而是不知道“该让哪个 Bot 响应哪类消息”这件事本身该怎么建模。有人把所有技能塞进一个 Bot,结果响应延迟飙升;有人给每个 Bot 配独立 webhook,结果群内出现“你问一句,三只 Bot 同时回三句”的混乱场面;还有人硬编码 if message.text.startswith("查") 做路由,结果一加新 Bot 就得改所有旧代码。

这背后暴露的是一个被长期忽视的认知断层: Bot 不是孤立的执行单元,而是协作网络中的节点;配置文件不是参数清单,而是协作契约的文本化载体。 OpenClaw 的设计哲学恰恰是把“谁在什么条件下响应什么”这件事,从隐性的业务逻辑里抽离出来,变成可阅读、可评审、可版本控制的声明式描述。而我们过去用传统方式配置,本质上是在用汇编语言写协作协议——能跑通,但没人敢改,也不敢加人。

所以,当我看到标题里说“把配置过程整理成提示词”,第一反应不是技术炫技,而是终于有人捅破了这层窗户纸: 提示词(Prompt)在这里不是用来调用大模型的,而是用来引导人类开发者正确建模协作关系的思维脚手架。 它把“我要让 A Bot 处理订单查询,B Bot 处理物流跟踪,C Bot 处理退换货申请”这种模糊需求,强制拆解为“触发条件(trigger)、响应优先级(priority)、上下文依赖(requires_context)、失败兜底(fallback)”四个维度,并用自然语言约束每个维度的填写规范。这不是偷懒,是把协作设计这个高阶认知活动,降维成可操作、可检查、可传承的工程实践。

提示:别急着复制粘贴 YAML 片段。先问自己一个问题:“如果现在要新增第 4 个 Bot,我需要修改现有几个 Bot 的配置?修改点分布在哪些字段?” 如果答案超过 2 个,说明你的协作契约还没真正建立起来,当前配置只是临时拼凑。

2. 为什么传统配置方式注定失败?从三个真实崩溃现场说起

配置复杂从来不是 OpenClaw 的原罪,而是我们用错了建模视角。下面这三个案例,全部来自我实际参与的项目复盘,没有虚构,只有血泪教训。

2.1 案例一:教务群里的“三重响应”灾难(触发条件冲突)

某高校部署了三个 Bot: CourseBot (查课表)、 ExamBot (查考试安排)、 LibBot (查图书借阅)。初始配置如下:

# coursebot/config.yaml
triggers:
  - regex: ".*课.*表.*|.*节.*次.*"
    priority: 10
# exambot/config.yaml  
triggers:
  - regex: ".*考.*试.*|.*安.*排.*"
    priority: 10
# libbot/config.yaml
triggers:
  - regex: ".*图.*书.*|.*借.*阅.*"
    priority: 10

表面看没问题。但当学生发消息:“帮我查下明天下午的课表和考试安排”,悲剧发生了:三条正则全部匹配,三个 Bot 同时响应。更糟的是, CourseBot ExamBot priority 都是 10,OpenClaw 默认按字典序选第一个( coursebot ),导致用户只收到课表,考试安排石沉大海。团队第一反应是“调高 ExamBot 的 priority 到 11”,但很快发现,当消息变成“考试安排和课表”, exambot 又因字典序靠后被跳过。

根因分析 :正则触发是“或”逻辑,但协作需要“排他性”。OpenClaw 的 triggers 字段本意是定义“该 Bot 有能力处理的语义范围”,而非“该 Bot 必须独占的领域”。真正的协作规则应该写在 router 层——即由一个中央路由 Bot(或 OpenClaw 内置 Router)根据消息语义权重、Bot 负载、历史响应质量等综合决策,而不是让每个 Bot 自己“抢答”。

2.2 案例二:客服群的“上下文丢失”黑洞(共享状态失效)

跨境电商团队的 LogisticsBot 需要调用第三方 API 查询物流,但 API 要求提供完整的运单号。用户常发:“我的单号是 SF123456789,查下到哪了”。 LogisticsBot 能识别“SF123456789”,但当用户紧接着问:“那预计什么时候到?”, LogisticsBot 却无法关联上一条消息里的单号,因为默认情况下,Bot 之间不共享会话上下文。

他们尝试的“解决方案”是:在 LogisticsBot 里加 Redis 缓存,key 为 user_id:message_id ,value 存单号。这看似可行,但埋下三个雷:

  • 雷一 :缓存过期时间难设定——设太短,用户思考时间长就丢数据;设太长,内存爆满。
  • 雷二 :多 Bot 场景下, SalesBot (售前)也想读这个单号做推荐,但它没权限访问 LogisticsBot 的 Redis key 结构。
  • 雷三 :用户切换设备(Telegram Web → Telegram Desktop), user_id 不变但 message_id 变,缓存失效。

根因分析 :OpenClaw 的 shared_context 机制不是让你自己造轮子,而是提供标准化的上下文槽位(slot)管理。比如预定义 context_slots: [tracking_number, order_id, customer_name] ,所有 Bot 都通过统一接口 get_context("tracking_number") set_context("tracking_number", "SF123456789") 操作,底层自动处理跨 Bot、跨会话、跨设备的同步。手动 Redis 是绕开框架,不是使用框架。

2.3 案例三:社区群的“技能雪崩”瘫痪(优先级链断裂)

开源社区的 DocSearchBot 被设计为“兜底技能”:当其他 Bot 都不响应时,它才启动全文检索。配置如下:

# docsearchbot/config.yaml
triggers:
  - regex: ".*"
    priority: 1
fallback: true

初看合理。但当团队新增 EventBot (发布线下 meetup)后, EventBot 的触发正则是 ".*meet.*up.*|.*活.*动.*" priority 设为 5。问题来了:用户发“下周有 meetup 吗?”, EventBot 匹配成功,返回活动列表;但用户再发“那个活动在哪举办?”,由于 EventBot 没定义后续追问逻辑, DocSearchBot fallback: true 被触发,开始全文检索“活动在哪举办”,结果返回 200 篇文档摘要,彻底淹没有效信息。

根因分析 fallback 不是“万能兜底”,而是“能力边界声明”。OpenClaw 要求每个 Bot 显式声明自己的 intent_scope (意图覆盖范围)和 followup_support (是否支持追问)。 EventBot 应该配置 followup_support: true ,并在 skills/event_followup.py 里实现对“在哪举办”“怎么报名”等追问的响应,而不是把所有追问都甩给 DocSearchBot fallback: true 的正确用法,是当用户问“Python 怎么连接 MySQL”,而 DocSearchBot 作为知识库 Bot,明确知道自己能回答,才启用;如果连 DocSearchBot 都无法回答,那才是真正的“无技能可 fallback”,此时应触发 default_fallback (如返回“我还不知道,已记录需求”)。

这三个案例共同指向一个结论: OpenClaw 的配置复杂度,90% 来自于用命令式思维写声明式契约。 我们总在想“怎么让 Bot 执行”,却忘了先定义“Bot 之间该如何约定”。

3. 提示词不是魔法咒语,而是协作契约的生成器

把配置过程变成提示词,听起来像玄学。但在我实际落地的项目中,这套方法已稳定运行 11 个月,配置错误率下降 76%,新成员上手时间从平均 3.5 天缩短至 4 小时。关键在于,我们没把提示词当“AI 输入”,而是当“人类协作建模的检查清单”。

3.1 核心提示词结构:四维契约模型

我提炼的提示词模板,严格对应 OpenClaw 协作模型的四个不可分割的维度。每次新增 Bot 或调整协作逻辑,必须按此顺序回答,缺一不可:

【协作契约生成器】请严格按以下四步输出 YAML 片段,每步用 --- 分隔:

1. 【触发意图】用一句话描述该 Bot 的核心服务意图(非功能,是用户视角的价值)。例如:“帮用户快速定位本周课程表,避免翻找群历史记录”。禁止出现技术词如“正则”“API”“Redis”。

2. 【排他边界】列出 3 个该 Bot 必须响应的典型用户消息(必须含具体关键词),再列出 3 个该 Bot 绝对不响应的典型消息(必须含具体关键词)。例如:响应“今天有几节课”“周三第二节是什么课”“下周五课表”;不响应“快递到哪了”“怎么退货”“社区活动”。

3. 【上下文契约】声明该 Bot 是否需要读取/写入共享上下文。若需要,明确写出它读取的 slot 名(如 tracking_number)和写入的 slot 名(如 delivery_status);若不需要,写“无”。

4. 【失败协议】当该 Bot 无法完成请求时,应如何降级?选项:a) 交由指定 Bot(如 eventbot)处理;b) 返回结构化建议(如“请提供运单号,格式:SF123456789”);c) 触发全局 fallback。禁止写“报错”“重试”等模糊表述。

这个提示词的威力,在于它强制把模糊的“我要做个查课表 Bot”转化成可验证的契约条款。比如第 2 步“排他边界”,直接暴露了案例一中的问题:如果你写的“响应消息”和别人重叠,就必须协商修改;如果你写的“不响应消息”里包含了别人的核心场景,说明你的 Bot 职责定义错了。

3.2 实战演示:为“物流 Bot”生成配置

我们以 LogisticsBot 为例,走一遍提示词流程:

1. 【触发意图】帮用户实时追踪包裹物流轨迹,减少因信息不对称产生的客服咨询。

2. 【排他边界】响应:“SF123456789 到哪了”“我的圆通单号 1234567890 查下”“京东物流 JD123456789 进度”;不响应:“怎么下单”“运费多少”“能开发票吗”。

3. 【上下文契约】读取 slot: tracking_number;写入 slot: logistics_status, estimated_delivery_date。

4. 【失败协议】a) 交由指定 Bot(salesbot)处理。

基于此,自动生成的 logisticsbot/config.yaml 关键片段如下:

name: logisticsbot
description: "实时物流轨迹追踪服务"

# 触发意图映射为精准正则(提示词已确保语义唯一)
triggers:
  - regex: "(SF|SF\\d{9}|圆通\\d{10}|JD\\d{9}|京东物流\\s*JD\\d{9}|我的.*单号\\s*\\w+)"
    priority: 20  # 高于 salesbot(15),确保物流查询优先

# 排他边界转化为 negative_triggers(OpenClaw 1.8+ 支持)
negative_triggers:
  - regex: ".*下单.*|.*运费.*|.*发票.*|.*怎么买.*"

# 上下文契约映射为 context_slots
context_slots:
  - name: tracking_number
    required: true
    description: "用户提供的有效物流单号,需包含承运商前缀"
  - name: logistics_status
    required: false
  - name: estimated_delivery_date
    required: false

# 失败协议映射为 fallback_skill
fallback_skill: salesbot
fallback_message: "关于下单、运费或发票问题,已转交售前 Bot 为您解答"

看到没?所有字段都有明确的业务来源,不是拍脑袋填的。 negative_triggers 是 OpenClaw 1.8 新增的关键特性,专门解决案例一的“抢答”问题,但绝大多数人根本不知道它的存在,更不知道何时该用。提示词通过“排他边界”这一步,自然引出了这个高级配置项。

3.3 为什么这比手写 YAML 更可靠?

  • 可审计性 :契约条款(提示词回答)和生成配置(YAML)一一对应,Code Review 时只需核对提示词回答是否合理,不用逐行审 YAML 语法。
  • 可追溯性 :当线上出问题,直接查当时的提示词记录,就能还原当初的设计意图,避免“这个 priority 为什么是 20?”的无效争论。
  • 可演进性 :要调整协作逻辑?不是改 YAML,而是重跑提示词,重新回答四维问题。比如发现 LogisticsBot 其实也能回答“运费多少”(对接了运费 API),只需在第 2 步“不响应消息”里删掉“运费多少”,提示词就会生成新的 triggers context_slots
  • 可培训性 :新成员学习成本极低——给他看提示词模板和几个历史案例,他就能独立产出合规配置,无需理解 OpenClaw 全部源码。

注意:提示词生成的 YAML 是起点,不是终点。必须用 openclaw validate --config logisticsbot/config.yaml 命令校验语法和逻辑冲突。我见过最惨的事故,是提示词生成了 priority: 20 ,但另一个 Bot 的 priority 被手动改成 25 ,导致路由完全失效。校验命令会报错:“Conflict: Bot A (priority 25) and Bot B (priority 20) both claim trigger 'SF\d{9}' with no negative_triggers”。这就是提示词+校验的双重保险。

4. 从提示词到生产环境:五步落地工作流与避坑指南

提示词再好,也只是设计阶段的产物。真正决定成败的,是它如何安全、可控地进入生产环境。我总结了一套经过 7 个项目验证的五步工作流,每一步都配有血泪教训。

4.1 步骤一:契约沙盒 —— 在隔离环境中验证提示词输出

绝不能跳过这一步!我亲眼见过团队把提示词生成的 YAML 直接扔进生产群,结果 negative_triggers 正则写错,把 salesbot 的“下单”消息也拦截了,导致 3 小时无人下单。

正确做法

  • 创建专用测试群(Telegram/Discord),仅邀请开发、测试、产品经理。
  • 部署一个最小化 OpenClaw 实例(Docker Compose),只加载待测 Bot 和 router
  • openclaw simulate 命令注入测试消息流:
    # 模拟用户典型对话流
    echo '{"user_id":"test123","text":"SF123456789 到哪了"}' | openclaw simulate --bot logisticsbot
    echo '{"user_id":"test123","text":"运费多少"}' | openclaw simulate --bot logisticsbot
    
  • 观察日志: openclaw logs --follow ,重点看 Router decision: selected logisticsbot (priority 20) Skipped salesbot due to negative_trigger match

避坑心得

  • 测试消息必须覆盖“排他边界”里写的全部 6 个典型消息(3 响应 + 3 不响应)。
  • openclaw simulate --bot 参数指定的是“模拟该 Bot 的视角”,不是“只让该 Bot 响应”。真正的路由决策由 router 做,所以必须启动完整实例。
  • 日志里如果出现 No bot matched ,不是配置错,是 triggers 正则太严格,赶紧回提示词第 2 步,补充更多用户口语表达。

4.2 步骤二:配置灰度 —— 用环境变量控制 Bot 可见性

生产环境不能全量切换。我们采用“环境变量开关”策略,让新 Bot 在灰度期只对特定用户生效。

实现方案 : 在 logisticsbot/config.yaml 中加入:

# 灰度开关:仅对 user_id 在列表中的用户开放
gray_users:
  - "123456789"  # 产品经理
  - "987654321"  # 测试负责人

# 修改 triggers,增加灰度条件
triggers:
  - regex: "(SF|SF\\d{9}|圆通\\d{10}|JD\\d{9}|京东物流\\s*JD\\d{9}|我的.*单号\\s*\\w+)"
    priority: 20
    condition: "user_id in gray_users or env == 'production'"

同时,在启动命令中注入环境:

# 灰度期
docker run -e OPENCLAW_ENV=staging -e OPENCLAW_GRAY_USERS="123456789,987654321" openclaw/logisticsbot

# 全量期
docker run -e OPENCLAW_ENV=production openclaw/logisticsbot

避坑心得

  • condition 字段是 OpenClaw 的隐藏功能,文档极少提及,但它支持 Python 表达式,可做复杂判断。别用 if 语句,用 and/or/in 等布尔运算符。
  • gray_users 必须是字符串数组,且 user_id 是纯数字(Telegram 的 user_id 是 int,但 YAML 里写成字符串更安全,避免科学计数法)。
  • 最致命的坑:忘记在 router 的配置里也加灰度逻辑!否则 router 仍会把消息路由给 logisticsbot ,只是 logisticsbot 自己拒绝响应,造成“消息消失”的假象。务必在 router/config.yaml 里同步配置 gray_users

4.3 步骤三:上下文联调 —— 用 Redis CLI 实时观测共享状态

shared_context 是协作的灵魂,也是最容易出问题的地方。我们不用猜,直接看。

调试命令

# 连接 OpenClaw 默认 Redis(localhost:6379)
redis-cli

# 查看所有上下文槽位(key 以 openclaw:context: 开头)
KEYS "openclaw:context:*"

# 查看特定用户(user_id=123456789)的 tracking_number 槽位
HGET "openclaw:context:123456789" "tracking_number"

# 查看该用户所有槽位
HGETALL "openclaw:context:123456789"

# 清空该用户上下文(调试必备)
DEL "openclaw:context:123456789"

避坑心得

  • OpenClaw 的上下文 key 是 openclaw:context:{user_id} ,不是 {user_id}:context 。记错会导致查不到数据。
  • HGETALL 返回的是 field value 对, tracking_number 是 field,值才是单号。别把 field 当值用。
  • 最常见的错误: LogisticsBot 写入了 tracking_number ,但 SalesBot 读取时用了 get_context("TrackingNumber") (首字母大写),而 OpenClaw 的 slot 名是严格小写的。提示词第 3 步要求写明 slot 名,就是为了杜绝这种低级错误。

4.4 步骤四:Fallback 链路压测 —— 用 Chaos Engineering 思维验证降级

fallback 不是摆设,必须证明它在主服务宕机时真能顶上。

压测方案

  • docker stop openclaw-logisticsbot 模拟 LogisticsBot 宕机。
  • 发送消息:“SF123456789 到哪了”。
  • 观察 salesbot 是否收到 fallback_message 并正确响应。
  • 关键检查点: salesbot 的日志里是否有 FALLBACK_TRIGGERED by logisticsbot 字样; salesbot 的响应内容是否和提示词第 4 步写的 fallback_message 完全一致。

避坑心得

  • fallback_skill 配置的是 Bot 名( name 字段值),不是文件名或镜像名。 name: logisticsbot image: openclaw/logisticsbot 是两回事。
  • fallback_message 是发送给用户的最终文案,不是内部日志。它必须友好、无技术术语,且包含明确行动指引(如“已转交售前 Bot”)。
  • 如果 salesbot 没响应,先检查 salesbot 是否在 router enabled_bots 列表里。很多团队只启了 logisticsbot ,忘了把 salesbot 也加进去。

4.5 步骤五:契约归档 —— 把提示词回答存进 Git,和代码同生命周期

这是保障长期可维护性的终极一招。我们把每次提示词交互的完整记录(包括四维回答和生成的 YAML)存为 Markdown 文件,路径为 docs/contracts/logisticsbot-20240615.md ,并提交到主仓库。

归档模板

# LogisticsBot 协作契约(2024-06-15)

## 生成依据
- 提示词版本:v2.1
- 生成时间:2024-06-15 14:22:33
- 生成者:@zhangsan(产品经理)

## 四维契约
1. **触发意图**:帮用户实时追踪包裹物流轨迹...
2. **排他边界**:响应:...;不响应:...
3. **上下文契约**:读取 tracking_number;写入 logistics_status...
4. **失败协议**:交由 salesbot 处理...

## 生成配置
```yaml
name: logisticsbot
...

变更记录

  • 2024-06-15 初始创建
  • 2024-06-20 增加 negative_triggers,排除“运费”类消息(PR #45)

**避坑心得**:
- 归档文件必须包含 `生成者` 和 `生成时间`,这是责任追溯的唯一依据。
- `变更记录` 里写的 PR 编号,必须和 Git 提交关联。这样未来任何人看到 `logisticsbot/config.yaml` 有改动,都能立刻找到背后的业务原因。
- 最大的教训:曾有个团队把提示词回答存在本地笔记里,服务器故障后全丢了,新成员只能靠猜重构配置。现在,契约即代码,和 `config.yaml` 一样,是受 Git 保护的一等公民。

## 5. 超越配置:用提示词驱动协作模式的持续进化

把提示词当配置生成器,只是起点。真正的价值,在于它让协作模式的迭代,从“改代码”变成了“改契约”。

### 5.1 从“Bot 协作”到“Bot 编排”的跃迁

当团队稳定运行 3 个 Bot 后,我们开始探索更高阶的协作:不是 A 或 B 响应,而是 A + B + C 串联响应。比如用户问:“查下 SF123456789,顺便告诉我附近有没有门店”。

传统做法是写一个新 Bot,把物流 API 和地图 API 全集成进去。但我们用提示词驱动了新模式:

  1. 【触发意图】帮用户一站式完成物流查询与周边服务推荐,无需切换多个 Bot。

  2. 【排他边界】响应:“查下 SF123456789,附近有门店吗”“SF123456789 到哪了,最近的店在哪”;不响应:“SF123456789 到哪了”(单任务)“附近有什么好吃的”(无关任务)。

  3. 【上下文契约】读取 tracking_number;写入 logistics_status, nearest_store_address。

  4. 【失败协议】若任一环节失败,返回结构化建议:“物流查询成功,但门店搜索失败,请稍后重试”。


生成的配置不再是单个 Bot,而是一个 `orchestrator` Bot,它不直接调用 API,而是按顺序调用 `logisticsbot` 和 `storebot` 的 Skill 接口,并聚合结果。OpenClaw 的 `skill_call` 机制天然支持这种编排。

**关键洞察**:提示词的“触发意图”描述,决定了协作粒度。从“查物流”到“查物流+找门店”,意图升级了,协作模式就从“并行响应”进化为“串行编排”。提示词是意图的翻译器,配置是意图的执行体。

### 5.2 用提示词沉淀组织级协作知识库

我们把所有历史提示词回答(脱敏后)喂给内部知识库 Bot。现在,新成员问:“我想做一个查考试安排的 Bot,该怎么配置?”,Bot 不是给文档链接,而是直接返回一个预填充的提示词模板,并附上三个相似案例的契约链接。

这形成了正向循环:每一次新 Bot 的诞生,都在丰富组织的协作模式库;每一次契约的归档,都在降低下一次的建模成本。OpenClaw 不再是一个技术框架,而成了团队协作的语言。

### 5.3 个人经验:提示词不是替代思考,而是放大思考

最后分享一个真实体会。刚用提示词时,我以为它能让我“不用思考”。结果恰恰相反——它逼我思考得更深。为了写出合格的“排他边界”,我花了两天时间蹲守客服群,记录用户 200 条原始消息,分类统计哪些词组合会触发哪个 Bot。为了写清楚“失败协议”,我和销售同事开了三次会,厘清了“物流查不到”和“门店搜不到”在用户心智中的不同严重等级。

**提示词真正的力量,是把隐性的协作智慧,变成显性的、可讨论、可优化、可传承的工程资产。** 它不降低专业门槛,而是把门槛从“会写 YAML”抬高到“懂业务、懂用户、懂协作”。而这,才是 OpenClaw 本该有的样子。

我在实际使用中发现,最有效的提示词迭代,往往发生在上线后的第一周。当真实用户开始用,那些在沙盒里永远测不出的“边缘语义”会汹涌而至——比如有人把运单号写成 “SF 123 456 789”(带空格),或者用方言说“我的包裹飞到哪咯”。这时,不是去改正则,而是回到提示词第 2 步,把新发现的“响应消息”加进去,重新生成配置。整个过程,就像给协作契约打补丁,快、准、稳。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐