给 Agent 构建者的启示
在深入研究 Claude Code 512K+ 行架构之后,这里是对任何构建 AI agent 系统的人最具可操作性的 10 条启示。每条启示都从一个具体的架构决策中提炼而来,并附有具体的实现指导。
启示 1:用 Generator 实现 agentic loop,而非递归
Section titled “启示 1:用 Generator 实现 agentic loop,而非递归”递归 agent 循环会累积调用栈帧,无法在执行中途被观测,也没有自然的取消点。
AsyncGenerator 提供线性可读性、内置背压、原生暂停/恢复以及零调用栈增长。
// ✅ generator 方式async function* agentLoop(messages: Message[]): AsyncGenerator<Event> { while (shouldContinue) { const response = await callLLM(messages); yield { type: 'response', data: response }; // 可观测
for (const tool of response.toolCalls) { yield { type: 'tool_start', tool }; // 可暂停 const result = await executeTool(tool); yield { type: 'tool_end', result }; // 可恢复 } }}
// 消费者控制节奏for await (const event of agentLoop(messages)) { if (event.type === 'tool_start' && needsPermission(event.tool)) { const ok = await askUser(); if (!ok) break; // 优雅取消 }}将你的 while (true) { ... } agent 循环替换为 async function*。每个 yield 点都成为日志记录、permission 检查和 UI 更新的免费观测/拦截点。
启示 2:tool permission 需要分层检查
Section titled “启示 2:tool permission 需要分层检查”单一 permission 检查是单点故障。如果 prompt 注入绕过了一个检查,tool 就会不受约束地执行。
多个独立安全层,其中任意一层都可以拒绝,但没有单层能单独批准。
// 五类检查,各自独立const securityPipeline = [ syntacticCheck, // 输入在结构上是否有效? semanticCheck, // 它打算做什么? scopeCheck, // 是否在允许的范围内? policyCheck, // 配置的规则是否允许? userCheck, // 用户是否批准?];
// 全部必须通过——任意一个拒绝即为最终结果for (const layer of securityPipeline) { const result = layer.check(toolInput); if (result.verdict === 'deny') return { allowed: false };}至少实现三层:输入校验(是否格式正确?)、范围检查(是否在边界内?)和用户确认(用户是否同意?)。在应用代码中执行,而非作为模型指令。
启示 3:context 压缩是长对话的生命线
Section titled “启示 3:context 压缩是长对话的生命线”不进行压缩的情况下,每次 agent 对话都有硬上限(约 15-20 分钟)。来自 tool 结果、文件读取和对话历史的 token 积累不可避免地会超出 context window。
渐进式 4 层压缩:Snip → Microcompact → Auto Compact → Hard Truncate。从早期(60% 容量)开始,而非等到太晚。
// 关键洞察:在达到限制之前就开始压缩const THRESHOLDS = { snip: 0.4, // 在 40% 时裁剪大型 tool 结果 microcompact: 0.6, // 在 60% 时摘要旧结果 autoCompact: 0.8, // 在 80% 时摘要对话 hardTruncate: 0.95, // 在 95% 时紧急丢弃};至少实现 Snip(截断大型 tool 输出,保留头部+尾部)和 Auto Compact(摘要旧消息)。这两层单独就能将会话寿命从约 20 分钟延长到潜在的数小时。
启示 4:prompt cache 是你最重要的成本控制杠杆
Section titled “启示 4:prompt cache 是你最重要的成本控制杠杆”API 成本随 input token 扩展。每次会话进行 20+ 次 API 调用,每次都带着大型 system prompt,会累积相当可观的成本。
构建你的 system prompt 以最大化 cache 前缀复用。把静态内容放在最前面,动态内容放在最后面。
System prompt 布局(cache 优化):┌─────────────────────────────────┐│ 静态:身份、能力说明 │ ← 跨所有会话缓存│ 静态:tool 定义 │ ← 跨所有会话缓存│ 半静态:项目 context │ ← 会话内缓存│ 动态:当前任务 context │ ← 不缓存(每次调用都变化)└─────────────────────────────────┘审计你的 system prompt。将所有在 API 调用之间不变化的内容移到最前面。即使只是重新排序各部分以最大化共享前缀,也能节省 30-60% 的 input token 成本。
启示 5:sub-agent 应复用 context,而非重建
Section titled “启示 5:sub-agent 应复用 context,而非重建”生成一个从头构建自己的 system prompt 和 context 的 sub-agent,会浪费 token 并丢失宝贵的已缓存状态。
从父级的现有 context fork 出 sub-agent,共享 system prompt 前缀以实现 cache 复用。
// ❌ 不好:每个 sub-agent 独立构建 contextconst subAgent = createAgent({ system: buildNewSystemPrompt(), // 与父级不同 messages: [{ role: 'user', content: task }], // 无共享历史});
// ✅ 好:sub-agent 扩展父级的已缓存 contextconst subAgent = createAgent({ system: parentAgent.systemPrompt, // 相同前缀 → cache 命中 messages: [ ...parentAgent.messages, // 共享历史 → cache 命中 { role: 'user', content: task }, // 只有这部分是新的 ],});在设计 fork/sub-agent 机制时,将父级的 system prompt 和消息历史作为共享前缀传递。有 3 个以上 fork 时,这可以将 input token 成本降低 60%+。
启示 6:从单 agent 开始,按需扩展至 multi-agent
Section titled “启示 6:从单 agent 开始,按需扩展至 multi-agent”为单 agent 就能完美处理的任务过度设计 multi-agent 系统。对简单任务而言,协调开销超过任何并行收益。
渐进式升级:单 agent → fork → coordinator → team。
graph LR
Q["Task complexity?"]
Q -->|"Simple"| S["Single Agent<br/>80% of tasks"]
Q -->|"Parallel subtasks"| F["Fork<br/>15% of tasks"]
Q -->|"Dependencies"| C["Coordinator<br/>4% of tasks"]
Q -->|"Collaboration"| T["Team<br/>1% of tasks"]
style S fill:#4ade80
style F fill:#a3e635
style C fill:#facc15
style T fill:#fb923c
先构建你的单 agent 循环。只有在你有具体证据证明单 agent 不足以应对特定类型任务时,才添加 multi-agent 协调。80/20 法则适用:80% 的任务用一个 agent 就能搞定。
启示 7:安全检查必须有不可绕过的基准线
Section titled “启示 7:安全检查必须有不可绕过的基准线”如果安全检查以模型指令形式实现(“永远不要运行危险命令”),prompt 注入就可以覆盖它们。
将安全实现为在模型执行边界之外运行的应用代码。模型的输出是你的安全管道的输入——它无法修改管道本身。
// 模型生成一个 tool 调用const toolCall = model.generateToolCall(); // 可能已被篡改
// 安全在你的代码中运行——模型无法影响这里const allowed = securityPipeline.evaluate(toolCall); // 对注入免疫
if (!allowed) { // 硬拒绝——无论多少 prompt 注入都无法改变这一点 return { error: 'Denied by security pipeline' };}识别你的硬安全边界——无论模型输出什么都绝不能被绕过的检查。在应用代码中实现这些检查,不留任何 prompt 可控的逃逸后门。
启示 8:streaming 让体验好一个数量级
Section titled “启示 8:streaming 让体验好一个数量级”传统请求-响应模式:用户等待 3-10 秒什么都看不到,然后收到一堵文字墙。这感觉缓慢且无响应。
stream 一切:API 响应逐 token 传输、tool 执行进度,甚至在收到完整响应之前就开始执行 tool。
// 关键洞察:在每个机会都 yield 进度async function* streamingExperience(): AsyncGenerator<UIEvent> { yield { type: 'thinking' }; // "Claude 正在思考..."
for await (const token of apiStream) { yield { type: 'token', text: token }; // 实时文本显示 }
yield { type: 'tool_start', name: 'Bash' }; // "正在运行命令..."
for await (const line of toolOutput) { yield { type: 'output_line', text: line }; // 实时命令输出 }
yield { type: 'complete' };}如果你的 agent 系统显示空白屏幕超过 500ms,你就在流失用户。在 200ms 内 stream 第一个 token,展示 tool 执行进度,并在可能时将 API streaming 与 tool 执行重叠进行。
启示 9:配置需要多层源合并
Section titled “启示 9:配置需要多层源合并”单一配置文件不适用于跨不同项目、团队和环境运行的工具。用户需要项目级、用户级和企业级设置。
多个配置源,优先级清晰:flags > 环境变量 > 项目配置 > 用户配置 > 企业配置 > 默认值。
// 配置优先级(从高到低)const config = mergeConfigs([ flagOverrides, // --model opus envVarConfig, // CLAUDE_MODEL=opus projectConfig, // .claude/settings.json userConfig, // ~/.claude/settings.json enterpriseConfig, // /etc/claude/settings.json defaults, // 硬编码的合理默认值]);
// 每层只指定它需要覆盖的内容// projectConfig: { "model": "sonnet", "tools": { "bash": { "allowedCommands": ["npm", "git"] } } }// userConfig: { "theme": "dark" }// 结果: { "model": "sonnet", "theme": "dark", "tools": { "bash": { ... } } }至少设计 3 层配置:项目(提交到 git)、用户(个人偏好)和默认值。使用深合并而非浅覆盖,这样任何层的部分配置都能正确工作。
启示 10:hook 系统提供最佳可扩展性
Section titled “启示 10:hook 系统提供最佳可扩展性”plugin API 功能强大但复杂。大多数用户只想定制某个特定行为,而不是构建整个 plugin。
hook 系统让用户能够用简单的脚本或函数拦截特定的生命周期事件。
// .claude/hooks.json — 简单、声明式、强大{ "onToolStart": [ { "matcher": { "tool": "Bash" }, "action": "log", "config": { "file": "./claude-commands.log" } } ], "onToolEnd": [ { "matcher": { "tool": "WriteFile", "path": "*.test.ts" }, "action": "exec", "config": { "command": "npm test" } } ]}在构建 plugin API 之前,考虑 hook 系统是否能以 10% 的复杂度覆盖 90% 的使用场景。hook 是声明式的(易于理解)、可组合的(每个事件可有多个 hook)和安全的(在你的进程中运行,而非用户的)。
| # | 启示 | 关键指标 |
|---|---|---|
| 1 | agent 循环用 generator | 调用栈溢出风险为 0 |
| 2 | 分层安全检查 | 至少 5 个独立层 |
| 3 | 渐进式 context 压缩 | 在 60% 容量时开始 |
| 4 | prompt cache 优化 | 已缓存 token 节省 90% |
| 5 | sub-agent context 复用 | 3+ 个 fork 时降低 60%+ 成本 |
| 6 | 渐进式 agent 扩展 | 80% 的任务只需 1 个 agent |
| 7 | 不可绕过的安全基准 | prompt 可控逃逸后门为 0 |
| 8 | stream 一切 | < 200ms 看到第一个可见输出 |
| 9 | 多层配置 | 3+ 个配置源层 |
| 10 | 基于 hook 的可扩展性 | 90% 的定制需求,10% 的复杂度 |