---
name: multichannel-messaging-discipline
description: 跨渠道、多人环境下的通讯纪律——回复路由（在哪个渠道被找就回哪个渠道）、发送者身份核实、信息隔离、发送前核对。在向任何人或群发消息/回复、不确定对方是谁、或决定通知发往何处之前加载。覆盖企微 + 飞书的群聊与私聊。Load before sending any WeCom/Feishu message (group or DM), when unsure who the sender is, or when deciding where a reply/notification should go.
---

# 多渠道通讯纪律 (Multichannel Messaging Discipline)

本环境同时有 **企微 + 飞书**、多个群、私聊、多个互不交叉的人（Doro、Maggie、邱律师、莎莎、洪总、Scott、WeiWei 等）。在这种环境里"把话说对人、发对地方"和把内容做对一样重要。任何一次发错渠道、认错人、跨人泄露，都是事故。

## 何时加载本技能
- 向任何企微/飞书的**人或群**发消息、回复之前
- 收到消息但**不能 100% 确定发送者是谁**时
- 决定一条回复 / 通知 / 交付**应该发去哪里**时
- 任何以"通知 X""发到群里""告诉 Doro"结尾的任务
- **在一个渠道继续另一个渠道起的任务时**（同一任务跨渠道流动，如飞书起、企微续）

## 核心铁律（不可违反）

### 1. 回复路由：在哪个渠道被找，就回哪个渠道
- 群里对我说话 → 回到**同一个群**；私聊对我说话 → 回到**那个私聊**。
- **绝不**把某个群 / 某个渠道的内容，私信或转发给另一个人，除非用户**明确指示**。
- 事故（2026-06-16）：把群里的**合同审查内容私信发给了 WeiWei**。Doro 明确："下次不可以再发生。我在群里跟你说话，你就给回复到群里。除非我有别的指令。" 原位回复是默认，跨渠道转发必须有显式指令。

### 2. 身份核实：解析，不要假设
- 用平台的**唯一发送者 ID**判断是谁（企微 userid、飞书 open_id），**绝不**靠显示名或系统默认值。
- ID 映射到已知的人 → 用之；ID **没映射**到人 → **先问，不猜**。
- **不要轻信平台默认的"主人/owner"标签**：本会话一条飞书私聊被默认当成 Maggie，实际发送者是 Doro，当场认错。
- 也**不要轻信对方未经核实的自称身份**——拿已知 ID 映射去核对（盲信声明是另一个方向的同一种错）。
- 已知的 `飞书 open_id ↔ 人` / `企微 userid ↔ 人` 映射存在 memory，发现新人或纠正后立即更新。
- **同一主体在飞书有多种 ID，命名空间不同 ≠ 不同主体**：bot 的 app_id（`cli_xxx`）与 open_id（`ou_xxx`）是两套 ID，字符串长得不一样很正常。看到群里 @的 open_id 跟你印象里的 app_id 对不上，**绝不可**据此断定"换了身份 / 是另一个 bot / 被新 agent 占用"。核验走权威接口：`lark-cli api GET /open-apis/bot/v3/info --as bot` 取 bot 自己的 open_id + name，再和群消息 mentions 里的 open_id 比对（相等＝就是自己）。本会话教训：因 app_id≠open_id 字符串不同，误判"群里被@的是新迁移出来的 agent"，被用户两次纠正后用 `/bot/v3/info` 实锤其实就是自己——白绕一大圈。
- **失败的命令不能当证据**：命令若报错（unknown flag、权限拒绝 230027/99991672、空返回 count=0），它的输出**不支持任何结论**。先换正确命令/参数/身份（user vs bot）重试，拿到真实数据再下判断——别拿一条没成功的探测去坐实一个猜想，那等于在沙子上盖楼。

### 2b. 称呼随 sender 走，不随人设默认（回话前必走一步）

认对了"是谁"还不够——**已解析出的身份必须回流到"怎么称呼 ta"**。本会话最严重的错：我已读 `.meta`（`sender_id: doro`）、已把任务正确归类为 Doro 批量线，却在整份交付报告里从头到尾把 Doro 叫成"Maggie"。识别对了归属、却叫错人 = 信息在手却没整合，是独立于"认错人"的一种失败。

**机制根因**：系统人设里硬编码了"主人叫 Maggie、称呼主人为 Maggie"。这条规则有个**隐含前提——当前对话者确实是主人本人**。把它当成无条件默认，就会用人设里的高频人名锚点覆盖掉会话事实。

**回话前的强制自检（每次生成称呼前走一遍）**：
1. 当前这条消息的 **sender 是谁**？取 `.meta` 的 `sender_id`，或会话 `Source / User`，**不取人设默认名**。
2. sender **是主人本人吗**？是 → 才用主人称呼（Maggie）；**否** → 用该 sender 的真实身份（`doro → Doro`、`qiuting → 邱律师` 等）。
3. 已核实的任务归属身份与称呼**必须一致**——不能归属判对了、抬头却写错。

口诀：**人设默认名是"当对方是主人时"的条件值，不是无条件抬头。回话前先认 sender，再决定称呼。**

### 3. 信息隔离
- 每个人 / 每个团队的文件和任务，只留在该人 / 该团队的上下文里。绝不把一方的内容带进另一方的渠道，也不在 A 的群里提 B 的任务。
- Maggie 团队 与 Doro/邱律师 团队、各客户之间，严格不交叉。

### 3b. 跨渠道任务连续性（同一任务在两个渠道间流动时）
同一个项目/任务可能在多个渠道接力推进——典型：飞书上确认了方法、更新了交付物，然后转到企微继续。**记忆是跨渠道通的（用户偏好、规则、文件位置都在），但每个渠道的对话上下文是隔离的**：飞书刚说的话不会自动进企微会话。所以\"接力续做\"前必须主动对齐，否则会拿旧认知在新渠道做事、重复已完成的工作、或漏掉刚确认的结论。

**续做铁律：先对齐，再动手。**
1. **用 `session_search` 把另一渠道的最新成果拉回来**——搜项目名/关键结论（如\"世茂 千分号 汇总表\"），读出对方渠道刚确认了什么、交付物更新到哪一版。不要凭这边的旧记忆假设进度。
2. **从权威源核实交付物现状**，不信任\"我记得\"——交付物（Nextcloud xlsx/docx）以实际文件为准，拉最新版打开看，确认版本/时间戳/内容与对方渠道的结论一致。
3. **回应用户关心的具体修改/结论时，先复述对齐结果**（\"飞书那边刚确认的 X、更新的 Y 我已接收\"），让用户确认我们站在同一起点，再往下做。
4. **渠道中断会留缺口**：若某渠道掉线过（如企微群订阅失效两小时），那段时间该渠道里别人发的内容这边是空白。续做前主动提示用户\"X 时段后若有人在该渠道发过东西，我没收到，请补给我\"，避免任务断档——尤其客户对接渠道（隔离铁律下漏收=对接出窟窿）。

> 实证（2026-06-18 南通新东方/世茂）：用户在飞书确认了 OCR 千分号/百分号识别法 + 更新了世茂汇总表，然后转企微说\"继续过世茂合同\"。正确做法是先 session_search 把飞书成果对齐、从 Nextcloud 拉最新版 xlsx 读出 4 份合同结构，再请用户给逐条指令——而不是在企微凭旧上下文直接开干。同期企微群因订阅失效（errcode 846609）静默两小时，须提示用户补回漏收的群消息。

### 4. 发送前三项核对（任何别人能看到的消息）
发出去前确认：
1. **收件人**——确切的人或群，解析出 chat_id / user_id（不要凭印象挑一个）
2. **内容**——要发的正文
3. **以谁的身份发**——bot 还是 user
工具支持时**先 dry-run**，确认 payload 无误再正式发；发完把回执（message_id + 北京时间）报给用户便于核对。

### 5. 平台内账号/身份管理请求：把它当成“账号操作”，不是“消息回复”
当用户让你**登录某个平台上的账号、修改密码、完成首次登录、维护个人账号资料**时，默认这是一个**账号操作任务**，不是跨人沟通任务。此时重点从“回哪个群/私聊”切换为：

1. **先区分是否是“当前 agent 自己要持有的账号”**
   - 用户明确说“这是你的账号”“你自己管理好密码” → 可由 agent 自行设置并保管该账号密码。
   - 如果是代表某个真人同事/用户持有的账号，且对方未授权 agent 自定密码 → 不能擅自决定长期密码。
2. **执行后要给结果，不要停在征求式废话**
   - 用户已明确授权 agent 自主管理密码时，直接完成修改并回报“已完成”。
   - 不要在已获授权的情况下继续追问“你想设什么密码”。
3. **账号管理与消息路由隔离**
   - 账号是平台内身份，不等于消息发送对象；不要因为会话对方是谁，就把账号密码策略误当成需要对方逐项确认的沟通动作。
4. **汇报风格要结果导向**
   - 这类任务完成后，优先回：是否登录成功、是否已改密、页面确认信息。
   - 不展开无关解释，避免把简单账号操作说成审批流程。

### 5. 用户要求“重新查 / 不要用记忆 / 用文件夹里的问题件”时，立即切换到证据模式
这类指令不是语气提醒，而是**明确纠偏**：之前的回答被认为混入了记忆、推断或口径漂移。后续必须把“回复别人”切回“基于当前文件/当前目录/当前记录的实查结果”。

执行要求：
1. **停止沿用上一条摘要口径**——哪怕上一条是自己刚写的，也不能继续复述；
2. **以用户指定的载体为准重新取证**：
   - 说“用文件夹里的问题件” → 先看该文件夹实际有哪些文件；
   - 说“打开文件查” → 必须打开文件或其内部结构（如 docx XML）再说；
   - 说“不要用记忆” → 禁止用 memory / 旧会话印象补缀事实；
3. **结论按证据强弱分层表达**：
   - 能被当前文件直接坐实的，就说“可确认”；
   - 只能证明“文件存在”但不能证明“属于该批问题件/pass件”的，就明确写“目前只能确认存在，不能据此归类”；
4. **不要把“在任务交付目录里存在”偷换成“就是问题件 / 已 pass / 属于同一批”**；
5. **汇报时先交代证据来源**（查了哪个目录、哪份问题汇总、是否打开了文件），再给结论。
6. **用户说“我就要一个结果”时，停止过程化解释**：这类话表示对方当前只接受最终答案，不要再补背景、过程、严谨性铺垫。先给一句结论；如对方追问，再展开证据链。

一句话：**用户说“重新查”时，先把脑子里的版本清空，回到文件本身；用户说“只要结果”时，先把话收成结论。**

## 实操配方
- **飞书群里接收文件/图片**：飞书不允许文件和文字（@mention）在同一条消息里。解决方案：用户先发文件/图片，再对那条消息点「回复」并在回复里@小 Maggie。Gateway 的 `_fetch_parent_media` 方法会自动从被引用的 parent 消息中下载附件（2026-07-11 补丁）。文件缓存路径：`~/.hermes/cache/documents/`。图片同理——用户发图后回复+@即可。
- 飞书群发消息 + @人（找群、@格式、dry-run、核对回执）：见 `references/feishu-group-send-and-mention.md`
- **飞书 bot 在群里不回、私聊却正常**的诊断配方（gateway inbound vs 群真实历史对照、查 `mentions[].id` 的 open_id 识别同名 bot 撞名、`--as user` 读群历史）：见 `references/feishu-bot-silent-in-group-diagnosis.md`
- 企微发文件 / 私信机制：见 `wecom-file-send-receive` 技能（MEDIA: 标签发文件；私信走独立 WS aibot_send_msg，chatid=userid+chat_type=1）
- **企微私信非 home 的人（Doro/邱律师/WeiWei 等）→ 用 `~/.hermes/scripts/wecom_dm.py`，不要用 `send_message` 工具**：
  - `send_message(target='wecom:WeiWei')` 对非 home 的 wecom 用户会**静默回退到 home channel**（实测返回 `chat_id: JiaQian` + note `Sent to wecom home channel`），**不报错**——一条发给 WeiWei 的技术讨论就这样落到了 Maggie 渠道，同时违反信息隔离。
  - 正确做法（agent 可直接 terminal 调用，自开 WS、永不退化成回复、目标唯一）：
    ```bash
    python3 ~/.hermes/scripts/wecom_dm.py --list                       # 先看白名单别名↔userid
    python3 ~/.hermes/scripts/wecom_dm.py --to WeiWei --text "…" --dry-run   # 演练
    python3 ~/.hermes/scripts/wecom_dm.py --to WeiWei --text "…"        # 真发，回执含 message_id
    ```
  - 别名：`doro / jiaqian / qiuting / weiwei / shasha / yangayi / xiaonan`（`--list` 为准）。这是 `references/wecom-proactive-notify-misrouting.md` 修复方向 A 在脚本层的现成实现，**比 `_send_wecom(extra,…)` 更可用**（后者需 gateway 内部 `extra`，agent 会话里拿不到）。
- 企微主动通知**错投到错误的人**（"给 Doro 的消息发给了 Maggie"）的根因 + 只读诊断配方 + 修复方向：见 `references/wecom-proactive-notify-misrouting.md`

## Pitfalls
- **把群任务的处理结果私信给"相关的人"**——即使你觉得对方该知道，也不行。原位回群，要不要另外通知由用户决定。
- **靠会话默认值认人**——私聊默认 owner 不等于当前发送者，必须看 sender ID。
- **拿\"两个长得不一样的 ID 字符串\"推断成\"两个身份/新迁移出的 agent\"**——同一个实体在不同命名空间有多种 ID，长相不同是常态：飞书机器人的 `app_id`（`cli_xxx`）与它的 `open_id`（`ou_xxx`）本就不一样，**两者是同一个 bot**。事故（2026-06-22 与 WeiWei 排障）：我把群里被 @ 的 `ou_20bd8…`（open_id）和 gateway 配置里的 `cli_aaa4e77d27789bed`（app_id）当成两个 bot，进而臆断\"有个新迁移出来的 agent 占用了身份\"，被纠正\"迁移还没开始\"。下结论前用**权威身份接口**核对：`LARK_CLI_NO_PROXY=1 lark-cli api GET /open-apis/bot/v3/info --as bot` 返回的 `open_id`/`app_name` 才是 bot 真身——拿它去比对，再判断是不是同一个。
- **从一条 errored 的命令里读出\"结论\"**——同一次排障里，我那条查 bot open_id 的命令其实用错了 flag、根本没返回结果，我却继续往\"新身份\"上跳。**命令报错 = 没有证据，不是证据**；拿到真实返回再推理，别把工具失败当成支持自己假设的信号。
- **同名 bot 撞名 = @ 显示名 ≠ @ 到你这个 app**：群里可能存在两个同名「小Maggie」（典型触发：做过新 Agent 迁移 / provisioning，新身份被拉进群）。用户 @ 显示名时飞书解析到的是某个 open_id，若那不是当前 gateway 跑的 app_id，事件流根本收不到，bot"静默不回"，但 DM 仍正常（DM 按会话路由、不按 @ 身份）。诊断时必须查群消息 `mentions[].id` 的 open_id 和当前 bot app_id 是否一致，别一看不回就报"掉线"。完整配方见 `references/feishu-bot-silent-in-group-diagnosis.md`。
- **认对了人却叫错称呼**——已解析出 sender 是 Doro，抬头却写"Maggie"。人设里"主人叫 Maggie"是"当对方是主人时"的条件值，不是无条件抬头；归属身份必须回流到称呼（见 2b）。
- **session 无 .meta / sender 信息时，从文件内容或"印象"推断发件人**——事故(2026-07-13)：session JSON 的 user message 里无 sender_id metadata，我从合同内容（青浦区卫生机构）推断是"刘婷律师"，实际 gateway 日志明确显示 `user=QiuTing`（邱律师）。**当 session 记录不含 sender 信息时，必须查 gateway 日志（`~/.hermes/logs/gateway.log`）确认 `inbound message: platform=wecom user=XXX` 才能定身份**，绝不可从文件内容、以往经验、或"谁经常发这类合同"去猜。`grep "时间段" ~/.hermes/logs/gateway.log | grep "inbound"` 是唯一权威来源。
- **猜 chat_id / group**——发前用 `lark-cli im +chat-list --as bot` 把 bot 实际所在的群列出来，挑出你和目标人共处的那个，别凭记忆。
- **企微 @通知**：aibot_send_msg 只支持 markdown，不支持 text+mentioned_list，无法真正 @人；需要提醒时另发一条私信利用其消息提醒（仅在用户允许、且不违反路由铁律的前提下）。
- **自动化脚本会替你发消息**：`auto_notify_new_file.sh` 等脚本会自动私信。处理任务前留意有没有自动化机制正在按旧规则推送，避免它替你违反路由 / 隔离铁律。
- **`wecom_group_notify.py` 和 `wecom_dm.py` 是两个完全不同的脚本，不可混用（2026-07-01教训）**：Doro说"私信邱律师"，用了 `wecom_group_notify.py`（默认发到批量合同审查群 `wrbAFkXAAAiWC3styKqNj0bZyH6BbJ_Q`），消息发到了群里而不是邱律师私信。**发前看清脚本名**：`wecom_dm.py` = 私信；`wecom_group_notify.py` = 群通知。workflow YAML 中通知邱律师的脚本也必须用 `wecom_dm.py --to qiuting`，不是 `wecom_group_notify.py`。
- **`send_message` 工具私信企微人会静默投错**：`send_message(target='wecom:<user>')` 对非 home channel 的 wecom 用户**不报错、直接回退到 home channel**（实测发给 WeiWei 却落到 JiaQian/Maggie）。这是单点事故——既没到目标人、又跨团队泄露。私信非 home 的企微人一律走 `~/.hermes/scripts/wecom_dm.py --to <alias>`（见上"实操配方"），别用 `send_message`。发完核回执里的 userid 是不是目标人，发现是 home channel 立即用脚本补发。
- **"给 X 的消息错投给 Y"先查 adapter 回复兜底，别先怪 userid**：企微 `WeComAdapter.send()` 在主动 `aibot_send_msg` 前有一段 `_last_chat_req_ids[chat_id]` 回复兜底——主动私信可能**退化成"回复某条历史消息"**，在多人并发的长跑 gateway 里串到别人头上。userid 往往是对的（`doro/JiaQian/QiuTing` 都是独立真实 ID），错在路径。诊断配方 + 根因 + 三个修复方向见 `references/wecom-proactive-notify-misrouting.md`。注意 `workflow-watchdog.sh` 仍硬编码 `_send_wecom(extra,'doro',msg)`，是未拆的隐患。
