CrabNote螃蟹笔记

你不知道的 Agent:原理、架构与工程实践

你不知道的 Agent:原理、架构与工程实践

你不知道的 Agent:原理、架构与工程实践 你不知道的 Agent:原理、架构与工程实践 Modified March 20 即使模型能力没有任何变化,评测环境越紧,基础设施错误率就越高。在 1x 资源限制下,infra error 接近 6.5%,放开到 Uncapped 后,错误率接近 0,但模型的平均得分几乎没有变化。也就是说,如果在资源受限的评测环境中看到性能下降,第一步先排查基础设施问题,而不是修改 Agent。 为什么能力评测和回归测试要分开 这两者经常被混用,但生命周期和用途完全不同: 能力评测 ,衡量的是系统在最好情况下能做到什么,使用 Pass@k,允许多次尝试,用来寻找能力上限; 回归测试套件 ,衡量的是已有功能是否被改坏,使用 Pass^k,每次运行都应通过,用来防止上线后出现静默退化。 两者一旦混用,就很容易带来误判,回归测试过于宽松会漏掉问题,能力评测过于严格又会让每一次小改动都触发告警。 如何从零搭起评测体系 没有完整体系的情况下,先把最小闭环搭起来:收集 20 到 50 个来自真实失败的案例,为每个案例写明确的验收标准,优先用代码评分器而不是 LLM judge,每次变更后都跑一遍完整评估,并定期人工抽查完整执行记录,而不只看聚合数字。 9. 如何追踪 Agent 的执行过程 先把 Trace 能力搭起来,没有完整记录,失败案例就没法稳定复现。Agent 出现问题时,传统只监控延迟和错误率的 APM 往往帮助有限,接口层看起来可能一切正常,但真正的问题出在模型某一轮做出了错误决策,只有回看完整 Trace 才能定位。 对 Agent 来说,可观测性的重点不只是看系统有没有报错,而是把每一步决策过程保留下来。Agent 处理的是自然语言,质量很难压成单一指标,排查时通常需要工程师、产品和领域专家一起看。 Code block Plain Text Copy 每次 Agent 运行: ├── 完整 Prompt,含系统提示 ├── 多轮交互的完整 messages[] ├── 每次工具调用 + 参数 + 返回值 ├── 推理链,如有 thinking 模式 ├── 最终输出 └── token 消耗 + 延迟 如果条件允许,这套系统还应具备语义检索能力,能够查询「哪些 Trace 里 Agent 混淆了两种工具」这类问题,而不只是做精确字符串匹配。人工审查的效率大约是每小时 50 到 100 条 Trace,如果系统每天处理 1000 个请求,就需要 10 到 20 小时人工投入,自动化不是加分项,而是前提条件。 两层可观测性如何分工 第一层是人工抽样标注,基于规则采样错误案例、长对话和用户负反馈,由人工判断执行质量和失败原因,主要用来摸清失败模式,并给第二层提供校准数据。 第二层是 LLM 自动评估,对更大范围的 Trace 做全量覆盖,以第一层标注结果作为校准依据。只跑第二层,评分标准很容易漂移,只靠第一层,规模上又覆盖不了真实流量,两层要一起用。 在线评测如何做采样 第一层和第二层之间还有一个关键的工程细节。全量运行在线评测的成本通常不低,但完全随机采样又很容易错过关键 Trace。更稳妥的做法,是对 10% 到 20% 的 Trace 运行在线评测,再让采样按规则路由,而不是完全随机: • 负反馈触发 :用户明确表示不满意的 Trace,100% 进队列 • 高成本对话 :token 消耗超过阈值的,优先审查,往往代表 Agent 在绕圈子 • 时间窗口采样 :每天固定时间段随机采,保持对正常流量的覆盖 • 模型或 Prompt 变更后 :头 48 小时全量审查,确认没有退化 把 Agent 的执行步骤发布成事件流之后,可观测性才真正有了底座。 事件流为什么更适合做底座 把 Agent 的执行步骤发布成事件流之后,可观测性才真正有了底座。 Agent Loop 在 tool start、tool end、turn end 三个节点发出事件,完整 Trace 同步落盘,再分发给日志系统、UI 更新、在线评测、人工审查队列这些下游。人工抽样标注和 LLM 自动评估两层评测共享同一份 Trace 数据,互相校准。事件一次发布,多路消费,主循环不需要为了任何下游改代码。 Code block Markdown Copy Agent 执行时 emit 事件 on tool start: emit { type, tool name, input, timestamp } on tool end: emit { type, tool name, result, duration } on turn end: emit { type, turn output } 多路下游订阅,Agent 核心代码不变 agent.on("event") write to logs agent.on("event") update ui agent.on("event") send to eval framework 10. 用 OpenClaw 看 Agent 如何落地 前面几节讲的是 Agent 的控制流、上下文、工具、记忆、评测和安全,这一节换成 OpenClaw,看看这些设计在系统里是怎么落下去的。上下文分层、Skills 延迟加载、结构化通信协议、文件系统状态,在 OpenClaw 里都能找到对应实现,后面就拿这个实现一层层往下看。 整体架构:四层解耦 OpenClaw 可以看成四层,分别解决渠道接入、消息解耦、Agent 调度和工具执行。最上面是负责连接和消息分发的 WebSocket 服务,底部是 SOUL.md、MEMORY.md、Skills 等配置文件,下面这张表把各层职责和关键设计放在一起看会更清楚。 1. Gateway:WebSocket 服务,统一路由消息,Channel 和 Agent 不直接通信 2. Channel 适配器:23+ 渠道统一接口,新增渠道不改 Agent 代码 3. Pi Agent:维护主循环、会话状态、调度,核心循环和渠道完全解耦 4. 工具集:shell/fs/web/browser/MCP,按 ACI 原则设计 5. 上下文+记忆:Skills 延迟加载 + MEMORY.md,50% token 阈值自动整合 前面主要看各层负责什么,下面这张图再把它们在系统里的连接关系串起来。 消息总线如何把渠道和 Agent 隔开 加上定时任务之后,系统不再只有用户消息这一个入口,OpenClaw 就在渠道和 Agent 之间加了一层 MessageBus,Channel 只管收发,AgentLoop 只管处理,互不干扰。 Code block TypeScript Copy // 入站消息结构,Agent 不知道来自哪个平台 const inbound = { channel, session key, content }; // 每个渠道只需实现三个方法 class ChannelAdapter { start() {} stop() {} send(session key, text) {} } 一条最小可运行链路 如果只看最小主链路,OpenClaw 的流程其实很直接:Channel 适配器把消息写入 MessageBus,AgentLoop 从 Bus 中消费消息,处理完成后再把结果发回去。 Code block TypeScript Copy // MessageBus:渠道和 Agent 之间的解耦层 class MessageBus { async consumeInbound() { / 从队列取下一条消息 / } async publishOutbound(msg) { / 路由到对应渠道发出 / } } // AgentLoop:消费消息,驱动 ReAct 循环 class AgentLoop { constructor(bus, provider, workspace) { this.bus = bus; this.provider = provider; this.tools = registerDefaultTools(workspace); // shell、fs、web、message、cron this.sessions = new SessionManager(workspace); // 持久化会话历史 this.memory = new MemoryConsolidator(workspace, provider); // 跨会话记忆整合 } async run() { while (true) { const msg = await this.bus.consumeInbound(); this.dispatch(msg); // 不 await:不同 session 的消息并发处理,互不阻塞 } } async dispatch(msg) { const session = this.sessions.getOrCreate(msg.sessionKey); await this.memory.maybeConsolidate(session); // token 超阈值时自动整合记忆 const messages = buildContext(session.history, msg.content); const { text, allMessages } = await this.runLoop(messages); session.save(allMessages); await this.bus.publishOutbound({ channel: msg.channel, content: text }); } async runLoop(messages) { for (let i = 0; i < MAX ITER; i++) { const resp = await this.provider.chat(messages, this.tools.definitions()); if (resp.hasToolCalls) { for (const call of resp.toolCalls) { const result = await this.tools.execute(call.name, call.args); messages = addToolResult(messages, call.id, result); } } else { return { text: resp.content, allMessages: messages }; // 无工具调用,本轮结束 } } } } // 入口:接上渠道,启动 const bus = new MessageBus(); new TelegramChannel(bus, { allowedIds }).start(); // Channel 只负责收发 new AgentLoop(bus, new ClaudeProvider(), WORKSPACE).run(); 即使模型能力没有任何变化,评测环境越紧,基础设施错误率就越高。在 1x 资源限制下,infra error 接近 6.5%,放开到 Uncapped 后,错误率接近 0,但模型的平均得分几乎没有变化。也就是说,如果在资源受限的评测环境中看到性能下降,第一步先排查基础设施问题,而不是修改 Agent。 为什么能力评测和回归测试要分开 这两者经常被混用,但生命周期和用途完全不同: 能力评测 ,衡量的是系统在最好情况下能做到什么,使用 Pass@k,允许多次尝试,用来寻找能力上限; 回归测试套件 ,衡量的是已有功能是否被改坏,使用 Pass^k,每次运行都应通过,用来防止上线后出现静默退化。 两者一旦混用,就很容易带来误判,回归测试过于宽松会漏掉问题,能力评测过于严格又会让每一次小改动都触发告警。 如何从零搭起评测体系 没有完整体系的情况下,先把最小闭环搭起来:收集 20 到 50 个来自真实失败的案例,为每个案例写明确的验收标准,优先用代码评分器而不是 LLM judge,每次变更后都跑一遍完整评估,并定期人工抽查完整执行记录,而不只看聚合数字。 9. 如何追踪 Agent 的执行过程 先把 Trace 能力搭起来,没有完整记录,失败案例就没法稳定复现。Agent 出现问题时,传统只监控延迟和错误率的 APM 往往帮助有限,接口层看起来可能一切正常,但真正的问题出在模型某一轮做出了错误决策,只有回看完整 Trace 才能定位。 对 Agent 来说,可观测性的重点不只是看系统有没有报错,而是把每一步决策过程保留下来。Agent 处理的是自然语言,质量很难压成单一指标,排查时通常需要工程师、产品和领域专家一起看。 如果条件允许,这套系统还应具备语义检索能力,能够查询「哪些 Trace 里 Agent 混淆了两种工具」这类问题,而不只是做精确字符串匹配。人工审查的效率大约是每小时 50 到 100 条 Trace,如果系统每天处理 1000 个请求,就需要 10 到 20 小时人工投入,自动化不是加分项,而是前提条件。 两层可观测性如何分工 第一层是人工抽样标注,基于规则采样错误案例、长对话和用户负反馈,由人工判断执行质量和失败原因,主要用来摸清失败模式,并给第二层提供校准数据。 第二层是 LLM 自动评估,对更大范围的 Trace 做全量覆盖,以第一层标注结果作为校准依据。只跑第二层,评分标准很容易漂移,只靠第一层,规模上又覆盖不了真实流量,两层要一起用。 在线评测如何做采样 第一层和第二层之间还有一个关键的工程细节。全量运行在线评测的成本通常不低,但完全随机采样又很容易错过关键 Trace。更稳妥的做法,是对 10% 到 20% 的 Trace 运行在线评测,再让采样按规则路由,而不是完全随机: • 负反馈触发 :用户明确表示不满意的 Trace,100% 进队列 • 高成本对话 :token 消耗超过阈值的,优先审查,往往代表 Agent 在绕圈子 • 时间窗口采样 :每天固定时间段随机采,保持对正常流量的覆盖 • 模型或 Prompt 变更后 :头 48 小时全量审查,确认没有退化 把 Agent 的执行步骤发布成事件流之后,可观测性才真正有了底座。 事件流为什么更适合做底座 把 Agent 的执行步骤发布成事件流之后,可观测性才真正有了底座。 Agent Loop 在 tool start、tool end、turn end 三个节点发出事件,完整 Trace 同步落盘,再分发给日志系统、UI 更新、在线评测、人工审查队列这些下游。人工抽样标注和 LLM 自动评估两层评测共享同一份 Trace 数据,互相校准。事件一次发布,多路消费,主循环不需要为了任何下游改代码。 10. 用 OpenClaw 看 Agent 如何落地 前面几节讲的是 Agent 的控制流、上下文、工具、记忆、评测和安全,这一节换成 OpenClaw,看看这些设计在系统里是怎么落下去的。上下文分层、Skills 延迟加载、结构化通信协议、文件系统状态,在 OpenClaw 里都能找到对应实现,后面就拿这个实现一层层往下看。 整体架构:四层解耦 OpenClaw 可以看成四层,分别解决渠道接入、消息解耦、Agent 调度和工具执行。最上面是负责连接和消息分发的 WebSocket 服务,底部是 SOUL.md、MEMORY.md、Skills 等配置文件,下面这张表把各层职责和关键设计放在一起看会更清楚。 1. Gateway:WebSocket 服务,统一路由消息,Channel 和 Agent 不直接通信 2. Channel 适配器:23+ 渠道统一接口,新增渠道不改 Agent 代码 3. Pi Agent:维护主循环、会话状态、调度,核心循环和渠道完全解耦 4. 工具集:shell/fs/web/browser/MCP,按 ACI 原则设计 5. 上下文+记忆:Skills 延迟加载 + MEMORY.md,50% token 阈值自动整合 前面主要看各层负责什么,下面这张图再把它们在系统里的连接关系串起来。 消息总线如何把渠道和 Agent 隔开 加上定时任务之后,系统不再只有用户消息这一个入口,OpenClaw 就在渠道和 Agent 之间加了一层 MessageBus,Channel 只管收发,AgentLoop 只管处理,互不干扰。 一条最小可运行链路 如果只看最小主链路,OpenClaw 的流程其实很直接:Channel 适配器把消息写入 MessageBus,AgentLoop 从 Bus 中消费消息,处理完成后再把结果发回去。 任务超过半小时,崩溃恢复是必选项,不是可选项。 为什么安全边界要先于功能 开放 Shell 权限之后,git push、rm、数据库写入这类操作都可能被触发,安全边界要先于功能。三件事必须先到位:谁能用、能在哪用、做了什么可以追踪。 白名单授权,只有授权用户可以触发 Agent: 工作空间隔离 ,shell 工具需要强制进行路径检查,越出工作空间目录就直接报错: 操作审计日志 ,每次执行都记一笔,方便后续审计和排查: 安全和可用性的两层兜底 除了权限、路径和审计,系统还要补两层兜底,一层防内容注入,一层防模型服务故障。 Prompt Injection 白名单和工作空间隔离解决的是越界操作,但还不够。Agent 读取的网页、邮件、文档本身也可能带攻击指令,这就是 Prompt Injection。单靠输入过滤基本挡不住,更实用的做法是按 source sink 去拆。source 就是不可信输入从哪里进来,sink 就是这些输入最后可能触发的危险操作。重点不是识别所有攻击,而是让 Agent 即使被注入,也没有机会把危险动作真正执行出去: • 最小权限 :不给 Agent 不需要的工具,没有 sink,source 侧的注入就无法落地 • 敏感操作显式确认 :向第三方传信息、调用写操作,执行前必须让用户确认,不能静默执行 • 标注外部内容边界 :外部拉取的内容进入上下文时显式标注来源,声明哪些内容不可信 • 关键路径加独立 LLM 验证 :同一上下文中的 Agent 很难判断自己是否已被注入,关键操作引入独立 LLM 复核更稳妥 最直接的做法,就是先把外部内容明确标成「不可信输入」,不要和系统提示混在一起。下面这个例子表达的就是这个意思: 敏感操作的显式确认也一样,本质上是把「先确认再执行」做成系统步骤,而不是让模型自己判断。 Provider 故障切换 模型服务出故障是常态,不是例外。Anthropic 返回 503、OpenAI 触发限速都很常见,所以这里要加一层 fallback,当前 Provider 挂了就自动切下一个,不用人盯: 工程实现应该遵循什么顺序 1. 单渠道先跑通 ,Telegram Agent Telegram 完整链路,不要第一版就抽象多渠道 2. 安全边界先于功能 ,工作空间隔离、白名单、参数验证,加任何新功能之前就要到位 3. 记忆整合要早做 ,不加整合,第 20 轮对话之后基本就垮了 4. Skills 先于新工具 ,领域知识用文档管理,比加新工具更灵活 5. 第一个失败就建评测 ,把第一个真实失败案例转成测试用例,不要等积累够了再开始 11. Agent 落地里的常见反模式 这类问题都很常见,很多看起来像模型能力不够,回头看其实是工程约束没立住: 1. 系统提示当知识库:越来越长,关键规则被忽略,约定留提示,知识移 Skills 2. 工具数量失控:Agent 频繁选错工具,合并重叠工具,明确命名空间 3. 验证闭环缺失:Agent 说完成了但没法验证,每类任务绑验收标准 4. 多 Agent 无边界:状态漂移,故障归因困难,明确角色权限,worktree 隔离 5. 记忆不整合:长对话第 20 轮后决策质量下降,监控 token,超阈值自动触发 6. 没有评测:改了一个地方不知道有没有引入回归,失败案例立刻转测试用例 7. 过早引入多 Agent:协调开销超过并行收益,先验证单 Agent 上限再扩展 8. 约束靠期望不靠机制:规则在文档里 Agent 选择性遵守,改用工具验证 / Linter / Hook 12. 收尾一下 前面几节其实都在讲同一件事,Agent 能不能稳定,不只看模型,也看 Harness。上下文管理、工具边界、记忆整合、评测、可观测性和安全边界,单拆出来都不难,难的是把它们一起做对。 多 Agent 也一样,先解决隔离,再谈并行。评测也一样,先保证评测可信,再去改 Agent。很多问题表面上像模型不够强,实际是任务没定义清楚,验收标准没立住,或者系统边界没收好。现阶段最值得投入的,还是把验证、上下文、工具和评测这些基础工程打牢。如果大家有更多 Agent 开发上的经验和技巧,也欢迎一起交流。 参考资料 1. OpenAI, Harness engineering: leveraging Codex in an agent first world, 2026 02 18 Harness engineering: leveraging Codex in an agent first world 2. Anthropic, Building a C compiler with a team of parallel Claudes, 2026 02 05 Building a C compiler with a team of parallel Claudes 3. Andrej Karpathy, karpathy/autoresearch, 2025 karpathy/autoresearch 4. Cloudflare, How we rebuilt Next.js with AI in one week, 2026 02 17 How we rebuilt Next.js with AI in one week 5. Simon Willison, I ported JustHTML from Python to JavaScript with Codex CLI, 2025 12 15 I ported JustHTML from Python to JavaScript with Codex CLI 6. Anthropic, Introducing Agent Skills, 2025 10 16 Introducing Agent Skills 7. Anthropic, Managing context on the Claude Developer Platform, 2025 09 29 Managing context on the Claude Developer Platform 8. LangChain, State of Agent Engineering, 2026 State of Agent Engineering 9. Anthropic, Measuring AI agent autonomy in practice, 2026 02 18 Measuring AI agent autonomy in practice 10. OpenAI, Designing AI agents to resist prompt injection, 2026 03 11 Designing AI agents to resist prompt injection 11. Anthropic, Demystifying evals for AI agents, 2025 09 17 Demystifying evals for AI agents 🔗 原文链接: https://x.com/HiTw93/status/2034627... https://x.com/HiTw93/status/2034627... 0. 太长不读 在写完「你不知道的 Claude Code:架构、治理与工程实践」之后,发现自己对 Agent 底层的理解还不够深入。加上团队在 Agent 方向已经有不少业务落地经验,一直缺少一份系统梳理,所以我又把资料、开源实现和自己写的代码一起过了一遍,最后整理成了这篇文章。 这篇文章主要讲 Agent 架构里几块最影响工程效果的内容,包括控制流、上下文工程、工具设计、记忆、多 Agent 组织、评测、追踪和安全,最后再用 OpenClaw 的实现把这些设计原则串起来看一遍。 整理下来,有几处判断和我原来想的不太一样。更贵的模型带来的提升,很多时候没有想象中那么大,反而 Harness 和验证测试质量对成功率的影响更大,调试 Agent 行为时,也应优先检查工具定义,因为多数工具选择错误都出在描述不准确。另外,评测系统本身的问题,很多时候比 Agent 出问题更难发现。如果一直在 Agent 代码上反复调,效果未必明显,下面文章应该能给你这些问题答疑。 1. Agent Loop 的基本运转方式 Agent Loop 的核心实现逻辑抽象后其实不到 20 行代码: 对应的控制流如下,感知 决策 行动 反馈四个阶段不断循环,直到模型返回纯文本为止: 看过不少 Agent 实现和官方 SDK,结构都差不多。循环本身相当稳定,从最小实现一路扩展到支持子 Agent、上下文压缩和 Skills 加载,主循环基本没有变化,新增能力通常都是叠加在循环外部,而不是改动循环内部。 新能力基本只通过三种方式接入:扩展工具集和 handler、调整系统提示结构、把状态外化到文件或数据库。不应该让循环体本身变成一个巨大的状态机,模型负责推理,外部系统负责状态和边界,一旦这个分工确定下来,核心循环逻辑就很少需要频繁调整了。 Workflow 和 Agent 有什么区别 Anthropic 对这两类系统有一个直接区分:执行路径由代码预先写死的是 Workflow,由 LLM 动态决定下一步的是 Agent,核心区别在于控制权掌握在谁手里。现实中很多标着 Agent 的产品,深入看其实更接近 Workflow,不过两者本身并无高下之分,真正重要的是给任务找到更适合的解决方案。 放在一张图里看,会更直观: 五种常见控制模式 大多数 AI 系统拆开看,其实都是这五种模式的组合。很多场景并不需要完整的 Agent 自主权,把其中几种模式搭起来就够了,关键还是看任务本身适合哪一种设计。 1. 提示链 Prompt Chaining :任务拆成顺序步骤,每步 LLM 处理上一步的输出,中间可加代码检查点,适合生成后翻译、先写大纲再写正文这类线性流程。 2. 路由 Routing :对输入分类,定向到对应的专用处理流程,简单问题走轻量模型,复杂问题走强模型,技术咨询和账单查询走不同逻辑。 3. 并行 Parallelization :两种变体:分段法把任务拆成独立子任务并发跑,投票法把同一任务跑多次取共识,适合高风险决策或需要多视角的场景。 4. 编排器 工作者 Orchestrator Workers :中央 LLM 动态分解任务,委派给工作者 LLM,综合结果。nanobot 的 spawn 工具和 learn claude code 的子 Agent 模式都是这个原型。 5. 评估器 优化器 Evaluator Optimizer :生成器产出,评估器给反馈,循环直到达标,适合翻译、创意写作这类质量标准难以用代码精确定义的任务。 上面这些模式解决的是控制流怎么搭,下面再看另一个更工程的问题,系统为什么能跑稳。 2. 为什么 Harness 比模型更关键 Harness 是指围绕 Agent 构建的测试、验证与约束基础设施,这里的 Harness 至少包括四个部分:验收基线、执行边界、反馈信号和回退手段。 下面三个案例角度不同,但讲的是同一件事。模型虽然重要,但决定系统能不能收敛的,往往是这些外围工程条件。这个判断在代码编写、编译器实现这类高可验证任务上最成立,但在开放式研究、多轮协商这类弱验证任务里,模型上限本身仍然更关键。 OpenAI Codex 的做法 3 个工程师 5 个月写了百万行代码,将近 1500 个 PR,是传统开发速度的 10 倍。这个案例更值得看的是,这么快的产出背后,哪些工程约束在起作用: 1. 代码库结构是 Agent 的导航信号 :清晰的目录结构、命名约定和模块边界会成为 Agent 的隐式引导,如果代码库本身缺乏结构化约束,Agent 的修改行为也会随之变得混乱。 2. 约束编码化而非文档化 :写在文档中的规范很容易被 Agent 忽略,而被编码进 Linter、类型系统或 CI 规则中的约束,才具备可执行性。 3. 基于执行日志的自验证闭环 :Agent 在完成操作后,通过查询执行日志或系统状态来确认修改确实生效,避免仅凭一次生成结果就认为任务完成。 4. 最小化合并阻力 :在高吞吐开发环境中,等待人工审查的成本往往高于修复小错误的成本,团队需要通过完善的自动化测试体系,真正建立对自动化修改的信任。 这个案例里比较关键的一点是,他们并没有把 Codex 仅仅当作代码生成器来用,而是为它配套了一整套按任务临时创建、任务完成后即销毁的可观测性栈,让 Agent 可以直接利用日志、指标和追踪来理解、验证并修正系统行为,从而把代码修改、运行验证和结果反馈串成一个闭环。 上图展示了这套可观测性栈的完整数据流:应用产生的日志、指标和追踪数据先汇集到可观测性栈,再通过统一查询接口暴露给 Codex。Codex 查询这些数据进行分析、关联和推理,生成代码修改,应用修改后重启服务并重新运行工作负载进行测试,测试结果再次进入可观测性栈,形成循环。这个架构的关键在于 Agent 能主动查询和理解系统状态,而不是被动等待人工告知错误。 Anthropic 的 C 编译器实验 他们通过 16 个并行 Agent,运行约 2000 个 Claude Code 会话,花费约 $20,000 API 成本,在两周内从零实现了一个可以编译 Linux 6.9 的 C 编译器。这个实验的数据很突出,但从工程角度看,更值得关注的是它怎么处理回归和收敛问题。 最终产出约 10 万行 Rust 代码,不仅能编译 Linux Kernel,还能编译 PostgreSQL、SQLite、Redis、FFmpeg、QEMU,并且通过了 99% 的 GCC torture test。 在接近 Opus 能力极限时,也遇到了一个非常真实的软件工程问题,每完成一个新功能,常常连带破坏若干已有功能,回归很多,最后项目能稳定推进,主要依赖三个关键工程判断: 1. 高质量测试先行 :Agent 只有在有清晰测试的情况下,才会朝正确方向优化,否则只会高效地写 Bug。 2. 用 GCC 做对照验证 :用 GCC 的编译结果作为基准,通过对比和二分定位 Bug,而不是依赖 Agent 互相 Review。 3. 角色专业化分工 :不同 Agent 分别负责重构、性能优化、代码质量等职责,避免所有 Agent 同时改同一类问题。 Karpathy 的 Autoresearcher Autoresearcher 是 Karpathy 做的一个实验项目,让 Agent 自主修改训练脚本、跑实验、评估改进是否有效,整个实现只有几百行代码,是理解 Agent Harness 设计的一个很好的例子。 图里是 83 次实验的结果。横轴是实验序号,纵轴是验证集 BPB,越低越好。绿色圆点是保留的 15 个有效改进,灰色点是废弃的尝试。失败是常态,但单次失败成本很低,直接回滚就行。 Agent 能改的只有train.py这个文件,评估指标固定为 bits per byte,也就是 bpb,越低越好。每次实验最多跑 5 分钟,结果更好就 commit 作为新基准,结果变差就直接 revert。所有实验结果记录在 results.tsv 中,不进入 git 历史。整个系统的控制平面是 program.md,相当于 Agent 的操作手册,里面定义了工作流程、可修改的文件边界、日志格式和崩溃恢复步骤,其中几条设计很值得参考: train.py 1. 单文件搜索空间 :Agent 只能修改train.py,数据处理和评估脚本保持只读,避免通过修改评估逻辑来刷分。 train.py 2. 固定时间预算 :每次实验只允许运行 5 分钟,这样系统优化的目标就变成在有限时间内取得更好的结果,而不是通过无限延长训练时间来提升指标。 3. 失败成本低 :实验结果不好就直接 Revert,不留下技术债,让 Agent 可以大胆探索不确定方向。 你的约束条件越清晰,Agent 的优化目标就越明确,加上搜索空间可控,我们就更容易在系统跑偏时把它及时拉回。 Harness 的关键结论是什么 这张图用验证难度和任务清晰度两个维度划分四个象限。右上角是最适合 Agent 发挥的区域,任务目标明确,结果也能自动验证。左上角的问题是任务虽然清楚,但结果还要靠人审查,右下角的问题是虽然有自动化反馈,但目标不够清楚,系统容易在错误方向上持续优化,左下角则同时缺少清晰目标和可靠验证,Agent 基本无从发力。 Harness 设计的关键在于,只有自动化验证和清晰的目标与参照标准同时具备,Agent 才能真正高效工作,只满足其中一个条件都不够。依赖人工验证的状态,效率和稳定性都有限,方向不清晰的状态,也很难持续产出可靠结果。也就是说,无论任务起点更接近人工验证,还是更接近方向模糊,最终都要被推进到同时具备清晰约束和自动化验证的状态,这才是 Harness 追求的理想工作区。 3. 上下文工程为什么决定稳定性 上下文工程的关键,不是窗口够不够长,而是放进去的东西是否真正相关,Transformer 的注意力复杂度是 O(n2) O ( n 2),上下文越长,关键信号越容易被噪声稀释。实践里一个很常见的失效模式是无关内容一旦占到上下文的大头,Agent 的决策质量就会明显下滑,这类现象通常被叫作 Context Rot(上下文腐化)。很多看起来像模型能力不足的问题,往往可以追溯到上下文组织不当。 这里将围绕这个四个点来讲:上下文信息怎么分层,历史信息怎么压缩,知识如何按需加载,以及大体积信息怎么移出上下文。 上下文为什么要分层 上下文里的信息并不是平铺的,而是应该按用途分层管理。下面这张图概括了一种常见的结构: 这里和我写的你不知道的 Claude Code 那篇文章里面的结论非常类似,上面这些层也对应一套信息分发机制: • 常驻层 :身份定义、项目约定、绝对禁止项等稳定规则 • 按需加载 :Skills,领域知识和操作流程 • 运行时注入 :当前时间、渠道 ID、用户偏好等动态信息 • 记忆层 :跨会话经验写入 MEMORY.md • 系统层 :Hooks 或代码规则处理确定性逻辑 别把确定性逻辑放进上下文 ,凡是可以通过 Hooks、代码规则或工具约束表达的内容,都应交给外部系统处理,而不是让模型反复读取。 三种常见压缩策略 1. 滑动窗口:丢弃旧消息,成本极低,会丢早期上下文,适合简短对话 2. LLM 摘要:模型生成总结,成本中等,丢细节保留决策,适合长任务 3. 工具结果替换:占位符替换原始输出,成本极低,适合工具调用密集型 压缩的目标不是单纯减少 token,而是在有限上下文预算内优先保留决策价值最高的信息,并把可重建内容移出上下文。 滑动窗口 实现最简单,超过阈值直接丢弃旧消息,适合短对话或低风险任务,但会同时丢掉早期决策背景。 LLM 摘要 更适合长任务,常见做法是在上下文接近容量时触发整合,把旧消息摘要写入 MEMORY.md,保留原始记录用于追溯。进阶做法是 branch summ