主动性设计¶
从被动通知走向前瞻式协作:Raven 主动性子系统的设计。
本文描述的是设计意图——主动性子系统应该成为什么,以及它为何是现在这个形态。其配套文档
主动性参考是竣工文档:它把每个部件映射到 raven/proactive_engine/
下的模块路径以及 spine(raven/spine/)。
1. 主动性的三个层次¶
一种被广泛采用的划分把代理的主动性分为三层,每一层都建立在前一层之上:
| 层次 | 名称 | 定义 |
|---|---|---|
| L1 | 响应式监控 | 事件已经发生并被检测到,代理决定是否通知用户。 |
| L2 | 预测式 / 基于例行 | 从用户历史中学习重复出现的模式,并在用户开口之前采取行动。 |
| L3 | 前瞻式 / 情境式 | 代理就用户当前处境进行推理,推断潜在需求并采取行动。 |
止步于 L1 的系统给人的感受是通知器,而不是协作者。Raven 的子系统触及 L2 与 L3, 同时在默认配置下保持克制。
由此得出的设计原则:
- 三选一的决策,而非二选一:保持沉默、发一条短消息,或执行一个多步任务——不只是 通知与否。
- 按用户建模:主动性偏好必须因人而异,不能是一个全局开关。
- 反馈闭环:每一次推送都会收集一个信号,用于收紧或放宽后续行为。
- 低打扰优先:保守的默认值,渐进式接管;最初几条主动消息的质量,决定用户会不会 继续保留这个功能。
- 内容质量重于频次:一条精准的主动消息胜过十条泛泛的通知。
2. 核心思路:周期性 Planner 加按需派生¶
该子系统不运行第二个代理循环,也不订阅事件流。它周期性醒来,读取一份打包好的上下文,
每个节拍做一次结构化的 LLM 决策。当某个决策需要多步执行时,它通过既有的
SubagentManager 派生一个微代理,而不是重新实现一套循环。
这样成本有界(每节拍一次 LLM 调用,派生的尾部开销也有界),同时仍能覆盖例行自动化
(L2)与多步前瞻(L3)。周期性 Planner 的形态对齐心跳服务
(raven/proactive_engine/schedulers/heartbeat/service.py),执行则复用子代理机制,
而非另建一套专用运行时。
主动性有两个来源:
- Sentinel——由 LLM 在每个节拍决定是否以及如何触达用户。
- Cron——由用户显式安排的提醒。
两者都以带来源标记的轮次经由 spine 抵达代理,并且都要通过同一本 NudgePolicy 账本,
因此这两个入口绝不会就同一话题重复提醒用户。
3. 组件¶
ProactivePlanner——周期性推理器¶
按自己的间隔醒来,读取单一的打包上下文,做一次返回结构化决策的 LLM 调用。它是其输入的
纯函数:没有副作用,也绝不抛出异常——任何失败都退化为 skip。
它唯一的输入是组装好的 PlannerContext:用户的长期记忆、近期历史的尾部、当前活跃会话、
已学习到的例行、日历条目、当前的 NudgePolicy 状态、上一节拍的决策、近期触发历史、
attention.md 状态文件中选定的若干段落,以及一个折叠后的近期行为窗口。
Planner 看不到其他任何东西。
ContextAssembler——输入打包¶
把每一个信号源汇聚进 PlannerContext,并按字段做优雅降级(某个来源缺失时给出空值,
而不是崩溃)。这里是决定 Planner 能看到什么的唯一位置。
RoutineLearner——行为模式学习¶
从用户历史中挖掘重复出现的模式(按时间新近度加权的词频统计,不需要 LLM),并产出候选 例行供 Planner 消费。候选项带确认生命周期持久化:一个模式先被提议,再由用户确认, 然后才自动触发;被拒绝则暂停,长期不用则退役。
NudgePolicy——共用的防打扰闸门¶
每一条主动消息——无论来自哪个执行器,还是来自任务发现菜单——在投递前都必须通过
NudgePolicy.check()。它执行配额、免打扰时段、冷却期、内容去重和按话题的限额,
并会根据用户反馈学会收紧或放宽。见第 5 节。
ProactiveSpawn——多步执行桥接¶
当决策为 spawn_agent 时,这一层包装 SubagentManager.spawn(...) 来运行一个微代理执行
多步任务(例如状态检查或摘要),再把结果经由 NudgePolicy 与分发器送回。它不引入新的代理
循环——只是在子代理自身的迭代上限之上,加一层薄薄的主动来源标记、结果格式化,以及
并发与超时约束。
任务发现——前瞻性菜单¶
每日一次的批处理读取近期记忆与历史,提出一份简短的候选任务菜单,发给用户按编号挑选。 用户的选择会在抵达代理之前被截获,并路由到某个执行器(回复、工具或派生), 必要时还可加一道确认步骤。
4. 动作空间¶
一个 Planner 节拍恰好返回五种动作之一,并依据工具 schema 校验,使 Planner 无法产出 格式错误的决策:
skip——本节拍没有值得做的事。nudge——立即发送一条独立消息。nudge_inject——把消息追加到代理在目标会话中的下一条回复里(用户本来就在那段对话中, 因此这条信息自然地延续了当前话题)。nudge_defer——等目标会话当前的话题告一段落后再发送(用户正忙于别的事,不应被打断)。spawn_agent——派出一个微代理执行多步任务。
nudge_inject 与 nudge_defer 是其中最有特色的两个:它们让代理意识到用户此刻正在做
什么,而不只是在「现在发」和「不发」之间二选一。它们的执行路径见实现参考文档。
5. 防打扰:NudgePolicy 闸门¶
NudgePolicy 是一道分层闸门,读写职责划分清晰:check() 是纯粹的裁决,
record_fired() 只在投递成功之后才写状态。按顺序应用的各层覆盖:
- 免打扰时段(一个静态窗口,加上一个从反馈中按小时学习得到的窗口);
- 按人格划分的免打扰窗口;
- 按天与按小时的配额;
- 按会话与按忽略次数的冷却期;
- 按话题的接受率冷却与硬拒绝冷却;
- 窗口内的内容去重;
- 按话题的滚动配额栈(小时 / 天 / 周)。
高优先级消息可以绕过部分软性层,但硬性配额与冷却期依然成立;而且当用户连高优先级消息 的接受率都很低时,这条高优先级豁免本身也会被收回。
小时配额会被一个自适应乘数缩放,该乘数随用户近期接受率对称移动:高度参与的用户可以收到 更多,低参与的用户收到更少。这个乘数还会作为软信号出现在 Planner 的提示词中, 使 Planner 能够提高自己的价值阈值,避免发起注定会被拒绝的 LLM 调用。
该策略可个性化:ProactivityPreferencesReader 允许学习到的用户偏好覆盖静态配置,
但只能朝收紧方向生效(用户偏好可以拉长免打扰窗口,绝不能缩短)。
所有这些状态都跨进程持久化(一个带 fcntl 锁、以原子重命名写入的 JSON 存储),
因此 REPL 与网关共用同一本账本,重启也不会丢失配额或冷却状态。
6. 投递与轮次传输:spine¶
系统中没有消息总线。spine 是唯一的轮次传输与投递路径。
- 一个轮次以
TurnRequest的形式提交给进程级的Scheduler(raven/spine/scheduler.py), 由它路由到按会话串行的Lane。每个请求都携带一个Origin——USER、SENTINEL、CRON、HEARTBEAT或SUBAGENT——它决定并发池的划分与控制权资格 (raven/spine/turn.py)。 - 回复,以及任何主动消息,都经由
DeliveryHub(raven/spine/delivery.py)投递, 由它把每份可投递内容路由到所属渠道的出口。普通 nudge 直接投到 hub(不再跑一遍轮次), 因此用户收到的是一条独立消息,代理也无法「就提醒采取行动」。 - Sentinel、cron 与心跳抵达代理的方式完全相同:都是经由 spine 提交的、带来源标记的轮次。
cron 的提醒以
CRON来源的轮次触发;心跳则作为自己的服务醒来。
USER 来源的轮次享有完整的用户入站处理(参与度检测,以及让 nudge_inject 得以搭车的
响应修饰链)。那些不应被当作用户输入、或不应在自身输出上再叠加 nudge 的主动系统轮次,
会按来源被挡在这些钩子之外。
7. 场景¶
L2——例行自动化¶
用户最近几个周一早上都查了天气。RoutineLearner 浮出一条候选例行;在下一个周一早上的
节拍上,Planner 提出建议(「我注意到你周一早上会看天气——要我自动帮你查吗?」)。
一经确认,此后某个周一早上的节拍就会发出 spawn_agent 去获取并总结天气预报,
然后投递一份简洁的摘要。
L3——关联记忆的提醒¶
用户提到过一张 SSL 证书将在月底到期。Planner 读取记忆后,提前一周提醒用户
(nudge,中等优先级);若此后仍无动作,则在到期前两天以高优先级再提醒一次。
L3——感知上下文的续接¶
用户两小时前在调试 Redis 连接,之后一直没有回复。Planner 读取该活跃会话,看到代理上一次
给出的建议,然后提出一个贴合上下文的追问(「Redis 连接的问题解决了吗?如果
systemctl start redis 没起作用,我可以去看看防火墙规则或 bind 地址。」)——
而不是一句脱离上下文的「你还在吗?」。
L3——主动状态检查¶
用户部署到了预发布环境,并说「先让它跑一会儿」。经过一段合理的间隔后,Planner 发出
spawn_agent 执行一次健康检查并报告结果——代理已经替用户看过了,而不是提醒用户去看。
8. 成本¶
Planner 每个节拍只做一次有界的 LLM 调用(小输入,小的结构化输出)。默认节拍间隔为
30 分钟,RoutineLearner 完全不使用 LLM。被派生的微代理是唯一的多步成本,其尾部开销由
子代理的迭代上限、每任务超时和并发上限共同约束。整个子系统默认关闭
(sentinel.enabled=false),因此选择不启用的用户不付出任何代价。
尾部风险控制:
- 主动微代理的并发上限;
- 每个任务各自的超时;
- 子代理自身的迭代上限;
- NudgePolicy 的限流,它间接约束了派生频率。
9. 风险与缓解¶
过度打扰¶
如果 Planner 判断失误、推送了低价值的提醒,用户就会关掉这个功能。缓解措施:保守的默认值;
RoutineLearner 先提议后行动;自适应的 NudgePolicy 在接受率低时自动收紧;Planner 的提示词
默认倾向 skip。
Planner 决策质量¶
小模型可能误判复杂上下文。缓解措施:Planner 的输出是结构化的工具调用(解析可靠); 上下文刻意保持精简,以落在模型的最佳区间内;高影响动作至少要求中等优先级; Planner 使用的模型可配置,需要更强模型的用户可以自行更换。
派生安全¶
无人看管的微代理可能做出破坏性动作。缓解措施:主动派生默认关闭;子代理在工作区限制下运行, 不具备消息发送与递归派生工具,并受迭代上限和一层额外超时约束。
历史格式漂移¶
RoutineLearner 依赖带时间戳的历史格式。缓解措施:历史由整合器按受控格式写入,解析器容忍 一定偏差,无法解析的条目会被跳过而不是导致失败。