多 Profile 定时任务终于不"串台"了:每个 Profile 用自己的机器人投递

你维护着两个 Profile:一个是个人助手,一个是团队机器人,各有各的飞书/Telegram 机器人。凌晨 3 点,团队机器人的定时任务跑完了,结果消息却从个人助手的机器人发了出来——或者更糟,直接报 Bot not in chat,任务执行成功但结果没人收到。8 月 31 日合入的修复(PR #99375)把这类“串台”问题连根拔掉:定时任务用谁的机器人发消息,由任务属于哪个 Profile 决定,而不是由哪个进程碰巧执行了它决定。
病根:投递身份跟错了“人”
Hermes 的多 Profile 设计里,每个 Profile 有自己的配置、密钥、会话数据库和消息机器人。但修复之前,定时任务的投递身份有个致命缺陷:它跟随的是“执行进程”,而不是“任务的主人”。
打个比方:你在主 Profile 的终端里启动了网关,它顺带托管了 B Profile 的任务。B 的任务执行时,系统去读“当前进程”的密钥——也就是主 Profile 的——于是 B 的结果用主 Profile 的机器人发出去。发到 B 的群聊里?机器人不在那个群,于是报错;发到主 Profile 的聊天里?消息落错了地方。任务明明成功了,结果却像丢了一样。
这还不是全部。cron status 也有类似的问题:它只要看到任何一个 Profile 的网关活着,就认为“一切正常”——哪怕 B Profile 自己的调度器根本没在跑。查的时候“绿油油”,实际上 B 的任务一个都不会触发。
修复:四个根因,一个原则
PR #99375 合并了 9 个贡献者的提交,把这条链路上的问题逐个修掉,原则只有一个:投递身份必须来自任务所属的 Profile。具体来说:
- 每个 Profile 的调度 tick 用自己的适配器映射:多 Profile 托管(
_start_multiplex)时,每个 Profile 的 tick 拿到自己的平台适配器;共享适配器只保留给默认 Profile 用,绝不作为未连接副 Profile 的兜底; - 密钥作用域覆盖投递环节:之前 Profile 密钥作用域在任务执行完就重置了,导致独立发送(standalone-send)兜底时用的是执行进程的平台密钥;现在密钥作用域贯穿
_deliver_result全程; - 家庭聊天 ID 从所属 Profile 的密钥里解析:目标 chat id / thread id 通过所属 Profile 的
get_secret解析,而不是直接用环境变量; - Profile 任务的会话写进自己的 state.db:通过
contextvars.copy_context()构造 SessionDB,B 的任务不再把会话记到默认 Profile 的数据库里; - 卫星 Profile 的预检救援:走
gateway.profile_routes的卫星 Profile 直接读主配置,失败即关闭(fail-closed),不泄漏环境变量; cron status说真话:systemd PID 发现限定到本 Profile 的服务名;从未见过心跳(heartbeat)的 Profile 显示为警告而不是绿色。
你怎么验证
更新到最新 main 后,先用这条命令确认你的 Profile 调度器状态是否可信:
# 查看某个 Profile 自己的 cron 状态(而不是"任何网关活着"的假绿灯)
hermes -p work cron status
再跑一个带投递的定时任务,观察消息是否从该 Profile 自己的机器人发出、落在该 Profile 的家庭聊天里。对多 Profile + 多平台用户来说,这几乎是立竿见影的体验修复:结果不再丢、不再串台、不再出现“任务成功但没收到”的幽灵消息。
何时能用上
PR #99375 于 2026 年 8 月 31 日合入,目前只在 main 分支,尚未进入正式版本。这个问题修复了 #94862、#97909、#99028、#98790、#97476 等一系列 issue,都是“任务没收到”“状态撒谎”类的真实反馈。
多 Profile 是 Hermes 最容易被低估的能力:每个 Profile 独立身份、独立记忆、独立机器人。想系统地用好它,可以看看我们的 Profile 多实例完全指南 和 飞书多 Profile 路由;定时任务的完整玩法见 cron 自动化完全指南。