跳转到内容

给 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 独立构建 context
const subAgent = createAgent({
system: buildNewSystemPrompt(), // 与父级不同
messages: [{ role: 'user', content: task }], // 无共享历史
});
// ✅ 好:sub-agent 扩展父级的已缓存 context
const 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 执行重叠进行。

单一配置文件不适用于跨不同项目、团队和环境运行的工具。用户需要项目级、用户级和企业级设置。

多个配置源,优先级清晰: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)和安全的(在你的进程中运行,而非用户的)。

#启示关键指标
1agent 循环用 generator调用栈溢出风险为 0
2分层安全检查至少 5 个独立层
3渐进式 context 压缩在 60% 容量时开始
4prompt cache 优化已缓存 token 节省 90%
5sub-agent context 复用3+ 个 fork 时降低 60%+ 成本
6渐进式 agent 扩展80% 的任务只需 1 个 agent
7不可绕过的安全基准prompt 可控逃逸后门为 0
8stream 一切< 200ms 看到第一个可见输出
9多层配置3+ 个配置源层
10基于 hook 的可扩展性90% 的定制需求,10% 的复杂度