吃透 AI Agent 开发 · 第 03 篇 · 第一章 · 认知校准
Claude Code、Cursor、Manus 看起来差异很大,但都在处理同一组问题:循环、工具、上下文、记忆、协作和 Harness。
Claude Code、Cursor、Manus 看起来差异很大,但都在处理同一组问题:循环、工具、上下文、记忆、协作和 Harness。
先做一次“换皮测试”
把产品名字暂时遮住,只留下一个任务:“读三份项目文件,提出修改方案,得到确认后再写入”。如果一个系统能回答模型看到了什么、哪一步允许写入、长结果放在哪里、失败后怎样继续,它就拥有一副可解释的骨架;如果只能说“框架内部会处理”,那只是把复杂度藏起来。
同一个 Agent 换成客服、代码助手或研究助手,输入和工具会变,六类责任不会凭空消失。Loop 负责推进,Tool System 负责副作用,Context 负责信息取舍,Memory 负责跨轮事实,Multi-Agent 负责分工,Harness 负责把它们装成可运行的产品。把这六个词当作检查问题,比背产品术语更耐用。
六个支柱不是六个目录
下面的图不是推荐的文件夹结构,而是一张“谁对什么负责”的地图。一个项目可以把 Loop 和 Context 写在同一个模块,也可以把 Memory 存成文件;只要交接对象和失败责任清楚,物理目录怎么排都不是关键。
flowchart TB
Request[任务与约束] --> Loop[Agent Loop]
Loop --> Context[Context Engineering]
Loop --> Tools[Tool System]
Tools --> State[状态与证据]
State --> Memory[Memory]
Loop --> Team[Multi-Agent]
Context --> Harness[Harness]
Tools --> Harness
Memory --> Harness
Team --> Harness
Loop:让“下一步”有来源
Loop 不是 while 循环的别名,而是一次次把模型判断、工具结果、取消信号和停止理由串起来的协议。它要能回答:本轮请求带着哪些消息进入模型?模型返回了文本还是工具意图?工具完成后,什么事实被追加进下一轮?什么时候结束,谁决定结束?
Tool System:把意图挡在副作用前面
工具层的职责不是把函数挂到对象上,而是把 schema、参数校验、权限、Hook、超时、审计和结果协议放进同一条入口。模型可以提出“删除这个文件”的意图,但不能凭一句自然语言绕过路径检查。
Context 与 Memory:同一份信息不代表同一种生命周期
当前任务中的文件摘要适合放进动态上下文,用户明确要求长期保存的偏好才可能进入 Memory。两者都叫“上下文”时最容易互相污染:压缩后的摘要被当成事实,旧项目记忆被当成当前指令,最后模型看似知道更多,实际更难判断哪些内容可信。
Multi-Agent 与 Harness:扩大能力也扩大控制面
拆成多个 Agent 不会自动获得并行收益。子任务需要输入边界、权限、完成标准和结果句柄;Harness 则负责配置、会话、日志、恢复、界面和版本变更。没有 Harness,演示里的 Agent 很聪明,部署后的 Agent 却无法重放。
用三个产品假设做比较
| 观察角度 | 代码助手 | 客服助手 | 研究助手 |
|---|---|---|---|
| 最贵的错误 | 改错文件或越权执行 | 给出过期或未经授权的承诺 | 把推测写成有来源的事实 |
| 最重要的支柱 | Tool System、Loop | Context、Memory | Context、Evidence、Eval |
| 必须留下的证据 | diff、路径、审批 | 引用版本、用户确认 | 文档来源、检索片段、置信度 |
| 不能交给模型独自决定 | 写入范围和停止条件 | 权限和赔付规则 | 结论是否足够可靠 |
这张比较的用处,是提醒我们不要照搬别人的目录。代码助手把文件系统放在中心,不代表客服也要复制同样的工具注册;研究助手的核心不是拥有更多工具,而是让每条结论能回到来源。
真实工程参考:按问题追踪代码
q-code 只作为可选的 TypeScript 参照。阅读时不要从入口一路通读,而是带着一个问题走到答案:
| 阅读顺序 | 源码路径 | 现场要确认的事实 |
|---|---|---|
| 1. Loop 入口 | src/agent/loop.ts |
一轮请求如何产生下一轮消息 |
| 2. 工具边界 | src/tools/registry.ts |
谁把模型意图变成可审计调用 |
| 3. 上下文组装 | src/context/prompt-builder.ts |
稳定规则和动态事实如何分开 |
| 4. 子 Agent | src/agents/registry.ts |
分工、生命周期和结果如何回到主流程 |
| 5. 证据链 | src/observability/audit.ts |
哪些事件能支持重放和定位 |
读完这五处,应该能画出一次请求从输入到证据的边界图,而不是记住五个文件名。
一个失败反例:六个支柱都变成全局变量
看到一个新框架就重新学习一套名词,最后只记住 API 名称,没有形成判断能力。更糟的实现是:Loop 直接改全局上下文,工具函数自己写日志,Memory 把每句聊天都保存,子 Agent 共享所有权限,界面再从异常文本里猜状态。它们在 happy path 上可以工作,遇到取消、重试或多人并发就互相覆盖。
修复不需要马上拆成六个包。先选一次真实请求,给每次交接补三个字段:输入来源、允许动作、失败接手者。哪一个字段写不出来,哪一个边界就应该先被澄清。架构图不是终点,能在故障现场指出责任归属才算有用。
留下一张可复查的地图
练习:选一个你正在做的 Agent,把它画成六个支柱,但每个支柱只写三行:拥有的状态、允许的副作用、失败时交给谁。再把一个“看起来能工作”的功能换成另一个产品场景,检查图中哪些职责仍然成立、哪些职责必须重新命名。
完成后保存两份产物:一张六支柱地图,以及一张“模型不能自行决定什么”的清单。以后换模型、换 SDK 或把单 Agent 拆成团队时,先拿这两份东西做差异对照。
支柱之间会互相放大,也会互相拖累
六大支柱并不是六项可以独立打分的功能。一个工具系统权限严密,但上下文没有告诉模型哪些路径可读,模型会反复撞墙;Memory 保存得很完整,但 Harness 没有版本和迁移,旧结构一升级就恢复失败;Multi-Agent 并发很多,但 Eval 只看最终文案,重复劳动和错误合并就不会被发现。
可以把它们看成一条承重链。最弱的部分往往决定用户真正感受到的可靠性。模型升级把任务成功率从 70% 提到 85%,如果写工具仍可越界,产品风险不会因此下降 15%。相反,补上审批、回滚和证据,可能不改变模型基准分,却会显著改变用户敢不敢授权执行。
下面是一份架构审计切片,它不是配置模板,而是某次代码评审留下的现场记录:
system: support-agent
task_sample: "查询退款进度并解释下一步"
pillars:
loop:
observed: "最多 8 步,有 timeout;没有循环检测"
evidence: "trace/run-184.jsonl"
tools:
observed: "订单查询只读;退款工具要求人工确认"
evidence: "policy/refund-v3.json"
context:
observed: "注入订单摘要和当前退款规则版本"
gap: "没有显示规则生效日期"
memory:
observed: "仅保存用户明确确认的联系方式"
multi_agent:
observed: "未启用,当前任务无并行收益"
harness:
observed: "会话可恢复;外部 CRM 超时会降级"
decision: "先补规则日期,再扩大灰度"
这份数据最重要的一行是 multi_agent: 未启用。支柱是检查维度,不是功能采购清单。没有并行收益时,不实现多 Agent 反而是成熟选择;关键是团队知道以后在哪些条件下才需要它。
用一次客服任务观察六次交接
用户问“上周申请的退款为什么还没到”。Loop 先决定缺少订单号;Context 从当前会话取得已验证的订单引用;Tool System 查询支付平台;返回结果显示银行处理中;Memory 不应把“银行一般三天到账”保存成用户事实;若支付平台和银行状态需要分别查询,Multi-Agent 才可能并行;Harness 负责超时、重试、脱敏日志和页面状态。
此时如果回答错误,六个支柱提供了不同诊断方向:
- Loop 是否在证据不足时过早结束。
- Tool System 是否调用了错误账户或过期接口。
- Context 是否混入上一笔订单。
- Memory 是否保存了未经确认的订单号。
- Multi-Agent 是否把两个不同支付流水合并。
- Harness 是否在超时后把缓存结果当成实时结果。
这比一句“模型幻觉了”更有用。幻觉描述现象,支柱地图帮助找到可改变的系统条件。
三种产品,支柱重心为何不同
代码 Agent 的副作用集中在文件和 Shell,所以 Tool System、回滚和工作区状态通常最先成为瓶颈。客服 Agent 面对身份、权限与政策时效,Context 和授权证据更重。研究 Agent 可能几乎没有写工具,却需要检索、引用、来源新鲜度和结论不确定性。
支柱的名字可以不变,验收材料必须跟着领域变化:
代码 Agent:diff + test exit code + approval receipt
客服 Agent:identity scope + policy version + action confirmation
研究 Agent:source URL + retrieved passage + publication date
同样叫 Evidence,三行内容完全不同。若团队拿代码 Agent 的“测试通过”标准去验收研究助手,就会漏掉最重要的引用真实性;拿研究助手的“引用完整”去验收写代码,则无法证明工作区真的可运行。
Harness 不是最后再包一层壳
不少项目先完成模型、工具和 Prompt,准备上线时才说“补一个 Harness”。但取消信号、会话编号、日志关联、配置覆盖和恢复协议都会进入核心调用签名,太晚补就要重写大量路径。
第一天不需要完整 Dashboard,却应该有最小运行信封:runId、sessionId、cwd、abortSignal、eventSink、usageSink。它们不决定模型做什么,只让每次执行可以被定位、停止和计量。这个信封越稳定,未来接 TUI、Web 或后台队列越容易。
什么时候该升级某个支柱
升级信号来自重复故障,不来自功能清单。出现以下现场时,才有明确投入方向:
- Loop 连续调用相同工具且输入不变,需要循环检测或停止策略。
- 工具拒绝理由散在不同模块,需要统一 Registry 和策略结果。
- 每轮都全量塞项目文件,需要 Context 预算与 JIT 读取。
- 用户反复纠正同一长期偏好,才考虑受控 Memory。
- 独立搜索明显占据串行时间,才考虑只读并行 SubAgent。
- 故障只能靠重新跑,说明 Harness 的 trace、artifact 或恢复不足。
这样规划路线,六大支柱就不会变成一张“全部要做”的大饼,而是一套根据现场选择下一项投资的坐标系。