你不知道的 Agent:原理、架构与工程实践
1. 你不知道的 Agent:原理、架构与工程实践
1.1. 核心要点
- Harness 优于模型:决定系统稳定性的往往是测试、验证与约束基础设施(Harness),而非模型本身。
- 上下文分层管理:解决 Context Rot 的关键在于对信息进行分层(常驻、按需、运行时、记忆、系统层),并配合压缩策略。
- ACI 工具设计:工具应面向 Agent 目标设计(Agent-Computer Interface),而非简单的 API 封装,需具备边界清晰和自我修正能力。
- 状态外化与可重入:长任务的稳定性依赖于将进度和状态保存在外部(如文件系统),实现跨 session 的恢复与续跑。
- 多 Agent 协议化:多 Agent 协作需建立在结构化通信协议和隔离边界之上,防止幻觉放大。
1.2. 太长不读
在写完「你不知道的 Claude Code:架构、治理与工程实践」之后,发现自己对 Agent 底层的理解还不够深入,加上团队在 Agent 方向已经有不少业务落地经验,一直缺少一份系统梳理,所以我又把资料、开源实现和自己写的代码一起过了一遍,最后整理成了这篇文章。
这篇文章主要讲 Agent 架构里几块最影响工程效果的内容,包括控制流、上下文工程、工具设计、记忆、多 Agent 组织、评测、追踪和安全,最后再用 OpenClaw 的实现把这些设计原则串起来看一遍。
整理下来,有几处判断和我原来想的不太一样:更贵的模型带来的提升,很多时候没有想象中那么大,反而 Harness 和验证测试质量对成功率的影响更大;调试 Agent 行为时,也应优先检查工具定义,因为多数工具选择错误都出在描述不准确;另外,评测系统本身的问题,很多时候比 Agent 出问题更难发现。
如果一直在 Agent 代码上反复调,效果未必明显,读完这篇,这几个问题应该能有些答案。
1.3. Agent Loop 的基本运转方式
Agent Loop 的核心实现逻辑抽象后其实不到 20 行代码:
const messages: MessageParam[] = [{ role: "user", content: userInput }];
while (true) {
const response = await client.messages.create({
model: "claude-opus-4-6",
max_tokens: 8096,
tools: toolDefinitions,
messages,
});
if (response.stop_reason === "tool_use") {
const toolResults = await Promise.all(
response.content
.filter((b) => b.type === "tool_use")
.map(async (b) => ({
type: "tool_result" as const,
tool_use_id: b.id,
content: await executeTool(b.name, b.input),
}))
);
messages.push({ role: "assistant", content: response.content });
messages.push({ role: "user", content: toolResults });
} else {
return response.content.find((b) => b.type === "text")?.text ?? "";
}
}
对应的控制流如下,感知 -> 决策 -> 行动 -> 反馈四个阶段不断循环,直到模型返回纯文本为止:
看过不少 Agent 实现和官方 SDK,结构都差不多,循环本身相当稳定,从最小实现一路扩展到支持子 Agent、上下文压缩和 Skills 加载,主循环基本没有变化,新增能力通常都是叠加在循环外部,而不是改动循环内部。
新能力基本只通过三种方式接入:扩展工具集和 handler、调整系统提示结构、把状态外化到文件或数据库。
不应该让循环体本身变成一个巨大的状态机,模型负责推理,外部系统负责状态和边界,一旦这个分工确定下来,核心循环逻辑就很少需要频繁调整了。
1.3.1. Workflow 和 Agent 有什么区别
Anthropic 对这两类系统有一个直接区分:执行路径由代码预先写死的是 Workflow,由 LLM 动态决定下一步的是 Agent。
核心区别在于控制权掌握在谁手里。
现实中很多标着 Agent 的产品,深入看其实更接近 Workflow,不过两者本身并无高下之分,真正重要的是给任务找到更适合的解决方案。
放在一张图里看,会更直观:
1.3.2. 五种常见控制模式
大多数 AI 系统拆开看,其实都是这五种模式的组合。
很多场景并不需要完整的 Agent 自主权,把其中几种模式搭起来就够了,关键还是看任务本身适合哪一种设计。
- 提示链 Prompt Chaining:任务拆成顺序步骤,每步 LLM 处理上一步的输出,中间可加代码检查点,适合生成后翻译、先写大纲再写正文这类线性流程。
- 路由 Routing:对输入分类,定向到对应的专用处理流程。简单问题走轻量模型,复杂问题走强模型。
- 并行 Parallelization:分段法把任务拆成独立子任务并发跑,投票法把同一任务跑多次取共识,适合高风险决策或需要多视角的场景。
- 编排器-工作者 Orchestrator-Workers:中央 LLM 动态分解任务,委派给工作者 LLM,综合结果。
- 评估器-优化器 Evaluator-Optimizer:生成器产出,评估器给反馈,循环直到达标,适合质量标准难以用代码精确定义的任务。
上面这些模式解决的是控制流怎么搭,下面再看另一个更工程的问题:系统为什么能跑稳。
1.4. 为什么 Harness 比模型更关键
Harness 是指围绕 Agent 构建的测试、验证与约束基础设施,这里的 Harness 至少包括四个部分:验收基线、执行边界、反馈信号和回退手段。
模型虽然重要,但决定系统能不能稳定运行的,往往是这些外围工程条件。
这个判断在代码编写这类高可验证任务上最成立,但在开放式研究、多轮协商这类弱验证任务里,模型上限本身仍然更关键。
1.4.1. OpenAI 的 Agent 优先开发实践
3 个工程师 5 个月写了百万行代码,将近 1500 个 PR,是传统开发速度的 10 倍。
这个速度背后不是模型有多强,而是几个工程决策做对了:
- Agent 看不到的内容等于不存在:知识必须存在于代码库本身,外部文档对运行中的 Agent 不可见。
- 约束编码化而非文档化:写在文档里的规范很容易被忽略,编码进 Linter、类型系统或 CI 规则里的约束才具备可执行性。
- Agent 端到端自主完成任务:从验证当前状态、复现 Bug、实现修复到开 PR,全链路不需要人介入。
- 最小化合并阻力:测试偶发失败用重跑处理而不是阻塞进度,写代码的纪律从人工 Review 变成了机器执行的约束。
APP 把日志、指标、追踪三路数据经由 Vector 分发到 Victoria 存储层,对应 LogQL、PromQL、TraceQL 三个查询接口,Codex 通过这三个接口查询、关联、推理,完成改动后重启应用、重跑工作负载,结果再打回给 Codex,UI Journey 也作为输入接入。
整套可观测性栈按任务临时创建、任务完成即销毁,Agent 不需要等人告知错误,直接查询系统状态验证修改是否生效。
1.4.2. Harness 的关键结论是什么
图里用任务清晰度和验证自动化程度把任务分成四种状态:
- 右上角:目标明确、结果可以自动验证,是最适合 Agent 发挥的区域。
- 左上角:任务清楚但验收还得人盯,吞吐量天花板是人的审查速度。
- 右下角:有自动化反馈但目标模糊,系统会高效地往错误方向跑。
- 左下角:两者都缺,Agent 基本起不到作用。
Harness 要做的就是把任务推进右上角,让对错有机器可以执行的判断标准,而不是靠人盯。
1.5. 上下文工程:决定系统稳定性的关键
Transformer 的注意力复杂度是 $O(n^2)$,上下文越长,关键信号越容易被噪声稀释。
实践中最常见的失效模式是无关内容一旦占到上下文的大头,Agent 的决策质量就会明显下滑,这种现象通常被称为 Context Rot。
很多看起来像模型能力不足的问题,往往可以追溯到上下文组织不当。
1.5.1. 上下文分层管理架构
问题通常不是窗口不够长,而是信息密度不对。
偶尔用的东西每次都加载进来,稳定的规则和动态的状态混在一起,模型能看到的内容越来越多,但真正有用的部分越来越难被注意到。
解决方式是按信息的使用频率和稳定性分层管理,每层只放自己该放的内容:
- 常驻层:身份定义、项目约定、绝对禁止项。每次会话都必须成立的内容,保持短、硬、可执行。
- 按需加载(On-demand):Skills 和领域知识。描述符常驻,完整内容仅在触发时再注入。
- 运行时注入(Runtime):当前时间、渠道 ID、用户偏好等动态信息,每轮按需拼入。
- 记忆层(Memory):跨会话经验写入
MEMORY.md,不直接进系统提示,需要时检索读取。 - 系统层(System):Hooks 或代码规则处理确定性逻辑,完全不进上下文。
原则:别把确定性逻辑放进上下文。
凡是可以通过 Hooks、代码规则或工具约束表达的内容,都应交给外部系统处理。
1.5.2. 三种常见上下文压缩策略
- 滑动窗口(Sliding Window):丢弃旧消息,成本极低,但会丢失早期背景,适合简短对话。
- LLM 摘要(LLM Summarization):模型生成总结,保留决策核心,适合长任务。进阶做法是 Branch Summarization,明确保留架构决策和未完成任务。
- 工具结果替换(Tool Output Compaction):用占位符替换原始冗余输出,适合工具调用密集的场景。
1.5.3. Prompt Caching:大幅降低重复开销
LLM 推理时,如果当前请求的输入前缀与之前完全一致,KV 缓存可以直接重用。
命中的前提是精确前缀匹配。
缓存友好的设计核心是稳定性:
- 系统提示、工具定义、长文档等不变内容放在前缀。
- 动态信息(当前时间、用户输入)放在末尾。
- 稳定的大系统提示,比频繁变动的小提示实际成本更低,因为后续调用的折扣可达 90%。
1.5.4. Skills 按需加载模式
核心思路:系统提示只保留索引,完整知识按需加载。
const systemPrompt = `
可用 Skills:
- deploy: 部署到生产环境的完整流程
- code-review: 代码审查检查清单
- git-workflow: 分支策略和 PR 规范
`;
async function executeLoadSkill(name: string): Promise<string> {
return fs.readFile(`./skills/${name}.md`, "utf-8");
}
关键技巧:
- 描述要像路由条件:说明"何时该用我"比"我能做什么"更重要。
- 包含反例(Negative Examples):加上反例后准确率可从 53% 提升至 85%。
- 控制字数:高效描述(约 9 tokens)比冗余描述(约 45 tokens)更能节省常驻空间。
1.5.5. 压缩时最容易丢失的信息
压缩阶段最常见的问题是保留优先级设错。
建议在 CLAUDE.md 中明确保留优先级:
- 架构决策(不得摘要)
- 已修改文件和关键变更
- 验证状态(pass/fail)
- 未解决的 TODO 和回滚笔记
- 工具输出结果(可删,仅保留结论)
注意:不要改动标识符。
UUID、Hash、URL、文件名等必须原样保留,否则后续工具调用会失效。
1.5.6. 动态上下文发现(Dynamic Context Discovery)
文件系统天然适合做上下文接口。
与其让模型一次性读取大量 JSON,不如让 Agent 通过 grep、rg 按需读取。
这种方式下,调用工具的任务总 token 消耗可减少约 46.9%。
1.6. 工具设计:决定 Agent 的能力边界
上下文决定模型能看到什么,工具决定模型能做什么。
1.6.1. ACI(Agent-Computer Interface)设计原则
工具设计不应是简单的 API 封装,而应面向 Agent 的目标。
- 第一代:API 封装。粒度过细,Agent 难以协调。
- 第二代:ACI 接口。合并操作,例如直接提供
create_script而非拆分创建、写入、改权限。 - 第三代:高级工具调用。
- Tool Search:动态发现工具,上下文保留率提升至 95%。
- Programmatic Tool Calling:代码编排工具,大幅降低 token 消耗。
- Tool Use Examples:提供 1-5 个示例,调用准确率提升至 90%。
1.6.2. 优秀工具 vs. 平庸工具
好的工具设计边界清楚、参数防错,并能提供结构化的修正建议。
// ✅ 优秀实践:使用 Zod 绑定定义与实现,提供修正建议
const updateTool = betaZodTool({
name: "update_yuque_post",
description: "更新语雀文章内容,不适合创建新文章",
inputSchema: z.object({
post_id: z.string().describe("语雀文章 ID,纯数字字符串,如 '12345678'"),
content_markdown: z.string().describe("Markdown 格式正文"),
}),
run: async (input) => {
const post = await getPost(input.post_id);
if (!post) throw new ToolError("文章 ID 不存在", {
error_code: "POST_NOT_FOUND",
suggestion: "请先调用 list_yuque_posts 获取有效的 post_id",
});
return await updatePost(input.post_id, input.content_markdown);
},
});
建议:调试 Agent 时优先检查工具定义。
大多数错误源于描述不准而非模型能力不足。
1.7. 记忆系统设计:跨会话的一致性
Agent 本身不具备原生的时间连续性。
要实现跨会话一致性,记忆层必须单独设计。
1.7.1. 四层记忆模型
- 工作记忆(Working Memory):当前上下文窗口中的消息。
- 程序性记忆(Procedural Memory):Skills 流程和规范。
- 情景记忆(Episodic Memory):持久化的 JSONL 会话历史。
- 语义记忆(Semantic Memory):
MEMORY.md中沉淀的稳定事实。
1.7.2. 记忆整合与回退机制
当 token 使用率超过阈值(如 50%)时,触发记忆整合:
- 成功路径:生成摘要并追加到
MEMORY.md。 - 失败路径:将原始消息归档,保留完整历史以备回溯。
核心:流程必须可回退。
系统只移动指针,不真正删除原始数据。
1.8. 自主度与长任务管理:从"单轮"到"持续"
提高自主度不仅是减少确认次数,更是让 Agent 能在长跨度内稳定推进任务。
1.8.1. 跨 Session 续跑:状态外化
长任务失败的主因通常是 Session 结束导致上下文耗尽或现场丢失。
更稳定的做法是角色协作与状态外化:
- Initializer Agent:仅运行一次,生成任务清单(
feature-list.json)、进度记录(claude-progress.txt)。 - Coding Agent:循环执行,每次从外部文件恢复现场,实现单一功能并跑通测试,更新状态后提交代码。
1.8.2. 显式任务状态:外部锚点
任务状态必须显式记录为外部对象,而非仅留在模型的工作记忆中。
- 同一时间仅限一个
in_progress。 - 每完成一步均先更新外部状态文件。
- 长任务偏航时,自动注入进度提醒。
1.8.3. 后台 I/O 异步接入
将慢速 subprocess(如文件操作、网络请求)放入后台,通过通知队列在下一轮注入结果,避免阻塞主循环。
1.9. 多 Agent 组织:隔离、协作与协议
多 Agent 的核心价值在于将人的持续参与,转化为对 Agent 产出工件(如 PR)的最终审核。
1.9.1. 统筹者模式(Orchestrator-Workers)
主 Agent 负责全局统筹,子 Agent 独立并行工作。
- 信息隔离:子 Agent 的搜索和调试细节不污染主 Agent 上下文。
- 摘要回传:主 Agent 仅接收子 Agent 的结论摘要。
1.9.2. 结构化协作协议
多 Agent 协作必须建立在清晰的通信协议之上,而非仅靠自然语言。
// 推荐的消息结构示例
{
"request_id": "req_001",
"from_agent": "orchestrator",
"to_agent": "coder",
"content": "实现登录接口",
"status": "pending",
"timestamp": 1747312345
}
关键设施:
- 协议:统一的消息结构。
- 任务图:记录依赖关系的
.tasks/。 - 隔离边界:使用
.worktrees/隔离每个 Agent 的文件修改。
1.9.3. 交叉验证:打断幻觉放大链
在多 Agent 互动中,错误会被层层放大。
交叉验证(如独立的第二个 Agent、单测、编译器)能有效打断这种错误链。
1.10. Agent 自动化评测:构建质量基石
评测的核心在于:测试用例、评分标准和自动验证。
1.10.1. 评测结构的复杂性
Agent 评测不仅是看输出了什么,更要验证环境里发生了什么。
- Single-turn:判断 Response 是否正确。
- Agent Loop:运行环境 + 工具调用,验证 Outcome(环境最终状态)。
1.10.2. 核心指标与评分器
- 指标选择:
Pass@k:验证理论上的能力上限。Pass^k:验证回归质量,保证上线稳健性。
- 评分器类型:
- 代码评分器:单测、结构比对,确定性最高。
- 模型评分器(LLM Judge):按标准评分或对比选优。
- 人工评分器:专家标注,用于建立基准和校准。
1.10.3. 先修评测,再改 Agent
当通过率下降时,先查评测基础设施(如资源不足、评分器 Bug),再动 Agent。
1.11. 执行过程追踪(Tracing)
没有 Trace,失败案例就无法稳定复现。
1.11.1. Trace 记录要素
- 完整 Prompt 与系统提示。
- 多轮消息历史。
- 工具调用详情(参数与返回值)。
- 最终输出与消耗统计。
1.11.2. 观察者模式:事件流底座
Agent 在关键节点(如 tool_start)发出事件,多路下游(日志、UI、评测)按需订阅,实现架构解耦。
1.12. OpenClaw:工程化落地实践
OpenClaw 是一个遵循上述原则构建的工业级 Agent 系统。
1.12.1. 五层解耦架构
- Gateway:WebSocket 消息分发。
- Channel 适配器:多渠道(23+)统一接口。
- Pi Agent:核心逻辑循环,与渠道解耦。
- 工具集:遵循 ACI 原则的 Shell/FS/Web 等工具。
- 上下文与记忆:Skills 延迟加载与
MEMORY.md。
1.12.2. 主动触发:Cron 与 Heartbeat
系统不再仅依赖用户消息,通过定时计划和轮询主动推进任务。
1.12.3. 安全边界:先于功能
- 白名单授权:限定用户范围。
- 工作空间隔离:强制路径检查,防止越界。
- 审计日志:操作全记录。
1.12.4. 防注入与故障切换
- 内容边界标注:使用
<untrusted_content>隔离外部输入。 - Provider Fallback:多模型供应服务自动切换。
1.13. Agent 落地中的常见反模式(Anti-patterns)
- 系统提示充当知识库:导致提示过长,关键规则被忽略。应将知识移入 Skills,提示仅保留索引。
- 工具数量失控:Agent 频繁选错工具。应合并重叠工具,建立明确的命名空间。
- 验证闭环缺失:Agent 仅给出执行陈述而无法验证。应为每类任务绑定客观验收标准。
- 多 Agent 无边界:导致状态漂移和故障归因困难。应明确角色权限,使用 Worktree 隔离。
- 记忆不整合:长对话后决策质量显著下降。应监控 token 并实现超阈值自动整合。
- 缺乏自动化评测:修改后无法感知回归风险。应将失败案例立即转为持久化的测试用例。
- 约束靠"期望"而非"机制":规则仅停留在文档中。应改用工具验证、Linter 或 Hook 强制执行。
1.14. 结语:迈向工业级 Agent 时代
实现工业级 Agent 的稳定运行,靠的不是更复杂的模型循环,而是消息解耦、状态外化、分层提示、记忆整合与安全边界等工程细节的严丝合缝。
1.15. 参考资料
- OpenAI, Harness engineering: leveraging Codex in an agent-first world
- Anthropic, Managing context on the Claude Developer Platform
- LangChain, State of Agent Engineering
- Anthropic, Demystifying evals for AI agents
- OpenAI, Designing AI agents to resist prompt injection
- Cloudflare, How we rebuilt Next.js with AI in one week
- Anthropic, Introducing Agent Skills