这篇文章我们还是聊 OMP(oh my pi)。 实现细节、配置项与
WATCHDOG.md发现机制见advisor-watchdog.md。 赞美创新。
TL;DR
强烈建议直接打开用。Claude Code 已经内置这个功能。(Auto mode)。通过 /advisor 来启用。
术语:turn 指的是一轮操作;可以是 bash 命令,也可以是一次对话。
coding agent 在长任务中常反复出现一类问题:走错方向、遗漏边界条件、调用不存在的 API、在错误的抽象上继续投入。这些问题事后 review 时容易发现,但在当前的 turn 中很难被 LLM 自己察觉。
OMP 的主会话已包含完整 transcript、assistant 私有 thinking、工具调用及结果,足以让一个旁观者判断「当前这一步是否值得继续」。缺少的不是更强的执行者,而是一个独立的、只读的、能在 turn 边界给出意见的角色。
我想正是因为如此,从引入 Advisor:挂载在主会话上的第二模型,默认关闭,开启后每个 turn 结束时自动接收 transcript 增量,用只读工具核查,再把建议注入回主会话。边界是只读、不执行、不直接改状态。
Callback
我之前有说 本地review便宜方案。但是实际跑下来发现两个问题:
- 占用 token 资源。daemon 还是太昂贵了。
- 没有很好的交互机制。不如 omp。
实践
- 模型智力差不多的时候(例如都是同一个模型),在执行的时候能及时纠错。
- Advisor 较弱的时候,会给出一些错误的提醒,但是偶尔也会说正确的事情。各半。
- Advisor 强的情况暂未测试。
以下是机制分析,不读也可以。
角色边界
OMP 如何定义 advisor。
- 只读,不是第二执行者。 Advisor 不能编辑文件、不能跑命令、不能批准操作、不能改 session 状态(
advisor-watchdog.md明确「It is not a second executor」)。 - 建议可被主 agent 读到,私有上下文读不到。 Advisor 给出的
note会注入主会话 transcript 或 steering channel;但 Advisor 自己的 system prompt、私有 thinking、WATCHDOG.md不进主 agent 上下文。 - 不审查自己。 渲染下一轮增量前,已注入的
advisor类型消息会被过滤掉,避免递归审查。
这三条决定了 Advisor 的形态:一个能说话、但不能动手的旁观者。
就像是你的结对编程伙伴。
运行机制
Advisor 的触发不是主 agent 主动调用,而是 turn-end 驱动的后台运行。一次完整循环落在 AdvisorRuntime(src/advisor/runtime.ts):
| |
几个关键设计点:
- 增量而非全量。
#renderDelta只取自上次以来新增的消息(runtime.ts:149-162)。Advisor 是 append-only 上下文,省 token。 - 独立上下文维护。 Advisor 有自己的上下文窗口;接近上限时,
AgentSession会按顺序尝试:模型升级(promote)→ 压缩自己的历史 → 仍放不下就 re-prime(清空后回放当前 bounded transcript)。 - 失败降级。 单次 advisor prompt 失败会重试;连续失败 3 次后丢弃 backlog、记 warning、放行主会话(
runtime.ts:229-236)。Advisor 的故障不阻塞主链路。
工具与隔离
Advisor 拿到的是一个硬隔离的只读工具集:read、search、find、advise(advise-tool.ts:62)。前三个跑在一个 session id 带 -advisor 后缀的独立 ToolSession 上,不共享主 agent 的文件快照、编辑能力或冲突状态。
advise 工具本身的返回对 Advisor 是 useless: true + "Recorded.",真正的副作用是触发 onAdvice 回调——所以 Advisor 调一次工具,等于往主会话送一条建议。
Feature 与用户感官
三档 severity,两种交付方式
advise 工具接受一个 note 和可选的 severity(advise-tool.ts:5-13)。severity 决定是否打断主 agent:
| severity | 交付方式 | 语义 | 颜色(终端 badge) |
|---|---|---|---|
省略 / nit | 非打断,批次化成一张 transcript 卡片,落点在下个 step boundary | 清理、简化、低风险边界 | muted(灰) |
concern | 打断式 steering message | 方向可能错、漏了关键约束、疑似幻觉 API | warning(黄) |
blocker | 打断式 steering message | 继续下去会浪费这个 turn / 产出破损结果 | error(红) |
判定逻辑见 isInterruptingSeverity(advise-tool.ts:53-55):concern 和 blocker 打断,nit 排队。打断式建议走 steering channel,能在下个 steering boundary 中止正在执行的工具。
非打断的 nit 会批次化,卡片正文带固定前缀:
| |
这个前缀刻意写成「weigh it, don’t blindly obey」——主 agent 被告知这只是一个 senior reviewer 的意见,不是命令。
终端里长什么样
Advisor 的建议渲染成一张独立的 transcript 卡片(src/modes/components/advisor-message.ts),复用 IRC 卡片的 glyph + 引用边框约定,让它读起来像「一段旁白」,而不是一轮用户输入:
| |
渲染细节:
- 标题行带 meta:
N notes,有 blocker 时额外标N blocker(红色)。 - 每条 note 前是按 severity 着色的 badge(
blocker=error/concern=warning/nit=muted,advisor-message.ts:32-41)。 - 正文走引用边框(quote border),换行而非两行截断(
ecccff62修过这个折叠 bug)。 - 默认折叠,只显示前 3 条,多出来的折叠成
… +N more notes。
状态栏的 ++ 标志
Advisor 活跃时,状态栏会出现一个 ++ badge,提示主会话背后有一个 reviewer 在跑(src/modes/components/status-line/segments.ts:90)。它和模型名一起作为 statusLineModel 着色显示。
/advisor 命令
| 命令 | 作用 |
|---|---|
/advisor | 切换持久化的 advisor.enabled |
/advisor on / off | 开/关,并启停 runtime |
/advisor status | 显示 Advisor 的模型、上下文用量、token、cost |
/advisor dump | 打印 Advisor 的精简 transcript |
/advisor dump raw | 打印含 system prompt、工具、thinking、调用的完整 dump |
配置
启用需要同时满足两个条件(advisor-watchdog.md:22-35):
| |
modelRoles.advisor 走正常的 model-role 解析(支持 provider 前缀、canonical id、thinking 后缀)。光开 enabled 但没分配 advisor 模型,status 会报告「已启用但没有 advisor model」。
其他开关:
advisor.subagents(默认false):是否给派生出的 task/eval subagent 也挂 Advisor。开了之后每个 subagent 会建自己的 Advisor runtime,并在该 subagent 的 cwd 重新跑WATCHDOG.md发现。advisor.syncBacklog(off/1/3/5,默认偏向吞吐):turn 结束时主 agent 是否、等多久让 Advisor 追平。off完全不等;1最接近「每步同步审查」(30 秒上限)。注意它不是让主 agent 等建议内容,而是等 Advisor 处理完增量;建议本身始终异步送达。
设计边界与降级
把容易踩的几条边界收在一起:
- 用户主动中断时,Advisor 停止自动 resume。 Esc、collab / ACP / RPC / SDK / extension 的 cancel 都算。一条
concern/blocker在 run 已停止时只会记成可见卡片,不会强行重启 turn;这条建议会在下次 resume 时(新消息、./c续接、steer)重新进入上下文(advisor-watchdog.md:94)。 - 历史重写会重置 Advisor。 compaction、session 切换/resume、branch/fork 这类 history 替换,以及上下文维护的 re-prime,都会清空 Advisor 私有上下文、回卷游标,下一轮回放当前 bounded transcript。这样 Advisor 不会停留在过时上下文里(
runtime.ts:125-134)。 - mid-session 启用不会重放全史。 开启时游标 seed 到当前 transcript 长度,避免第一次就把整段旧对话塞给 Advisor(
runtime.ts:136-147)。 - 独立计费。 Advisor 的 token 和 cost 是单独统计的,
/advisor status报的是 Advisor 自己 transcript 的用量,不和主 agent 混算。
收束
Advisor 把「事后 review 能发现的问题」前移到 turn 边界。它解决的不是「主 agent 不够强」,而是「同一个执行者难以在执行中审视自己」——因此它被设计成只读、可被主 agent 读到建议、且不参与执行的独立角色。
实现上值得注意的取舍是隔离与异步:Advisor 跑在隔离的工具会话里、用增量 transcript、后台 drain、失败即降级,保证它给主链路带来的只有收益(意见)而不引入新的阻塞或竞态。advisor-watchdog.md 里有完整的配置、发现机制与上下文维护说明,本文不再重复。
