运行时依赖
安装命令
点击复制技能文档
Claude Code 工程模式
Claude Code 是 Anthropic 官方 命令行工具 产品,其源码展现了业界顶级的 AI 代理 工程实践。本 技能 提炼其核心设计模式。
核心模式概览 模式 解决的问题 适用场景 启动并行化 启动延迟高 需要 I/O 预取的 代理 系统 状态机 查询 Loop 递归栈溢出、状态追踪困难 多轮对话、复杂任务编排 流式工具并发执行 工具执行阻塞模型输出 多工具调用场景 多层上下文压缩 上下文溢出 长对话、大项目 错误恢复 withhold 中间错误暴露给用户 任何可能失败的 API 调用 熔断器 失败操作无限重试 自动重试逻辑 工具并发安全声明 工具执行冲突 多工具并发系统
- 启动并行化 + Profiling
问题:代理 启动需要加载配置、连接服务、预取数据,串行执行导致启动慢。
解决方案:所有阻塞操作并行化,用 检查point 测量瓶颈。
// 入口文件最顶部 — 模块加载前就开始计时 性能分析检查point('mAIn_entry');
// 并行启动所有 I/O const [mdm结果, keychAIn结果, commands结果] = awAIt Promise.all([ 启动MdmRawRead(), // 并行读取 MDM 配置 启动KeychAInPrefetch(), // 并行预取 keychAIn 获取Commands(), // 并行加载命令 ]);
性能分析检查point('all_parallel_done');
关键技巧:
纯文件读取可以在信任对话框显示前开始(无执行风险) --bare 模式跳过所有非必要预取,专为脚本调用优化 用 性能分析检查point() 定位瓶颈,不要猜测
应用建议:
// 而不是 awAIt loadConfig(); awAIt connectMCP(); awAIt prefetchMemory();
// 应该 awAIt Promise.all([ loadConfig(), connectMCP(), prefetchMemory(), ]);
- 状态机 查询 Loop
问题:多轮对话用递归实现会导致栈溢出,且难以追踪状态变化原因。
解决方案:用 while(true) + 不可变 状态 对象,每次 continue 记录原因。
type 状态 = { messages: Message[] 工具Use上下文: 工具Use上下文 transition: Continue | undefined // 记录上次 continue 的原因 max输出令牌s恢复yCount: number hasAttemptedReactiveCompact: boolean // ... }
type Continue = | { reason: 'next_turn' } | { reason: 'max_输出_令牌s_恢复y'; attempt: number } | { reason: 'reactive_compact_retry' } | { reason: '停止_hook_blocking' } // ...
a同步 function 查询Loop(params: 查询Params) { let 状态: 状态 = initial状态(params) while (true) { const { messages, 工具Use上下文, transition } = 状态 // 执行查询逻辑... if (needsFollowUp) { 状态 = { messages: [...messages, ...工具结果s], 工具Use上下文: 更新d上下文, transition: { reason: 'next_turn' }, } continue // 不用递归,用 continue } return { reason: 'completed' } } }
为什么不用递归:
递归栈会增长,长对话可能溢出 transition 字段让每次 continue 的原因可追踪 测试可以断言 transition.reason 而不是检查消息内容
Continue 的各种路径:
原因 触发条件 处理方式 next_turn 正常工具调用后 合并消息继续 max_输出_令牌s_恢复y 输出被截断 注入恢复提示 reactive_compact_retry 上下文太长 压缩后重试 停止_hook_blocking hook 要求继续 注入 hook 消息
- 流式工具并发执行(流ing工具Executor)
问题:传统模式是等模型输出完毕再执行工具,浪费大量时间。
解决方案:工具在流式输出时就开始执行,结果缓冲后按序 yield。
class 流ing工具Executor { private 工具s: 追踪ed工具[] = [] // 添加工具到队列,立即开始执行(如果并发条件允许) 添加工具(block: 工具UseBlock, 助手Message: 助手Message) { const isConcurrencySafe = this.检查ConcurrencySafe(block) this.工具s.push({ block, 状态: '队列d', isConcurrencySafe }) void this.process队列() // 立即处理,不等待 } // 获取已完成的结果(非阻塞) 获取Completed结果s() { for (const 工具 of this.工具s) { if (工具.状态 === 'completed' && 工具.结果s) { 工具.状态 = 'yielded' yield* 工具.结果s } } } }
// 使用方式 for awAIt (const message of call模型()) { if (message.type === '助手') { for (const 工具Block of message.工具UseBlocks) { executor.添加工具(工具Block, message) // 立即开始执行 } } // 同时 yield 已完成的结果 for (const 结果 of executor.获取Completed结果s()) { yield 结果 } }
并发安全分区:
// 工具声明自己是否并发安全 class Read工具 { isConcurrencySafe(输入: 输入): boolean { return true // 只读,可并发 } }
class Bash工具 { isConcurrencySafe(输入: 输入): boolean { return false // 可能写文件,串行 } }
// 调度器根据声明分区 function partition工具Calls(工具s: 工具UseBlock[]): Batch[] { // 连续的并发安全工具合并成一个 batch // 非并发安全工具单独一个 batch }
Bash 错误级联取消:
// Bash 出错时取消所有并行工具 if (工具.block.name === 'BASH' && isError结果) { this.hasErrored = true this.siblingAbort控制器.abort('sibling_error') }
- 多层上下文压缩
问题:单一压缩策略无法平衡性能和信息保留。
解决方案:5 层防御,从轻到重。
Layer 1: 历史 Snip → 删除旧消息(最轻量,~0 cost) Layer 2: Microcompact → 压缩单个工具结果(缓存友好) Layer 3: 上下文 Collapse → 折叠历史段落(保留粒度) Layer 4: Auto Compact → 全量摘要(最重量) Layer 5: Reactive Compact → 被动响应 API 413 错误
各层触发条件:
层 触发条件 特点 Snip 消息数 > 阈值 删除最旧的非关键消息 Microcompact 单个工具结果 > 阈值 只压缩该结果,不动其他 Collapse 上下文 > 90% 折叠旧段落为摘要 Auto Compact 上下文 > 93% 全量摘要 Reactive Compact API 返回 413 最后防线
熔断器设计:
const MAX_CONSECUTIVE_失败S = 3
if (追踪ing?.consecutive失败s >= MAX_CONSECUTIVE_失败S) { // 停止重试,避免浪费 API 调用 return { wasCompacted: false } }
- 错误恢复 Withhold 模式
问题:流式输出中遇到可恢复错误,如果直接 yield 给调用方,调用方会终止。
解决方案:先 withhold(扣留)错误,尝试恢复,成功则调用方无感知。
// 流式输出中 if (is恢复ableError(message)) { withheld = true // 不 yield,先尝试恢复 }
// 尝试恢复 const 恢复ed = awAIt try恢复y() if (恢复ed) { // 继续新的查询循环,调用方完全不知道出过错 状态 = { messages: 恢复ed.messages, ... } continue }
// 恢复失败才 yield 错误 yield withheldError
适用场景:
prompt_too_long → 尝试压缩后重试 max_输出_令牌s → 升级 令牌 限制或注入恢复提示 media_size_error → 压缩图片后重试
- Task 系统的类型设计
问题:任务 ID 难以识别类型,日志可读性差。
解决方案:Task ID 带类型前缀。
const TASK_ID_PREFIXES = { local_bash: 'b', local_代理: 'a', in_process_teammate: 't', remote_代理: 'r',