在实现前加入只读研究子 Agent 读取文档并返回结构化摘要,API 幻觉 bug 从约 20% 降至 2.5%,额外 token 成本低于 10%。
我的自主编程 Agent 一直在提交根本不存在类库的 API 代码,因为它先写代码,从来不读文档。我通过加入一个只读的研究子 Agent 修复了这个问题——它在实现之前运行,返回一份简短的结构化简报,并阻止实现者直到简报存在。三个月来,幻觉 API 的 bug 从大约每五个任务出现一个降到了大约每四十个任务出现一个,额外成本不到 token 消耗的 10%。下面是它的工作原理以及我在这个过程中犯过的错误。🚀
我在 Mac mini 上运行一个全自主的实现系统。一个编排器(orchestrator)接收任务,将它们分发给并行的、基于 Claude Code 的实现 Agent,还有一个自愈 Agent 负责清理失败的任务。它向真实项目提交真实代码,基本不需要人工干预。
最初几个月,最烦人的失败模式是这样的:
实现者自信地调用 client.batchUpsert()。这个类库从来没有过 batchUpsert()。它有 upsertMany(),在 v4.2 加入,但参数形状不同。
测试失败,自愈 Agent 尝试三次修复,消耗了 40k tokens,最后放弃并将任务标记为 blocked。我早上醒来看到一堆"blocked"任务,它们的根本原因都一样:Agent 猜了一个 API 而不是去读文档。
我审计了两周内失败的任务,数据很糟糕:
百分之六十的失败是"写之前没看"。这不是智力问题,是流程问题。
有趣的是:实现者是能读文档的。它有网页获取和文件工具。只是它不读,因为当你给一个能力强的模型一个任务时,它的本能是开始产生代码。提示"请先读文档"只帮助了大约两天,然后这条指令就被上下文窗口里的其他内容淹没了。
修复是结构性的,不是基于提示词的。我把"弄清楚这怎么工作"和"实现它"分离成两个不同的 Agent,配备两套不同的工具集。
O[Orchestrator] -->|task + question list| R[Research agent<br/>read-only]
R -->|research brief| O
O -->|task + brief| I[Implementation agent]
I -->|diff| V[Verifier]
V -->|pass / fail| O
O -.->|brief missing or low confidence| R
让这个系统运作的三条规则:
研究 Agent 不能写。它只有 Read、Grep、Glob、网页搜索和网页获取。没有 Edit、没有 Write、没有 Bash。它在物理上就不能"快速修复一下"。
没有简报实现者就不能开始。编排器拒绝分发实现任务,除非该任务的研究简报文件存在且有置信度分数。
简报要短且结构化。不是文档转储,是一份契约。
每份简报都是一个固定格式的小 Markdown 文件。下面是一份真实的例子,做了轻度匿名处理:
# Research brief: task-0417
## Questions
1. How do we bulk-insert rows with the ORM at the pinned version?
2. Does the existing repo already wrap this anywhere?
## Findings
### Q1
- Pinned version is 4.3.1 (from lockfile).
- Bulk insert is `Model.bulkCreate(rows, { updateOnDuplicate: [...] })`.
- `upsertMany` does NOT exist in this version. It was a community plugin.
- Source: node_modules/<orm>/lib/model.js lines 2210-2290, and the 4.x changelog.
### Q2
- Yes. `src/db/batch.ts` exports `insertBatch()` which already handles chunking at 500 rows.
- Two callers use it. Reuse it; don't add a second path.
## Verdict
Use the existing `insertBatch()` helper. Do not touch the ORM directly.
## Confidence: 0.9
## Sources read: 4 files, 1 changelog, 0 web pages
## Staleness risk: low (pinned version, checked lockfile)
重要的四个字段是 Questions、Verdict、Confidence 和 Staleness risk。其他都是辅助证据。实现者先读 Verdict。编排器读 Confidence。
我用带 frontmatter 的 Markdown 文件定义子 Agent。研究 Agent 大致如下:
---
name: researcher
description: Read-only investigation. Answers "how does X actually work"
by reading source, lockfiles, changelogs, and official docs. Returns a
research brief, never code.
tools: Read, Grep, Glob, WebSearch, WebFetch
---
You answer questions about how things actually work. You never propose
an implementation.
Rules:
- Check the pinned version in the lockfile BEFORE reading any docs.
Docs for the wrong major version are worse than no docs.
- Prefer reading node_modules / site-packages source over web docs.
Source can't be out of date for the installed version.
- Search the repo for existing wrappers before answering "how do I call X".
- If two sources disagree, report both and lower your confidence.
- Output ONLY the brief format. No preamble.
那条"先检查 lockfile"的规则是在痛苦的一周后加上的。更多内容见下文。
这个门控本身是很无聊的 shell 代码。越无聊越好。它在每次分发前运行:
brief="state/briefs/${task_id}.md"
if [[ ! -f "$brief" ]]; then
dispatch_agent researcher "$task_id"
exit 0 # come back next tick
fi
confidence=$(grep -E '^## Confidence:' "$brief" | awk '{print $3}')
if (( $(echo "$confidence < 0.6" | bc -l) )); then
# Re-research with the low-confidence sections as new questions
dispatch_agent researcher "$task_id" --refine
exit 0
fi
dispatch_agent implementer "$task_id" --brief "$brief"
我喜欢这个设计的两点:
确定性门控。我不问模型它是否准备好了。一个文件存在,里面有一个数字;或者不存在。
改进是一个有上限的循环。低置信度触发第二次研究,聚焦于弱问题。两次改进后,任务被标记为"需要人工",而不是永远循环。
实现者的提示词把简报逐字注入到顶部,后面跟一行:
The brief above is the source of truth for library APIs and existing helpers. If you believe it is wrong, stop and write why to the task file. Do not work around it.
上面那份简报是类库 API 和现有辅助工具的事实来源。如果你认为它错了,停下来,把原因写到任务文件里。不要绕过它。
最后这句话很重要。在我加入之前,实现者偶尔会读简报,默默不同意,然后按自己的方式来。现在不同意变成了一件可见的事件,我可以 grep 到。
在四个项目的大约 1100 个任务中:
8% 的 token 开销让我意外。我预期是 25% 或更多。结果证明我消除的重试比加入的研究阶段要昂贵得多。一次失败的实现加上三次自愈尝试,比读四个文件的代价更高。
告诉单个 Agent"先研究,再写代码"会衰减。在上下文填满之前有效,然后指令就输给了任务。给研究 Agent 没有写工具让这种分离变成物理性的。它不可能漂移到实现,因为实现是不可能的。
这可以推广:如果你想要一个行为可靠,就移除做另一件事的能力,而不是客气地请求。
这个系统最糟糕的一周是研究 Agent 读了一个类库的最新网页文档,写了一份自信的简报,然后实现者却按我们没有安装的大版本构建。置信度是 0.95。简报对于错误版本完全正确。
现在 Agent 的第一个动作总是读 lockfile 或 pip freeze 输出,而且简报有一个 Staleness risk 字段。node_modules 里的源码永远比网页文档好,因为已安装的源码不可能对已安装版本过时。
我最初加置信度字段是为了自己看。它是装饰性的。直到编排器开始以其作为门控那一天,它才变得有用。一旦一个数字有了消费者,生产它的 Agent 在校准方面会明显更认真,因为低置信度会触发可见的返工。
同样的教训适用于 Agent 的任何结构化输出。如果什么东西都不读那个字段,就删掉它或者接上线路。
"如果你认为简报错了,停下来,把原因写出来"把一种静默失败模式变成了数据。三个月来实现者有 23 次不同意简报。它对了 9 次。那 9 次成了研究 Agent 指令的测试用例。那 14 次错误的不同意几乎都是实现者想要用它在训练时"记住"的更新版 API。
不是所有事情都需要简报。修改变量名、调整 CSS margin、更新版权年份。对这些运行研究浪费 tokens,更糟糕的是让系统对琐碎工作感觉很慢。
我加了一个规模启发式:如果任务涉及不到 2 个文件且没有提到外部类库,编排器写一行自动简报,置信度 1.0,然后继续。这个阈值很蠢。存在的蠢阈值好过我从未抽空构建的智能阈值。
跨任务复用简报。目前每个任务都做全新研究,即使用上一个任务回答过同样的问题。我正在加一个以类库加版本为键的简单索引,这样简报可以复用 7 天,然后过期。
研究作为一个一等 MCP server。我希望其他工具能够问"在这个版本的这个仓库里 X 怎么工作"并得到同样的简报格式回来,而不需要经过编排器。
我还想过把同样的分离用于调试:一个只读的"诊断"Agent在任何修复 Agent 接触代码之前生成一份假设简报。早期迹象很有希望,但我还没有足够的数据来写它。
如果你的编程 Agent 一直在发明 API,修复可能不是更好的模型或更长的提示词。是流程边界。给一个 Agent 眼睛但不给手,给另一个 Agent 手给一份契约,让编排器拒绝跳过第一步。
任何想复现的人使用的版本:截至 2026 年 9 月的 Claude Code 2.x 系列,Node.js 22.x,Python 3.13,macOS 上用纯 Bash 写的编排器。
如果这有用,欢迎在 Dev.to 上关注我,我会发布更多从 24/7 全自主实现系统运行日志。如果你为你的 Agent 构建了不同类型的研究或规划门控,我很想知道它在实际使用中的表现。💡