Featured image of post Advisor:挂在主会话上的只读审查 Agent

Advisor:挂在主会话上的只读审查 Agent

Advisor 是一个挂在主会话上的只读审查 Agent,在每个 turn 结束时自动分析 transcript 增量并提供建议,但不执行任何操作,旨在帮助 coding agent 避免走错方向或遗漏边界条件。

语速

这篇文章我们还是聊 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便宜方案。但是实际跑下来发现两个问题:

  1. 占用 token 资源。daemon 还是太昂贵了。
  2. 没有很好的交互机制。不如 omp。

实践

  1. 模型智力差不多的时候(例如都是同一个模型),在执行的时候能及时纠错。
  2. Advisor 较弱的时候,会给出一些错误的提醒,但是偶尔也会说正确的事情。各半。
  3. Advisor 强的情况暂未测试。

以下是机制分析,不读也可以。


角色边界

OMP 如何定义 advisor。

  1. 只读,不是第二执行者。 Advisor 不能编辑文件、不能跑命令、不能批准操作、不能改 session 状态(advisor-watchdog.md 明确「It is not a second executor」)。
  2. 建议可被主 agent 读到,私有上下文读不到。 Advisor 给出的 note 会注入主会话 transcript 或 steering channel;但 Advisor 自己的 system prompt、私有 thinking、WATCHDOG.md 不进主 agent 上下文。
  3. 不审查自己。 渲染下一轮增量前,已注入的 advisor 类型消息会被过滤掉,避免递归审查。

这三条决定了 Advisor 的形态:一个能说话、但不能动手的旁观者。

就像是你的结对编程伙伴。


运行机制

Advisor 的触发不是主 agent 主动调用,而是 turn-end 驱动的后台运行。一次完整循环落在 AdvisorRuntimesrc/advisor/runtime.ts):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 agent turn 结束
      
      
onTurnEnd():取自上次以来的 transcript 增量
        (过滤掉 advisor 自身消息,含 thinking/tool 意图)
      
#pending 入队,#backlog++
      
      
#drain() 后台循环:把增量 markdown 发给 advisor 模型
      
      
advisor 自行决定是否调用 advise 工具
        ├─ 不调用  静默(system prompt:「Prefer silence when on track」)
        └─ 调用    onAdvice 回调  注入主会话

几个关键设计点:

  • 增量而非全量。 #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 拿到的是一个硬隔离的只读工具集readsearchfindadviseadvise-tool.ts:62)。前三个跑在一个 session id 带 -advisor 后缀的独立 ToolSession 上,不共享主 agent 的文件快照、编辑能力或冲突状态。

advise 工具本身的返回对 Advisor 是 useless: true + "Recorded.",真正的副作用是触发 onAdvice 回调——所以 Advisor 调一次工具,等于往主会话送一条建议。


Feature 与用户感官

三档 severity,两种交付方式

advise 工具接受一个 note 和可选的 severityadvise-tool.ts:5-13)。severity 决定是否打断主 agent

severity交付方式语义颜色(终端 badge)
省略 / nit非打断,批次化成一张 transcript 卡片,落点在下个 step boundary清理、简化、低风险边界muted(灰)
concern打断式 steering message方向可能错、漏了关键约束、疑似幻觉 APIwarning(黄)
blocker打断式 steering message继续下去会浪费这个 turn / 产出破损结果error(红)

判定逻辑见 isInterruptingSeverityadvise-tool.ts:53-55):concernblocker 打断,nit 排队。打断式建议走 steering channel,能在下个 steering boundary 中止正在执行的工具。

非打断的 nit 会批次化,卡片正文带固定前缀:

1
Advisor (a senior reviewer watching your work — weigh it, don't blindly obey):

这个前缀刻意写成「weigh it, don’t blindly obey」——主 agent 被告知这只是一个 senior reviewer 的意见,不是命令。

终端里长什么样

Advisor 的建议渲染成一张独立的 transcript 卡片(src/modes/components/advisor-message.ts),复用 IRC 卡片的 glyph + 引用边框约定,让它读起来像「一段旁白」,而不是一轮用户输入:

1
2
3
4
  ℹ Advisor — 2 notes, 1 blocker
  │ [blocker] 这里继续写会引用到已被 compaction 删掉的 session entry,先停下确认
  │ [nit]     用 map 替代 forEach 更贴合返回值约定
  │ … +2 more notes

渲染细节:

  • 标题行带 meta:N notes,有 blocker 时额外标 N blocker(红色)。
  • 每条 note 前是按 severity 着色的 badge(blocker=error / concern=warning / nit=mutedadvisor-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):

1
2
3
4
5
modelRoles:
  advisor: anthropic/claude-sonnet-4-5:medium

advisor:
  enabled: true

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.syncBacklogoff / 1 / 3 / 5,默认偏向吞吐):turn 结束时主 agent 是否、等多久让 Advisor 追平。off 完全不等;1 最接近「每步同步审查」(30 秒上限)。注意它不是让主 agent 等建议内容,而是等 Advisor 处理完增量;建议本身始终异步送达。

设计边界与降级

把容易踩的几条边界收在一起:

  1. 用户主动中断时,Advisor 停止自动 resume。 Esc、collab / ACP / RPC / SDK / extension 的 cancel 都算。一条 concern/blocker 在 run 已停止时只会记成可见卡片,不会强行重启 turn;这条建议会在下次 resume 时(新消息、./c 续接、steer)重新进入上下文(advisor-watchdog.md:94)。
  2. 历史重写会重置 Advisor。 compaction、session 切换/resume、branch/fork 这类 history 替换,以及上下文维护的 re-prime,都会清空 Advisor 私有上下文、回卷游标,下一轮回放当前 bounded transcript。这样 Advisor 不会停留在过时上下文里(runtime.ts:125-134)。
  3. mid-session 启用不会重放全史。 开启时游标 seed 到当前 transcript 长度,避免第一次就把整段旧对话塞给 Advisor(runtime.ts:136-147)。
  4. 独立计费。 Advisor 的 token 和 cost 是单独统计的,/advisor status 报的是 Advisor 自己 transcript 的用量,不和主 agent 混算。

收束

Advisor 把「事后 review 能发现的问题」前移到 turn 边界。它解决的不是「主 agent 不够强」,而是「同一个执行者难以在执行中审视自己」——因此它被设计成只读、可被主 agent 读到建议、且不参与执行的独立角色。

实现上值得注意的取舍是隔离异步:Advisor 跑在隔离的工具会话里、用增量 transcript、后台 drain、失败即降级,保证它给主链路带来的只有收益(意见)而不引入新的阻塞或竞态。advisor-watchdog.md 里有完整的配置、发现机制与上下文维护说明,本文不再重复。