群里三个 agent,只有你的那个非要说话:Hermes 的按房间静默开关


设想一个群:你、两个同事、还有三个各管一摊的 agent。同事问了一句“这版部署谁来盯”,其中一个 agent 判断这不该它管,按约定回了 NO_REPLY。你希望群里什么都没有。结果几秒后,Hermes 在群里发出这么一句:

⚠️ 对于一条需要回复的消息,模型只返回了静默标记。请重试或换种说法。

问题不在于这句话不好——它是很负责任的兜底。问题在于,它把一次内部协商变成了所有人可见的“系统报错”。在多 agent 房间里,这种误报会反复出现,直到没人愿意用群聊。

静默是模型的能力,“允许静默”是房间的策略

要先分清两件事。第一件:模型可以选择不说话。Hermes 认几种静默标记,NO_REPLY、[SILENT] 这些,含义是“我收到了,这轮不需要我出声”。第二件:这条消息到底该不该被看见。这是策略问题,不该由模型单方面决定,也不该一刀切。

过去的规则里,只有几种情况允许静默生效:内部通知、定时心跳、以及适配器明确知道“这条不是在跟 bot 说话”。其余情况——尤其是有人 @ 了 bot、或者在论坛频道里直接提问——如果模型只回一个标记,网关会认为出了问题,于是补上那句提醒。

这个默认是对的。想象一下你在私聊里问“帮我跑一下测试”,结果对面一片空白:你不知道它是在跑、挂了、还是听不懂。提醒至少告诉你“它试过了但没出声”。

但在群里,这个默认会误伤。人和 agent 混在一起的多 agent 房间,本来就有大量“看到了、不用回”的时刻。于是 Hermes 加了一个房间级的开关。

一个键,只对一个房间生效

配置写在服务该房间的那个 profile 的 config.yaml 里,挂在已有的 channel_overrides 下面:

platforms:
  matrix:
    channel_overrides:
      "!exampleTeamRoom:example.org":
        allow_silence: true

三个要点:

  • 它是房间级覆盖,不是 profile 全局设置。别的房间一切照旧。
  • 不写、写 null、写 false,都保持原来的回答策略——有人提问而模型不出声,照样给你那句提醒。
  • 内部通知、定时心跳、“这条没在跟我说话”这几种既有豁免不受影响,即使房间里写了 allow_silence: false 也照样静默。

改完配置要重启网关,让所有正在服务该路由的 profile 都读到新值。

查找顺序里有个坑

房间覆盖的查找和模型覆盖共用同一套规则:先精确匹配频道 id,再匹配 thread id,最后匹配父频道/论坛 id。谁先命中就用谁。

坑在“第一条命中的条目说了算,包括它明确写了 false”。也就是说,如果你给某个 thread 单独配了 model,这个 thread 条目会先命中,而它没写 allow_silence——于是父频道的 allow_silence: true 就不生效了,这个 thread 又回到“必须出声”。解决办法很朴素:在那个 thread 条目里把 allow_silence 一起写上。

这跟 按聊天设置模型覆盖 是同一套机制,两者可以写在同一个条目里,只是别只写一半。

静默不等于“什么都没发生”

这一点最值得说清楚,因为很多人以为开了 allow_silence 就是“这个房间的 agent 可以随便消失”。实际上它只影响最终消息的投递:

  • 它不会撤销已经发生的工具调用。命令跑过了就是跑过了。
  • 它不会撤回已经发出去的进度消息。工具调用过程中的中间通知该发还是发。
  • 它不会让模型更倾向于沉默。开关只是允许,不鼓励;模型仍然要自己判断。

更关键的是,空结果、失败、部分完成、被打断的回合,都不获得“消失”的权限。模型不能靠回一个标记把一次失败的执行藏起来。这条是硬性健康检查:只有“完整跑完、且没有任何失败/部分/中断标记”的回合,房间的静默许可才作数。

还有个容易被忽略的细节:如果模型写了一段正式回答,同时又调用了只做家务的工具(比如写记忆、更新 todo、管理技能、搜索会话历史),然后以裸标记结束——这段回答会作为最终答复发出去,在开了 allow_silence 的房间里也一样。理由很直白:那不是“判断不需要回”,那是“回了但结尾手滑”。

崩溃恢复也遵守同一原则:只有当“这次静默”被明确记在那一条最终回复上时,重启后的补投才会尊重它;在记录之前就崩了,人类提问的兜底提醒照旧。

和这个功能一起合进来的四件事

这个能力不是单独一个 commit 落地的。上线前后还修了几处让它真正可靠的地方:

  1. 读取顶层平台块,并且失败时安全回退。 覆盖配置可能写在 platforms.<name> 顶层,也可能写在 extra 里;读取逻辑两者都认,读不到就退回默认策略,不会因为解析失败而放宽。
  2. 静默回执失败不再变成报错。 保存“这一轮决定静默”的回执如果写失败,不该让整个回合变成错误——它只是一条记录。
  3. 主 profile 用网关自己的配置。 网关自身服务的那个 profile,读的是它自己那份配置,而不是启动时的快照。
  4. Telegram 群的静默标记更稳。 一组修复让频道的回复、匿名管理员帖子的回复、未知作者的回复、以及没有字幕的语音/音频文件,在群里都能保住静默标记,而不是被当成“该回但没回”。

前三条是“别把策略读错”,第四条是“别把标记弄丢”。这类修复不出现在发布说明里,但它们决定了这个开关在你手上到底有没有用。

什么时候该开,什么时候别开

该开:多 agent 协作房间、有人和 agent 混在一起的项目群、通知量大的运营群——也就是那种“沉默才是正确回应”出现频率很高的地方。

别开:私聊、客服入口、任何“用户提问必须有回应”的通道。在这些地方,那句 ⚠️ 提醒不是噪音,它是唯一能告诉你“模型出问题了”的信号。你把它关掉,就等于把故障调成静音。

如果拿不准,先只给一个房间开,观察一周。真出问题时你损失的是“这个房间的兜底提醒”,而不是网关本身。

想知道这个房间为什么没回、或者为什么回了,另一个有用的话题是 静默断线与心跳——它讲的是连接层的沉默,跟这里的策略层沉默不是一回事,但两者经常一起出现。