标签 上下文管理 下的文章

一、开发痛点:为什么我们需要AI编程辅助?

核心发现: AI编程工具正在重塑开发流程,但真正的价值不在于替代开发者,而在于构建人机协作的新型开发范式。Claude Code通过精准对话流设计、模块化任务分解和专业化子代理协作,在提升开发效率的同时,也面临着上下文管理、协作边界和质量控制等实际挑战。

作为一线开发者,我们每天都在与复杂的业务逻辑和不断迭代的技术栈打交道。不知道你是否也遇到过这些场景:刚理清一个复杂业务流程,被打断后又得重新梳理思路;接手一个老项目,花了半天还没搞懂其中某个模块的设计思路;或者在不同项目间切换时,总要重新适应不同的编码规范和架构风格。

日常开发的三个"拦路虎":

  • 上下文切换成本高: 需求理解→技术选型→代码实现→质量验证的切换过程中,每次都要重新构建认知框架。
  • 知识传递效率低: 项目规范、架构经验分散在文档和个人经验中,新成员上手或跨模块开发时处处碰壁。
  • 开发流程割裂: 需求→设计→编码→审查各环节串行传递,信息易失真且反馈滞后。

这些问题不是简单的"加人"或"加班"能解决的。我们需要的是一种新的开发范式,而Claude Code这类AI编程工具正是在这样的背景下进入了我们的视野。它的价值不在于替我们写代码,而在于成为我们的"认知放大器"和"流程协作者"。

二、Claude Code核心功能解析:从工具到方法论

Claude Code构建了一套完整的AI辅助开发方法论。接下来将结合团队实际使用经验,从功能特性、使用场景和设计初衷三个维度,详细介绍其核心功能:

精准对话流设计:控制AI思考的艺术

第一次用Claude Code时,就像面对一个热情但经验不足的实习生——如果不明确告诉他要做什么、怎么做、有什么要求,他很可能会给你一个"惊喜"。对话流设计就是解决这个问题的关键。

设计初衷: 对话流设计的本质是将人类的编程思维模式转化为AI可理解的结构化交互方式,通过明确的上下文管理和约束条件设置,引导AI生成符合预期的代码结果。

核心功能

对话流设计通过三个关键机制控制AI的思考过程:

  • 上下文聚焦: 要求单次对话仅处理一个功能模块,避免多任务混合导致的AI注意力分散。我们曾经试过在一个对话里同时让AI处理多个模块,结果它把两个模块的错误处理逻辑混在了一起。
  • 约束明确化: 通过具体指令减少AI的自由度,比如"仅修改X包下文件"、"必须复用Y工具类"。这些约束要尽可能具体,比如不说"遵循项目规范",而是说"使用ResultDTO作为统一返回格式,错误码规则参考ErrorCodeEnum"。
  • 增量式提问: 采用"先框架后细节"的提问策略,先让AI生成接口定义和整体框架,待确认后再逐步深入实现细节。这种方式很像我们带新人时"先搭骨架再填肉"的指导方法。

使用心法

启动新功能开发时,我们会创建专用对话线程,并在初始prompt中明确四件事:

  1. 当前任务的功能边界和目标(做什么,不做什么。)
  2. 必须遵守的技术约束和规范(用什么技术栈,遵循什么标准。)
  3. 期望的输出格式和交付物(要代码?要文档?还是两者都要?)
  4. 分阶段的实现计划(先设计接口,再实现逻辑,最后写测试。)

真实踩坑经验

处理跨模块依赖时,我们发现AI很容易"忘记"之前设定的约束。后来我们总结出一个技巧:每开始一个新的实现阶段,就简要回顾一下关键约束。比如:"现在我们要处理任务交接流程,请记得:1. 使用Redis分布式锁;2. 需要修改商运关系和新商成长任务;3. 异常处理要符合规范。"

Plan模式:复杂任务的系统化分解

面对"实现一个完整的拜访任务系统"这样的复杂需求,直接让AI生成代码就像让一个刚入行的开发者独立负责整个项目——结果往往是逻辑混乱、漏洞百出。Plan模式就是解决这个问题的"项目管理工具"。

现状与问题:我们早期使用Claude Code时,经常犯一个错误:把一个复杂需求一股脑丢给AI,然后期待它给出完美解决方案。结果通常是:

  • 实现逻辑不完整,有些边界情况根本没考虑到。
  • 模块间接口设计不一致,调用起来磕磕绊绊。
  • 技术选型不合理,用了个"看起来很酷但项目中并不适用"的方案。

这其实不能怪AI,人在面对过于复杂的问题时也会手足无措。我们需要一种方法把大问题拆分成小问题,而Plan模式正是借鉴了项目管理中的WBS(工作分解结构)思想。

我们的解决方案:Plan模式使用三步法分解需求:

  1. 需求分析与模块划分:把整体需求分解为独立的功能模块。
  2. 技术方案设计:为每个模块确定实现思路和技术选型。
  3. 任务优先级排序:根据依赖关系和重要性确定实现顺序。

实际操作示例:拜访任务需求

拜访任务是一个简单的企业内部任务需求,在实现"拜访任务需求"时,我们通过Plan模式生成的任务清单是这样的:

---
拜访任务线上化 - 模块清单
M1: 任务创建模块
- 功能: 创建拜访任务,包含基本信息、拜访对象、参与人员
- 复杂度: Medium (需要处理多表关联和事务)
M2: 任务审批模块
- 功能: 飞书审批流程集成,支持任务提交、审批、驳回
- 复杂度: High (涉及飞书审批接口集成和状态流转)
M3: 日程同步模块
- 功能: 将拜访任务同步到飞书日历
- 复杂度: Medium (飞书API调用和异常处理)
M4: 任务列表查询模块
- 功能: 分页查询任务列表,支持多维度筛选
- 复杂度: Medium (ES聚合查询和数据转换)
M5: 任务详情查询模块
- 功能: 查询任务详情,包括拜访对象、参与人员、商家信息
- 复杂度: Low (简单的关联查询)
M6: 任务统计模块
- 功能: 统计不同类型、不同状态的任务数量
- 复杂度: Low (聚合统计)
M7: 拜访结果提交模块
- 功能: 提交拜访记录,上传附件,记录拜访内容
- 复杂度: Medium (文件上传和多表操作)
M8: 任务分配模块
- 功能: 批量分配任务给运营人员
- 复杂度: High (涉及分配策略和定时任务)
M9: 任务触达模块
- 功能: 任务开始、结束、上传提醒通知
- 复杂度: Medium (飞书消息卡片模板)
M10: ES数据同步模块
- 功能: 将任务数据同步到ES,支持全文检索
- 复杂度: Medium (事件驱动和ES批量操作)
M11: 任务导入模块
- 功能: Excel批量导入拜访任务
- 复杂度: High (Excel解析、数据校验、批量插入)
M12: 任务状态变更模块
- 功能: 定时更新任务状态(未开始→进行中→已结束)
- 复杂度: Low (定时任务和批量更新)
---
第二步:技术方案设计
| 模块       | 数据存储               | 查询方案         | 外部集成          |
|------------|------------------------|------------------|-------------------|
| M1-任务创建  | MySQL (事务)           | -                | -                 |
| M2-任务审批  | MySQL + 审批记录表      | -                | 飞书审批API       |
| M3-日程同步  | -                      | -                | 飞书日历API       |
| M4-任务列表  | ES (主) + MySQL (辅)   | ES聚合查询       | -                 |
| M5-任务详情  | MySQL                  | 联表查询         | 商家信息API |
| M6-任务统计  | ES                     | ES聚合统计       | -                 |
| M7-结果提交  | MySQL + OSS            | -                | OSS文件上传       |
| M8-任务分配  | MySQL                  | 分配算法         | -                 |
| M9-任务触达  | -                      | -                | 飞书消息卡片      |
| M10-ES同步  | ES                     | Spring Event     | -                 |
| M11-任务导入 | MySQL                  | Excel 解析    | -                 |
| M12-状态变更 | MySQL                  | JOB定时任务  | -                 |
---
第三步:任务优先级排序
P0 核心流程(必须先实现)
1. M1-任务创建 (基础功能)
2. M5-任务详情 (数据展示)
3. M4-任务列表 (核心查询)
4. M7-结果提交 (闭环流程)
P1 审批与通知(核心功能增强)
5. M2-任务审批 (依赖M1)
6. M3-日程同步 (依赖M2审批通过)
7. M9-任务触达 (依赖M1、M7)
P2 运营功能(效率提升)
8. M8-任务分配 (依赖M1)
9. M11-任务导入 (依赖M1)
10. M6-任务统计 (依赖M4)
P3 技术优化(性能优化)
11. M10-ES同步 (依赖M1、M4)
12. M12-状态变更 (独立模块)
---
实现建议
阶段一:基础能力
- 先实现 M1、M5、M4,打通任务创建→查询→详情的基本链路
- 使用 MySQL 实现,暂不接入 ES
阶段二:审批流程
- 实现 M2、M3,接入飞书审批和日历
- 完成任务的完整生命周期管理
阶段三:结果闭环
- 实现 M7,完成拜访结果提交
- 实现 M9,添加任务触达通知
阶段四:运营优化
- 实现 M8、M11、M6
- 提升运营效率
阶段五:性能优化(按需)
- 实现 M10,将查询迁移到 ES
- 实现 M12,优化任务状态管理

这种方式不仅让AI能够有序工作,也让我们对整个开发进度有了更清晰的把控。

系统提示词:给AI立"规矩"的艺术

如果把Claude Code比作一个新加入团队的开发人员,系统提示词(CLAUDE.md)就相当于给他的"入职手册",告诉他团队的编码规范、工作流程和注意事项。

新手常犯的错误: 把系统提示词写成"百科全书",恨不得把所有项目知识都塞进去。结果AI要么忽略大部分内容,要么在生成代码时顾此失彼。我们早期的系统提示词长达5000字,包含了从架构设计到代码规范的所有内容,效果反而不好。

实践心得:有效的系统提示词应该像"护栏"而非"详尽手册"。我们发现,针对AI常见错误模式设计的针对性提示,远比全面但泛泛的规范更有效。现在我们的系统提示词控制在200字以内,只包含最关键的约束和指引。

系统提示词模板

经过多次迭代,我们总结出包含三个关键模块的系统提示词结构:

使用技巧

分享几个在实践中总结的系统提示词编写技巧:

  • 避免信息过载: 不要试图包含所有知识,而是指引AI在需要时查询特定文档。例如:"遇到分布式事务问题时,请参考/doc/分布式事务最佳实践.md文档中的TCC模式实现方案"。
  • 提供正向引导: 不仅说"不要做什么",更要明确"应该怎么做"。例如,不说"不要使用过时的API",而说"请使用OrderServiceV2替代OrderServiceV1。
  • 动态调整策略: 我们每两周会回顾一次系统提示词的有效性,根据AI最近常犯的错误补充新的约束。比如发现AI经常忘记处理空指针,就新增一条:"所有方法入参必须进行非空校验,使用ValidateUtil.isEmpty()方法,异常时抛出IllegalArgumentException"。

SKILL与MCP:知识沉淀与外部能力扩展

在团队协作中,我们经常说"不要重复造轮子"。同样,在使用Claude Code时,我们也需要一种机制来沉淀和复用那些有效的Prompt和解决方案——这就是SKILL和MCP机制的价值所在。

SKILL机制: 把好经验变成"可复用组件"

SKILL本质上是将单次生效的Prompt指令沉淀为可反复调用的标准化复用资产。举个例子,我们团队处理"ES数据查询"逻辑时,总结出了一个内部版本的SDK。我们把这个SDK的调用方式封装成一个SKILL,以后遇到类似场景,只需调用这个SKILL,AI就能按照我们团队的最佳实践来实现。

MCP协议: 让AI能"调用"外部工具

MCP(模型上下文协议)解决了AI与外部工具、数据源的连接问题。通过MCP,AI不再局限于静态知识,而是能够动态访问实时数据。我们集成了飞书MCP服务器,让AI能够直接操作飞书平台,如自动生成技术方案文档、读取PRD需求、同步数据到多维表格等。

最适合封装为SKILL的场景

1.复杂工具使用指南: 如"ElasticSearch接入"、"Redis缓存更新策略"等需要特定知识的场景。

2.常见错误处理模板: 如"分布式锁冲突处理"、"数据库乐观锁重试机制"等反复出现的问题解决方案。

MCP协议的典型应用场景

  • 场景1: 自动生成技术方案文档
  • AI分析需求后,通过飞书MCP调用feishu_create_doc;
  • 直接在指定的知识库目录创建格式化的技术方案文档;
  • 省去手动复制粘贴的繁琐步骤。
  • 场景2: 读取PRD需求
  • 用户提供飞书文档链接;
  • AI通过feishu_get_doc_content获取文档内容;
  • 基于完整需求信息生成技术方案和实现计划。
  • 场景3: 数据同步到多维表格
  • 代码生成后的统计数据(如代码行数、涉及文件等);
  • 通过feishu_append_bitable_data自动追加到飞书多维表格;
  • 便于团队追踪AI编程效率指标。

三、对话流设计方法论:让AI"懂"你的真实需求

刚接触Claude Code时,我们采用的是简单直接的"需求-响应"模式:开发者描述需求,AI生成代码,开发者修改调整。这种模式在处理简单功能时还行,但遇到复杂场景就会出问题。

现状分析:传统对话模式的局限性

我们早期在项目中踩过的三个坑:

三大典型问题:

  • 需求表达不完整:

开发者说"实现一个商家信息查询接口",AI生成了基础的CRUD代码,但没有考虑商家数据权限、数据脱敏、缓存策略等实际业务需求 ;

实现任务时,只描述了"需要任务分配功能",结果AI生成的代码没有处理任务池、任务优先级、分配策略等核心逻辑。

  • 上下文管理混乱:

一个对话持续了十几轮后,AI开始忘记我们前面确定的"使用MyBatis-Plus + BaseMapper"的设计决策,擅自改成了JPA Repository模式; 

在实现相关功能时,早期确定的DTO转换规范在后续模块中被遗忘,导致代码风格不一致。

  • 迭代反馈滞后:

等AI生成完整的Service + Controller + Repository代码后才发现方向不对,比如数据库表设计与现有架构冲突,不得不从头再来,浪费了大量时间;

实现触达功能时,生成的飞书消息发送代码没有考虑现有的FeishuClient封装,重复造了轮子。

核心问题:为什么AI总是"听不懂"?

深入分析后,我们发现传统对话模式失败的根源在于三个核心矛盾:

语义鸿沟

自然语言描述的模糊性与代码逻辑的精确性之间的差距。我们说"这个接口要安全",AI可能理解为"需要登录校验",而我们实际想要的是:

  • 使用项目中的@Permission注解进行权限校验。
  • 参数需要使用ValidatorUtil进行校验。
  • 敏感操作需要记录操作日志。

约束衰减

随着对话推进,早期设定的技术约束在AI理解中的权重逐渐降低。就像我们记笔记时,重要的事情要反复强调。比如:

  • 第1轮对话强调"必须继承BaseServiceImpl"。
  • 第5轮对话AI可能忘记这个约束,直接实现了一个独立的Service类。
  • 第10轮对话可能连项目的分层架构都混淆了。

目标偏移

在多轮对话中,AI容易过度关注当前细节而忽视整体目标。比如讨论某个接口的参数设计时:

  • AI可能会纠结于参数名称是否优雅。
  • 而忽略了这个接口的核心业务价值是"快速检索符合条件的商家"。
  • 结果生成的代码参数命名很完美,但缺少了分页、排序等实际必需的功能。

解决方案:结构化对话设计方法

针对这些问题,我们团队总结出一套"三阶段对话模型",现在已经成为我们使用Claude Code的标准流程:

阶段一:需求定义——把"要做什么"说清楚

这个阶段的目标是确保我们和AI对需求达成共识。我们会用"用户故事+验收标准"的格式来描述需求:

示例1:新商户成长任务分配

【用户故事】
作为新商户运营,我需要一个任务分配功能,以便将成长任务高效分配给运营人员
【验收标准】
 - 支持从任务池中按优先级(P0/P1/P2)筛选待分配任务
 - 支持指定运营人员进行任务分配,需校验运营人员是否有权限
 - 分配时需检查运营人员当前任务负载,超过上限时提示"当前任务数已达上限"
 - 分配成功后需发送飞书消息通知运营人员,消息内容包含任务详情和截止时间
 - 操作需记录到表,包含操作人、操作时间、任务ID、分配对象

示例2:商家数据权限查询

【用户故事】
作为商家运营,我需要一个商家信息查询接口,查询结果需要根据我的数据权限进行过滤
【验收标准】
 - 支持按商家ID、商家名称、商家状态进行查询
 - 支持分页查询,默认每页20条,最大100条
 - 查询结果需要根据当前用户的数据范围进行过滤
 - 商家敏感信息(手机号、身份证号)需脱敏处理
 - 接口需要权限校验,至少具有"商家查看"权限
 - 查询条件需记录到操作日志,便于审计

阶段二:边界明确——确定"怎么做"的约束条件

在这个阶段,我们会明确技术栈选择、架构设计和各种约束条件。关键是要区分"必须遵守"和"建议参考"的约束:

示例1:新商户成长任务模块

【技术约束】
必须遵守:
 - 使用SpringBoot标准分层架构,所有Service继承OcsBaseServiceImpl
 - 数据库操作使用MyBatis-Plus,实体类继承BaseEntity,Mapper继承BaseMapper
 - 接口返回统一使用Result<T>格式,错误码使用ErrorCode
 - 权限校验使用@Permission注解,参数校验使用@Valid + ValidatorUtil
 - 飞书消息发送必须使用FeishuClient,不要重复实现
建议参考:
 - 任务状态流转参考TaskServiceImpl中的状态机模式
 - 批量分配操作参考AssignImportHandler中的异步处理方式
 - 运营人员权限校验参考OperatorRelationServiceImpl
 - 数据权限过滤参考ScopeServiceImpl中的范围查询逻辑
【数据库约束】
 - 新增表必须包含created_at, updated_at, is_deleted字段
 - 表名使用ocs_前缀,字段名使用蛇形命名法
 - 索引设计需考虑查询场景,高频查询字段必须建立索引
 - 外键约束通过代码层面维护,不在数据库层面创建

示例2:机器人问答功能

【技术约束】
必须遵守:
 - Controller层使用@RestController + @RequestMapping,路径遵循/api/v1/{module}/{action}格式
 - Service层业务逻辑必须有事务控制,使用@Transactional(rollbackFor = Exception.class)
 - DTO转换使用项目中的ConvertUtil,不要手动赋值
 - 第三方API调用(如Dify)必须有重试机制和降级策略
 - 敏感配置(API Key)必须从配置中心读取,不要硬编码
建议参考:
 - 对话上下文管理参考RobotServiceImpl中的会话ID生成逻辑
 - 消息发送失败重试参考FeishuMessageHandler中的重试策略
 - 错误处理参考GlobalExceptionHandler中的统一异常处理
【集成约束】
 - Dify API调用统一通过DifyClient,响应格式需转换为项目标准格式
 - 飞书机器人回调处理需实现签名校验,参考FeishuEventHandler
 - 所有外部API调用需记录请求日志到integration_log表 

阶段三:迭代反馈——在"做的过程"中持续对齐

这个阶段的核心是增量验证,避免一次性生成大量代码后才发现方向错误。

实践要点:

1.分模块实现,逐个验证

  • 先实现核心接口(如任务分配),验证通过后再实现辅助功能(如任务查询)。
  • 先实现Service层核心逻辑,验证业务流程正确后再补充Controller和参数校验。

2.关键节点主动暂停

  • 数据库表设计完成后,先让开发者确认表结构再继续。
  • 核心业务逻辑实现完成后,先通过单元测试验证再生成Controller。
  • 集成第三方API前,先确认调用方式和参数映射。

3.持续同步技术方案

  • 发现架构设计与现有代码不一致时,立即更新tech-solution.json和tech-solution.md。
  • 新增数据表、修改核心逻辑时,同步更新技术方案文档。
  • 每个模块完成后,更新模块状态为implemented → reviewed → verified。

对话示例:

开发者: "实现新商户成长任务分配功能"
AI: "我将分三步实现:
1. 先设计数据库表结构(ocs_task_assignment表)
2. 实现核心分配逻辑(NewMerchantTaskAssignService)
3. 实现Controller层接口
现在开始第一步,设计的表结构如下:
[展示表结构]
请确认表结构是否符合需求?"
开发者: "字段没问题,但索引需要加上(operator_id, status)"
AI: "已更新索引设计,现在开始实现Service层核心逻辑...
[生成Service代码]
核心逻辑已实现,包含:
- 任务池查询(按优先级过滤)
- 运营人员负载检查
- 任务分配和状态更新
- 飞书消息发送
请验证业务逻辑是否正确?"
开发者: "逻辑正确,继续实现Controller"
AI: "开始实现Controller层...
[生成Controller代码]
已完成模块M1实现,更新技术方案状态为implemented"

对话设计三原则与常见问题应对策略

为什么这样设计:背后的认知科学原理

这种结构化对话设计不是凭空想出来的,而是基于我们对人类认知过程的理解:

  • 工作记忆限制理论: 就像我们一次只能记住7±2个信息块一样,AI的上下文理解能力也是有限的。通过分阶段对话和单次聚焦单模块,我们控制了每次交互的认知负荷。
  • 渐进式知识构建: 学习和理解是一个渐进过程,先掌握整体框架再深入细节,符合认知规律。这和我们教新人时"先讲架构图,再讲模块间交互,最后讲具体实现"的思路是一致的。

四、AI团队协作模式:子代理系统的实践与思考

随着团队使用Claude Code的深入,我们发现单个AI助手已经难以满足复杂项目的开发需求——就像一个人再厉害也干不了一个团队的活。于是,我们开始探索让多个AI"角色"协同工作的模式,这就是子代理(SubAgent)系统的由来。

团队协作的现状与挑战

在传统开发模式中,我们有需求分析师、架构师、开发工程师、测试工程师等不同角色,他们通过文档、会议和代码审查等方式协作。这种模式虽然成熟,但在快节奏的业务迭代中,我们发现了一些问题:

协作中的三大痛点:

  • 信息传递损耗: 需求文档从产品经理到开发再到测试,每经过一个环节就可能产生一些理解偏差。就像玩"电话游戏",信息传到最后可能已经面目全非。
  • 责任边界模糊: 当出现问题时,有时会出现"这是架构设计问题"、"这是实现问题"、"这是测试不充分"的互相推诿。
  • 反馈周期漫长: 从需求分析到代码审查,整个流程走下来往往需要几天时间,等发现问题时可能已经投入了大量开发资源。

这些问题促使我们思考:能不能在Claude Code中模拟团队协作模式,让不同的AI角色各司其职又协同工作?

Claude Code的子代理协作模式

借鉴了MetaGPT等框架的思想,我们在Claude Code中构建了由多个专业化子代理组成的AI团队协作系统。每个子代理承担特定角色,通过标准化中间产物协同工作。

核心工作机制:中间产物驱动

所有子代理通过共享"技术方案文档"进行协作,这个文档就像团队的"共享白板",包含需求分析、模块划分、实现状态和接口设计等关键信息。每个子代理只负责修改文档中与自己角色相关的部分,确保信息一致性。

四个核心子代理角色

技术方案架构师

负责需求分析、技术方案设计和模块划分。相当于团队里的架构师,输出"技术方案文档"这个"施工蓝图"。

核心职责:

  • 需求拆解与模块划分
  • 技术栈选型与架构设计
  • 接口定义与数据模型设计
  • 模块间依赖关系梳理
  • 技术方案文档编写与维护

代码审查专家

负责代码质量审查。扮演技术负责人的角色,从架构合规性、代码规范和稳定性等角度挑毛病。

核心职责:

  • 检查代码是否符合架构设计
  • 验证代码规范和命名约定
  • 识别潜在性能问题和bug
  • 评估代码可维护性和扩展性
  • 提供具体修改建议

代码实现专家

专注于代码实现和单元测试编写。就像主力开发工程师,按照架构师设计的蓝图一块块地实现功能。

核心职责:

  • 根据技术方案实现代码
  • 编写单元测试和集成测试
  • 修复代码审查中发现的问题
  • 编写API文档和使用说明
  • 同步更新技术方案实现状态

前端页面生成器

专门负责生成符合我们低代码平台规范的前端页面配置。这是针对我们商家域管理后台特点定制的角色。

核心职责:

  • 根据接口定义生成前端页面配置
  • 实现表格、表单、详情页等标准组件
  • 配置页面权限和数据范围过滤
  • 优化前端交互体验
  • 确保符合设计规范和响应式要求

协作流程

我们采用"先整体规划,再迭代实现"的工作方式,有点像敏捷开发中的Sprint规划+Daily Scrum:

1. 整体规划阶段:

  • 产品经理提供需求文档。
  • 协调者调用"技术方案架构师"子代理分析需求,生成技术方案文档。
  • 团队评审技术方案,提出修改意见。
  • 架构师子代理根据反馈修改方案,直到团队确认。

2. 单模块迭代阶段:

  • 协调者从技术方案文档中选取一个模块。
  • 调用"代码实现专家"生成代码。
  • 调用"代码审查专家"审查代码。
  • 实现专家根据审查意见修改代码。
  • 重复"实现-审查-修改"直到通过。
  • 更新技术方案文档,标记该模块为"已完成"。
  • 进入下一个模块。

子代理协作的价值与局限

实践中的三个显著价值

  • 专业化分工提升质量: 每个子代理专注于特定领域,就像专科医院比综合医院在特定疾病上更专业一样。我们发现,专门的代码审查子代理比通用AI能发现更多潜在问题。
  • 流程标准化降低风险: 通过技术方案文档和明确的角色分工,开发流程被标准化和可视化。新人加入项目时,只要看技术方案文档就能快速了解整体情况。
  • 知识沉淀促进复用: 子代理的专业知识和决策逻辑被编码为可复用的配置和规则,避免了"人走经验丢"的问题。

遇到的四个实际挑战

子代理协作的挑战与应对:

  • 上下文同步问题: 当技术方案文档更新时,各子代理有时不能立即同步最新信息。解决办法:每次修改文档后,明确通知相关子代理"技术方案中XX部分已更新"。
  • 协作边界模糊: 在处理跨模块功能时,出现"该由哪个子代理负责"的困惑。解决办法:在技术方案文档中添加"责任人"字段,明确每个模块由哪个子代理负责。
  • 灵活性与标准化的平衡: 高度标准化的流程有时会限制处理特殊情况的灵活性。解决原则:90%的常规情况严格遵循标准流程,10%的特殊情况由人工介入处理。
  • 错误传递放大效应: 如果技术方案设计阶段就有问题,这个问题会在后续实现和审查阶段被放大。解决办法:加强技术方案的人工评审环节,确保"地基"打牢。

子代理协作的设计思考

在设计这套协作模式时,我们有几个关键思考:

  • 为什么选择"中间产物驱动"而非"直接沟通"?
  • 直接让子代理之间对话可能更灵活,但会导致沟通成本指数级增加(n个代理就有n(n-1)/2种沟通渠道)。通过"技术方案文档"这个单一事实来源,我们大大降低了协作复杂度,也便于追踪变更历史。
  • 角色划分的依据是什么?
  • 我们的角色划分基于软件开发的自然阶段(设计→实现→审查)和专业领域(后端→前端),这符合软件开发生命周期的自然规律。没有盲目追求角色数量,而是根据实际需求逐步增加。
  • 为什么采用"增量迭代"而非"一次性开发"?
  • 复杂系统的构建本质上是一个不断学习和调整的过程。增量迭代让我们能够及早发现问题并调整方向,避免在错误的道路上走得太远。这和我们常说的"小步快跑,快速迭代"理念一致。

五、实践经验与未来展望

经过几个月的Claude Code实践,从最初的"试试看"到现在成为离不开的开发工具,我们积累了一些经验,也对AI编程的未来有了更清晰的认识。

实践经验总结

人机协作的最佳平衡点:

我们发现最有效的AI编程模式是"人类主导,AI辅助",而不是反过来。我们将工作内容分为三类:

  • AI主导: 标准化代码生成(如基础CRUD接口)、单元测试编写、API文档生成等重复性高、规则明确的任务。
  • 人机协作: 技术方案设计、复杂逻辑实现、代码审查等需要结合领域知识和创造性思维的任务。
  • 人类主导: 需求分析、架构设计、质量决策等高风险、高创造性的任务。

上下文管理的实用技巧

管理好对话上下文是用好Claude Code的关键,分享几个我们团队总结的技巧:

  • 对话线程化: 为不同功能模块创建独立对话线程。我们曾经在一个对话里讨论三个不同模块,结果上下文混乱到不得不从头开始。
  • 关键信息锚定: 重要的技术决策和约束要在对话中反复强调。就像写文章时,核心观点要多次出现。
  • 文档外化: 复杂设计和决策要记录在外部文档中,而不是仅依赖对话历史。我们会在对话中引用这些文档:"数据库设计详见/doc/db_design.md,特别是索引设计部分"。
  • 状态可视化: 通过技术方案文档中的进度标记(如[未开始]、[设计中]、[已实现]、[已审查]),直观跟踪开发状态。

质量控制的三个关键策略

使用AI生成代码后,质量控制变得更加重要。我们的做法是:

  • 多层次验证: 单元测试(AI生成)+ 集成测试(人工设计)+ 代码审查(人机结合)的三层验证体系。
  • 渐进式信任: 从简单、低风险模块开始使用AI,建立信任后再逐步扩展。我们最先用AI生成内部工具,验证没问题后才用于核心业务系统。
  • 错误模式学习: 记录AI常犯的错误类型,针对性优化系统提示词。我们有一个"AI错误案例库",记录了"AI忘记处理分布式锁超时"、"日期格式转换错误"等典型问题及解决方案。

AI编程的局限性认知

在实践过程中,我们也清醒地认识到AI编程并非万能解决方案,它有几个明显的局限性:

  • 创造性思维不足: AI擅长在已有知识范围内进行组合和优化,但在需要突破性创新的场景下表现有限。比如我们尝试让AI设计一个全新的商家结算模型时,它还是会倾向于参考现有模型进行修改,难以跳出固有思维框架。
  • 上下文理解深度有限: 尽管Claude Code的上下文窗口已经很大,但对于我们系统中某些"牵一发而动全身"的核心模块,AI还是难以把握其深层设计意图和与其他模块的隐性依赖。
  • 质量责任边界模糊: 当AI生成的代码出现质量问题时,责任界定变得复杂。我们的解决办法是:开发者对AI生成的代码负全部责任,就像我们对自己写的代码负责一样。
  • 领域知识滞后性: AI对我们公司内部系统的最新变更反应不够及时。为此我们建立了"知识库更新机制",每月将最新的系统变更和业务规则整理成文档,供AI参考。

未来发展方向思考

基于这些实践经验,我们对AI编程工具的未来发展有几点思考:

  • 更智能的上下文管理: 未来的AI编程工具应该能自动识别相关上下文、追踪依赖关系,并在适当的时候提醒开发者潜在的上下文冲突。就像经验丰富的团队领导,能记住每个人负责的模块和项目的整体情况。
  • 多模态交互模式: 除了文本对话,未来可能引入图表、流程图等多种交互方式。有时画一个简单的流程图(PlantUML),比写几百字描述更能说明问题。
  • 自适应学习机制: AI编程工具应该能从团队的使用反馈中学习,适应特定团队的编码风格和业务领域。就像新加入团队的开发者,会逐渐适应团队的工作方式。

六、结语:人机协作的新型开发范式

回顾这几个月使用Claude Code的经历,我们最大的体会是:AI编程工具的价值不在于替代开发者,而在于构建人机协作的新型开发范式。在这种范式下,人类开发者从繁琐的重复劳动中解放出来,更专注于需求分析、架构设计和质量把控等高价值创造性工作,而AI则承担起代码实现、文档生成和基础验证等标准化工作。

Claude Code作为我们实践的核心工具,通过精准对话流设计、模块化任务分解和专业化子代理协作,展示了这种新型开发范式的潜力。但我们也认识到,成功的AI编程应用需要"工具+方法论+团队协作"三位一体的系统性变革,其中人的角色从"代码生产者"向"问题解决者"和"质量把控者"转变。

作为开发者,我们需要保持开放学习的心态,积极探索和适应这种新范式。未来已来,与其恐惧被AI替代,不如学会与AI协作,在人机协作中实现更高的个人价值和团队效能。毕竟,代码只是解决问题的手段,而非目的;AI只是增强我们能力的工具,而真正的创新和价值,始终源于人的智慧和创造力。

实践启示: 在AI编程时代,最有价值的开发者不是"写代码最快的人",而是"最会引导AI、最能把控质量、最能解决复杂问题的人"。掌握与AI协作的技巧,建立系统化的AI辅助开发流程,将成为未来开发者的核心竞争力。我们的经验表明,通过合理设计对话流程、明确分工协作和严格质量控制,AI编程工具能够显著提升团队效能,但这需要整个团队在思维方式和工作流程上的共同转变。

往期回顾

1.入选AAAI-PerFM|得物社区推荐之基于大语言模型的新颖性推荐算法

2.Galaxy比数平台功能介绍及实现原理|得物技术 

3.得物App智能巡检技术的探索与实践

4.深度实践:得物算法域全景可观测性从 0 到 1 的演进之路

5.前端平台大仓应用稳定性治理之路|得物技术

文 /稚归

关注得物技术,每周更新技术干货

要是觉得文章对你有帮助的话,欢迎评论转发点赞~

未经得物技术许可严禁转载,否则依法追究法律责任。

编者按: 我们今天为大家带来的这篇文章,作者的核心观点是:相较于依赖复杂且高成本的动态 MCP 工具加载机制,以 Skills 为核心的能力摘要与自维护模式,在当前阶段反而更加高效、稳定且可控。

文章系统梳理了延迟工具加载(deferred tool loading)的工程现实与限制,指出即便工具可以延后注入,对话级别的工具集合仍然是静态的,且发现机制高度依赖正则匹配,收益并不如预期。作者进一步深入分析了 MCP 在上下文占用、API 稳定性、缓存失效与推理轨迹丢失等方面带来的隐性成本,并结合 Sentry MCP、Playwright 等实践案例,说明为何将 MCP 转换为 Skills,反而能让 Agent 更好地发挥既有工具的能力。文章最后还探讨了 MCP 是否可能完全转化为 Skills 的可行性,并坦率指出当前协议与生态在稳定性与摘要机制上的不足。

作者 | Armin Ronacher

(作者为 Flask、Jinja2 等开源项目的创建者)

编译 | 岳扬

我正把所有的 MCP 都迁移到 Skills 上,包括之前还在使用的最后一个:Sentry MCP(译者注:Sentry 是流行的应用监控与错误追踪平台)。早前我就已经完全弃用 Playwright(译者注:由 Microsoft 开发的现代 Web 自动化测试和浏览器自动化框架),转向使用 Playwright Skill。

过去一个月左右,关于使用“动态工具配置(dynamic tool loadouts)[1]”来推迟工具定义的加载的讨论一直不少。Anthropic 也在探索通过代码来串联 MCP 调用的思路,这一点我也尝试过[2]。

我想分享一下自己在这方面的最新心得,以及为什么 Anthropic 提出的“延迟工具加载方案(deferred tool loading)”并未改变我对 MCP 的看法。或许这些内容对他人会有所帮助。

01 什么是工具(Tool)?

当 Agent 通过强化学习或其他方式接触到工具定义时,它会被鼓励在遇到适合使用该工具的场景时,通过特殊的 token 输出工具调用。实际上,工具定义只能出现在系统提示词(system prompt)中特定的工具定义 token 之间。从历史经验来看,这意味着我们无法在对话状态的中途动态发出新的工具定义。因此,唯一的现实选择是在对话开始时就将工具加载好。

在智能体应用场景中,我们当然可以随时压缩对话状态,或更改系统消息中的工具定义。但这样做的后果是,我们会丢失推理轨迹(reasoning traces)以及缓存(cache)。以 Anthropic 为例,这将大幅增加对话成本:基本上就是从头开始,相比于缓存读取,需要支付完整的 token 费用,外加缓存写入成本。

Anthropic 最近的一项创新是“延迟工具加载”(deferred tool loading)。我们仍然需要提前在系统提示词(system message)中声明工具,但这些工具不会在系统提示词发出时就注入到对话中,而是会稍后才出现。不过据我所知,这些工具定义在整个对话过程中仍必须是静态的 —— 也就是说,哪些工具可能存在,是在对话开始时就确定好的。 Anthropic 发现这些工具的方式,纯粹是通过正则表达式(regex)搜索实现的。

02 与 Skills 的对比

尽管带延迟加载的 MCP 感觉上应该表现更优,实际上却需要在 LLM API 端做不少工程化工作。而 Skills 系统完全不需要这些,至少从我的经验来看,其表现依然更胜一筹。

Skills 实质上只是对现有能力及其说明文件位置的简短摘要。这些信息会被主动加载到上下文中。 因此,智能体能在系统上下文里(或上下文的其他位置)知晓自己具备哪些能力,并获知如何使用这些能力的“手册链接”。

关键在于,Skills 并不会真正把工具定义加载到上下文中。 可用工具保持不变:bash 以及智能体已有的其他工具。Skills 所能提供的,只是如何更高效使用这些工具的技巧和方法。

由于 Skills 主要教的是如何使用其他命令行工具和类似实用程序,因此组合与协调这些工具的基本方式其实并未改变。让 Claude 系列模型成为优秀工具调用者的强化学习机制,恰好能帮助处理这些新发现的工具。

03 MCP 能否转换为 Skills?

这自然引出了一个问题:既然 Skills 效果这么好,我能不能把 MCP 完全移出上下文,转而像 Anthropic 提议的那样,通过 CLI 来调用它?答案是:可以,但效果并不好。Peter Steinberger 的 mcporter[3] 就是其中一种方案。简单来说,它会读取 .mcp.json 文件,并将背后的 MCP 暴露为可调用的工具:

npx mcporter call 'linear.create_comment(issueId: "ENG-123", body: "Looks good!")'

确实,它看起来非常像一个 LLM 可以调用的命令行工具。但问题在于,LLM 根本不知道有哪些工具可用 —— 现在你得专门教它。于是你可能会想:那为什么不创建一些 Skills,来教 LLM 了解这些 MCP 呢?对我而言,这里的问题在于:MCP 服务器根本没有维持 API 稳定性的意愿。它们越来越倾向于将工具定义精简到极致,只为节省 token。 这种做法有其道理,但对 Skills 模式来说却适得其反。举个例子,Sentry MCP 服务器曾彻底将查询语法切换为自然语言。这对 Agent 来说是一次重大改进,但我之前关于如何使用它的建议反而成了障碍,而且我没能第一时间发现问题。

这其实和 Anthropic 的“延迟工具加载方案”非常相似:上下文中完全没有任何关于该工具的信息,我们必须手动创建一份摘要。我们过去对 MCP 工具采用的预加载(eager loading)方式,如今陷入了一个尴尬的局面:描述既太长,不便预加载;又太短,无法真正教会 Agent 如何使用它们。 因此,至少从我的经验来看,你最终还是得为通过 mcporter 或类似方式暴露出来的 MCP 工具,手动维护这些 Skills 摘要。

04 最省事的路线

这让我得出了目前的结论:我倾向于选择最省事的方式,也就是让 Agent 自己以“Skills”的形式编写所需的工具。 这样做不仅耗时不多,最大的好处还在于工具基本处于我的掌控之中。每当它出问题或需要新增功能时,我就让 Agent 去调整它。Sentry MCP 就是个很好的例子 —— 我认为它可能是目前设计得最好的 MCP 之一,但我已经不再使用它了。一方面是因为一旦在上下文中立即加载它,就会直接消耗约 8k 个 token;另一方面,我也一直没能通过 mcporter 让它正常工作。现在我让 Claude 为我维护一个对应的 Skill。没错,这个 Skill 可能有不少 bug,也需要不断更新,但由于是 Agent 自己维护的,整体效果反而更好。

当然,这一切很可能在未来发生变化。但就目前而言,手动维护的 Skills,以及让 Agent 自行编写工具,已成为我的首选方式。我推测,基于 MCP 的动态工具加载终将成为主流,但要实现这一点,可能还需要一系列协议层面的改进,以便引入类似 Skills 的摘要机制,以及为工具内置使用手册。 我也认为,MCP 如果能具备更强的协议稳定性,将大有裨益。目前 MCP 服务器随意更改工具描述的做法,与那些已经固化下来的调用方式(materialized calls)以及在 README 和技能文件中编写的外部工具说明很难兼容。

END

本期互动内容 🍻

❓抛开现有方案,你理想中的AI工具调用范式应该长什么样?用一句话描述你最核心的需求。

文中链接

[1]https://www.anthropic.com/engineering/advanced-tool-use

[2]https://lucumr.pocoo.org/2025/7/3/tools/

[3]https://github.com/steipete/mcporter

原文链接:

https://lucumr.pocoo.org/2025/12/13/skills-vs-mcp/

导读

AI 编码工具正在从"智能补全"演进为能自主完成复杂任务的 Coding Agent。本文基于开源项目源码研究与实践经验,系统性地拆解 Coding Agent 的工作原理。旨在帮助开发者在了解Coding Agent后,与AI伙伴更好的协作配合,更高效的提问和拿到有效结果。

01 背景

AI 编码工具的发展速度快得有点"离谱"。从开始使用 GitHub Copilot 的代码补全,到使用Claude Code、Cursor、Comate IDE等完成复杂编程任务,AI 不再只是个「智能补全工具」,它能读懂你的代码库、执行终端命令、甚至帮你调试问题,成为你的“编码伙伴”。

我自己在团队里推 AI 编码工具的时候,发现一个很有意思的现象:大家都在用,但很少有人真正理解它是怎么工作的。有人觉得它"很神奇",有人吐槽它"经常乱来",还有人担心"会不会把代码搞乱"。这些困惑的背后,其实都指向同一个问题:我们对这个"伙伴"还不够了解。

就像你不会无脑信任一个新来的同事一样,要和 AI 编码伙伴配合好,你得知道它的工作方式、能力边界、以及怎么"沟通"才更有效。

在经过多次的实践尝试后,我尝试探索它的底层原理,并写下了这篇文章记录,主要围绕了这些内容展开:

  • Coding Agent 的核心工作机制,包括身份定义、工具调用、环境感知等基础组成。
  • 从零实现一个最小化 Coding Agent 的完整过程,以建立对 Agent 工作流程的直观理解。
  • 上下文管理、成本控制、冲突管控等生产环境中的关键技术问题及其解决方案。
  • Rule、MCP、Skill 等能力扩展机制的原理与应用场景。

在了解原理后,我和伙伴的协作更佳顺畅,让伙伴更清晰的了解我的意图,我拿到有效的回答。

02 概念

2.1 从Workflow到Agent

取一个实际的例子:休假申请。

如果我们的需求非常简单:

一键申请明天的休假。

在这里插入图片描述

这个需求可以被简化为一个固定的工作流

  1. 打开网页。
  2. 填写起始时间。
  3. 填写结束时间。
  4. 填写休假原因。
  5. 提交表单。

全过程没有任何模糊的输入,使用程序化即可完成,是最原始的工作流形态。

如果需求再模糊一些:

申请后天开始3天休假。

这个需求的特点是没有明确的起始和截止时间,需要从语义上分析出来

  1. 起始时间:后天。
  2. 休假时长:3天。
  3. 转换日期:10.14 - 10.16。
  4. 执行申请:提交表单。

这是一个工作流中使用大模型提取部分参数的典型案例,是模型与工作流的结合。

如果需求更加模糊:

国庆后休假连上下个周末。

这样的需求几乎没有任何直接确定日期的信息,同时由于年份、休假安排等动态因素,大模型不具备直接提取参数的能力。将它进一步分解,需要一个动态决策、逐步分析的过程:

  1. 知道当前年份。
  2. 知道对应年份的国庆休假和调休安排。
  3. 知道国庆后第一天是星期几。
  4. 国庆后第一天到下个周末设为休假日期。
  5. 额外补充调休的日期。
  6. 填写并提交表单。

可以看出来,其中1-5步都是用来最终确定休假日期的,且需要外部信息输入,单独的大模型无法直接完成工作。这是一个典型的Agent流程,通过大模型的智能工具访问外部信息结合实现用户需求。

2.2 什么是Agent

Agent是以大模型为核心,为满足用户的需求,使用一个或多个工具,自动进行多轮模型推理,最终得到结果的工作机制。

2.3 什么是Coding Agent

在Agent的基本定义的基础上,通过提示词、上下文、工具等元素强化“编码”这一目的,所制作的特化的Agent即为Coding Agent。

Coding Agent的最大特征是在工具的选取上,模拟工程师进行代码编写的环境,提供一套完整的编码能力,包括:

  • 阅读和查询代码:

    • 读取文件,对应 cat 命令。
    • 查看目录结构,对应 tree 命令。
    • 通配符查找,对应 ls命令(如 **/*.test.tssrc/components/**/use*.ts)。
    • 正则查找,对应grep 命令(如function print\(.+\) 可以找函数定义)。
    • LSP(Language Server Protocol),用于提供查找定义、查找引用、检查代码错误等能力。
  • 编写或修改代码:

    • 写入文件。
    • 局部编辑文件。
    • 删除文件。
  • 执行或交互命令:

    • 执行终端命令。
    • 查看终端命令stdout输出。
    • 向终端命令stdin 输入内容。

除此之外,通常Coding Agent还具备一些强化效果而设定的工具,通常表现为与Agent自身或外部环境进行交互,例如经常能见到的TODO、MCP、Subagent等等。

03 内部组成

3.1 上下文结构

3.2 身份定义

一个Agent首先会将模型定义成一个具体的身份(红色与橙色部分),例如在社区里常见的这样的说法:

You are a Senior Front-End Developer and an Expert in React, Nexts, JavaScript, TypeScript, HTML, CSS and modern UI/UX frameworks.

在身份的基础上,再附加工作的目标和步骤拆解,比如Cline有类似这样的内容:

https://github.com/cline/cline/blob/4b9dbf11a0816f792f0b3229a08bbb17667f4b73/src/core/prompts/system-prompt/components/objective.ts

  1. Analyze the user's task and set clear, achievable goals to accomplish it. Prioritize these goals in a logical order.
  2. Work through these goals sequentially, utilizing available tools one at a time as necessary. Each goal should correspond to a distinct step in your problem-solving process. You will be informed on the work completed and what's remaining as you go.
  3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. Before calling a tool, do some analysis within <thinking></thinking> tags. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Then, think about which of the provided tools is the most relevant tool to accomplish the user's task. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params). DO NOT ask for more information on optional parameters if it is not provided.
  4. Once you've completed the user's task, you must use the attempt_completion tool to present the result of the task to the user. You may also provide a CLI command to showcase the result of your task; this can be particularly useful for web development tasks, where you can run e.g. open index.html to show the website you've built.
  5. The user may provide feedback, which you can use to make improvements and try again. But DO NOT continue in pointless back and forth conversations, i.e. don't end your responses with questions or offers for further assistance.

不用特别仔细地看每一句话,多数Coding Agent会提供一些详实的行动准则、目标要求,这部分称为“Guideline”。

有一些Coding Agent可以在多种模式(或者说智能体)之间进行切换,例如Cursor有Edit、Ask、Plan等,RooCode有Architect、Orchestrator等,有些产品还支持自定义模式。

Cursor

RooCode

选择不同的模式时,实际上会产生不同的目标要求、行为准则,即不同的Guideline环节。因此系统提示词中的身份部分,通常会分成不变的Base Prompt(红色)和可变的Agent Prompt(橙色)两个部分来管理,实际开始任务时再拼装起来。

3.3 工具调用

Agent的另一个最重要的组成部分是工具,没有工具就无法称之为一个Agent。让Agent能够使用工具,就必须要有2部分信息:

  1. 有哪些工具可以用,分别是什么作用。
  2. 如何指定使用一个工具。

对于第一点(哪些工具),在Agent开发过程中,一般视一个工具为一个函数,即由以下几部分组成一个工具的定义:

  1. 名称。
  2. 参数结构。
  3. 输出结构。

实际在调用模型时,“输了结构”往往是不需要提供给模型的,但在Agent的实现上,它依然会被预先定义好。而“名称”和“参数结构”会统一组合成一个结构化的定义,通常所有工具都只接收1个参数(对象类型),用JSON Schema表示参数结构。

一个典型的工具定义:

{
  "name": "read",
  "description": "Read the contents of a file. Optionally specify line range to read only a portion of the file.",
  "parameters": {
    "type": "object",
    "properties": {
      "path": {
        "type": "string",
        "description": "The file path to read from"
      },
      "lineStart": {
        "type": "integer",
        "description": "The starting line number (1-indexed). If not specified, reads from the beginning of the file."
      },
      "lineEnd": {
        "type": "integer",
        "description": "The ending line number (1-indexed). If not specified, reads to the end of the file."
      }
    },
    "required": ["path"]
  }
}

可以简单地把这个工具理解成对应的TypeScript代码:

interface ReadToolParameter {
        path: string;
        lineStart?: number;
        lineEnd?: number;
}

async function read(parameters: ReadToolParameter) {
        // 工具实现
}

对于第2点(指定使用工具),则是要让大模型知道工具调用的具体格式。这在业界通常有2种做法。

第1种以Claud Code、Codex等为典型,使用大模型提供的Function Calling格式调用,分为以下几步:

  1. 在调用大模型时,通过一个tools 字段传递所有的工具定义。
  2. 模型会返回一个消息中包含tool_calls 字段,里面每一个对象是一个工具的调用,使用id 作为唯一标识。
  3. 工具产生的结果,以一条role: 'tool' 的消息返回,其中tool_call_id 与调用的id对应,content 是工具的结果(这里各家模型厂商的实现略有不同,其中Anthropic要求role: user,但content字段中传递toolResult,其结构是[{type: 'tool_result',tool_use_id: toolBlock.id, content: toolResultContent}],tool_use_id与调用的id对应)。

第2种方式是以Cline、RooCode为典型,使用一种自定义的文本格式来表示工具调用,通常选择XML的结构,例如对于Cline,读取一个文件的结构如下:

<read_file>
<path>src/index.ts</path>
</read_file>

只要在模型返回的消息中出现这样的结构,就会被解析为一个工具调用,得到的结果以普通的role: 'user' 的消息返回,包括实际内容和一些提示相关的信息。

Content of src/index.ts:

Note:

- this file is truncated to line 1000, file has a total 2333 lines.
- use read_file with line_start and line_end parameters to read more content.
- use seach_in_files tool searching for specific patterns in this file.

...

3.4 环境感知

Coding Agent之所以可以在一个代码库上执行任务,除了通过工具来遍历、检索代码外,另一个因素是Agent实现会在调用模型时主动地提供一部分与项目有关的信息。

其中对Coding Agent工作最有用的信息之一是代码库的结构,即一个表达出目录、文件结构的树型区块。这部分信息通常会符合以下特征:

  1. 尽可能地保留目录的层级结构,使用换行、缩进的形式表达。
  2. 遵循 .gitignore 等项目配置,被忽略的文件不会表现在树结构中。
  3. 当内容过多时,有一定的裁剪的策略,但同时尽可能多地保留信息。

以Cursor为例,这部分的内容大致如下:

<project_layout>
Below is a snapshot of the current workspace's file structure at the start of the conversation. This snapshot will NOT update during the conversation. It skips over .gitignore patterns.

codex-cursor/
  - AGENTS.md
  - CHANGELOG.md
  - cliff.toml
  - codex-cli/
    - bin/
      - codex.js
      - rg
    - Dockerfile
    - package-lock.json
    - package.json
    - scripts/
      - build_container.sh
      - build_npm_package.py
      - init_firewall.sh
      - [+4 files (1 *.js, 1 *.md, 1 *.py, ...) & 0 dirs]
  - codex-rs/
    - ansi-escape/
      - Cargo.toml
      - README.md
      - src/
        - lib.rs
</project_layout>

当内容数量超过阈值时,会采用广度优先的保留策略(即尽可能地保留上层目录结构),同时对于被隐藏的文件或子目录,会形如 [+4 files (1 *.js, 1 *.md, 1 *.py, ...) & 0 dirs]这样保留一个不同文件后缀的数量信息。

除了目录结构外,还有一系列默认需要模型感知的信息,在一个Coding Agent的工作环境中,它通常分为2大类,各自又有一系列的细项:

  1. 系统信息:

    1. 操作系统(Windows、macOS、Linux,具体版本)。
    2. 命令行语言(Shell、Powershell、ZSH)。
    3. 常见的终端命令是否已经安装( python3nodejqawk等,包含具体版本)。
    4. 代码库目录全路径。
  2. 为Agent扩展能力的信息:

    1. Rule(自动激活的部分)。
    2. Skill(摘要描述部分)。
    3. MCP(需要的Server和Tool列表)。
    4. Memory(通常是全量)。

需要注意的是,环境信息这部分,一般不出现在系统提示词中,而是和用户提问的消息放置在一起。

3.5 简单实现

在身份定义、工具调用、环境感知这3部分最基础的Agent组成都达成后,简单地使用大模型的API,进行自动化的工具调用解析、执行、发送新一轮模型调用,可以非常简单地实现一个最小化的Coding Agent。

可以尝试用以下的提示词,使用任意现有的Coding Agent产品,为你编写一个实现,并自己调试一下,感受Coding Agent的最基础的逻辑:

我希望基于大模型实现一个Coding Agent,以下是我的具体要求:

1. 使用Claude作为模型服务商,使用环境变量管理我的API Key。
2. 默认使用Claude Sonnet 4.5模型。
3. 使用Anthropic's Client SDK调用模型。
4. 不需要支持流式输出。
5. 使用TypeScript编写。

以下是Agent提供的工具:

1. read({path: string}):读取一个文件的内容
2. list({directory: string}):列出一个目录下的一层内容,其中目录以`/`结尾
3. write({path: string, content: string}):向文件写入内容
4. edit({path: string, search: string, replace: string}):提供文件中的一块内容

以下是交互要求:

1. 通过NodeJS CLI调用,支持`query`和`model`两个参数,可以使用`yargs`解析参数。
2. 在System消息中,简短地说明Coding Agent的角色定义、目标和行为准则等。
3. 在第一条User消息中,向模型提供当前的操作系统、Shell语言、当前目录绝对路径信息,同时包含跟随`query`参数的内容,组织成一条模型易于理解的消息。
4. 对每一次模型的工具调用,在控制台打印工具名称和标识性参数,其中标识性参数为`path`或`directory`,根据工具不同来决定。
5. 如果模型未调用工具,则将文本打印到控制台。

请在当前目录下建立一个`package.json`,并开始实现全部的功能。

04 优质上下文工程

4.1 成本控制

大模型是一个非常昂贵的工具,以Claude为例,它的官方API价格如下:

我们可以观察到一些特征:

  1. 输出的价格是输入的5倍(但实际考虑到输出与输出的数量比例,输出的价格根本不值一提)。
  2. 缓存输入(Cache Writes)比正常输入(Base Input)更贵一些,约1.25倍。
  3. 缓存命中(Cache Hits)的价格比正常输入(Base Input)要便宜很多,为1/10的价格。

这就意味着,一个良好使用缓存的Agent实现,其成本会比不用缓存降低8-10倍。因此所有的Coding Agent一定会细致地梳理内容结构,最大化利用缓存

在大模型的API中,缓存通常以“块”为单位控制,例如:

  1. 系统提示词中不变的部分。
  2. 系统提示词中可变部分。
  3. 工具定义。
  4. 每一条消息,单条消息也可以拆成多个块。

继续观察Claude对于缓存控制的文档:

可以看到,在大模型API中各种参数一但有所变动,缓存都会大量失效(至少消息缓存全部失效,大概率系统缓成失效),这就会造成成本的极大提升。因此,在Coding Agent实现中,都会从一开始就确定所有参数,整个任务不做任何变更。一些很经典的实例:

  1. 一次任务不会一部分消息开思考模式,一部分不开,因为思考参数会让全部的消息缓存失效。
  2. 切换不同模式(如Edit、Ask、Plan)时,虽然能使用的工具不同,但只是在消息中增加说明,而不会真的将 tools 字段改变。

另外,Coding Agent会尽可能保持历史消息内容完全不变,以最大化地缓存消息。例如对于一个进行了10轮模型调用的任务,理论上第10次调用中,前9轮的消息内容都会命中缓存。但如果此时擅自去修改了第1轮的工具调用结果(例如试图删除读取的文件内容),看似可能消息的长度减少了,但实际因为缓存被破坏,造成的是10倍的成本提升。

总而言之,缓存是一个至关重要的因素,Coding Agent的策略优化通常以确保缓存有效为前提,仅在非常必要的情况下破坏缓存

4.2 空间管理

Coding Agent因为会自动地与大模型进行多轮的交互,随着不断地读入文件、终端命令输出等信息,上下文的长度会变得非常的大,而大模型通常只具备128K左右的总长度,因此如何将大量内容“适配”到有限的长度中,是一个巨大的挑战。

控制上下文长度的第一种方式是“裁剪”,即在整个上下文中,将没用的信息删除掉。试想如下的场景:

  1. 模型读取了一个文件的内容。
  2. 模型将文件中 foo 这一行改成了 bar
  3. 模型又将文件中 eat 这一行改成了 drink

假设我们对模型每一次修改文件,都返回最新的文件内容,如果这个文件有1000行,那么1次读取、2次修改,就会产生3000行的空间占用

一种优化方式就是,在这种连续的读-改的场景下,只保留最后一条消息中有全文内容,即上述3次模型调用后,出现在上下文中的内容实际是这样的:

<!-- Assistant -->
read(file)

<!-- User -->
[This file has been updated later, outdated contents are purged from here]

<!-- Assistant -->
edit(file, foo -> bar)

<!-- User -->
The edit has been applied successfully.

--- a/file
+++ b/file
@@ -23,1 +23,1 @@
-foo
+bar

[This file has been updated later, outdated contents are purged from here]

<!-- Assistant -->
edit(file, eat -> drink)

<!-- User -->
The edit has been applied successfully, the new file content is as below:

{content of file}

可以看到,通过将连续对同一文件的修改进行裁剪,可以只保留最新的内容,同时又使用unidiff 之类的形式保留中间编辑的差异信息,最大限度地降低空间占用,又能保留模型的推理逻辑。

但裁剪不能使用在非连续的消息中,随意地使用剪裁逻辑,很有可能破坏消息缓存结构,进而使模型调用的输入无法通过缓存处理,几倍地增加模型的调用成本。

即便裁剪有一定效果,但随着更多的内容进入到上下文中,始终会有将上下文占满的时候,此时模型将完全无法进行推理。为了避免这种情况出现,Coding Agent通常会使用“压缩”这一技术,即将前文通过模型摘要成少量的文字,同时又保留比较关键的推理链路。

通常,压缩在上下文即将用完的时候触发,如已经使用了90%的上下文则启动压缩,压缩的目标是将90%的内容变为10%的长度,即省出80%的空间供后续推理。

压缩本身是一个模型的任务,即将所有的上下文(可以选择性地保留最新的1-2对消息)交给模型,同时附带一个压缩的要求,让模型完成工作。这个压缩的要求的质量将决定压缩的最终结果,一个比较典型的实现是Claude Code的“八段式摘要”法:

const COMPRESSION_SECTIONS = [
  "1. Primary Request and Intent",    // 主要请求和意图
  "2. Key Technical Concepts",        // 关键技术概念
  "3. Files and Code Sections",       // 文件和代码段
  "4. Errors and fixes",              // 错误和修复
  "5. Problem Solving",               // 问题解决
  "6. All user messages",             // 所有用户消息
  "7. Pending Tasks",                 // 待处理任务
  "8. Current Work"                   // 当前工作
];

通过将信息压缩成8部分内容,能够最大限度地保留工作目标、进度、待办的内容。

4.3 独立上下文

在实际的应用中,其实大概率是不需要128K上下文用满的,但真实表现又往往是上下文不够用。这中间存在的差异,在于2类情况:

  1. 为了满足一个任务,需要收集大量的信息,但收集到正常信息的过程中,会引入无效的、错误的内容,占用上下文。
  2. 一个任务足够复杂,分解为多个小任务后各自占用部分上下文,但加起来以后会超出限制。

试想一下,对于一个这样的任务:

修改我的Webpack配置,调整文件拆分逻辑,让最终产出的各个JS文件大小尽可能平均。

但是很“不幸”地,这个项目中存在6个 webpack.config.ts文件,且最终splitChunks 配置在一个名为 optimization.ts 的文件中管理,那么对于Coding Agent来说,这个任务中就可能存在大量无意义的上下文占用:

  1. 读取了6个 webpack.config.ts ,一共2000行的配置内容,但没有任何splitChunks 的配置,包含了大量 import 其它模块。
  2. 又读取了10个被 import 的模块,最终找到了 optimization.ts 文件。
  3. 经过修改后,执行了一次 npm run build 来分析产出,发现JS的体积不够平均。
  4. 又修改 optimization.ts ,再次编译,再看产出。
  5. 循环往复了8次,终于在最后一次实现了合理的splitChunks 配置。

这里面的“6个 webpack.config.ts ”、“10个其它模块”、“8次优化和编译”都是对任务最终目标并不有效的内容,如果它们占用150K的上下文,这个任务就不得不在中途进行1-2次的压缩,才能够最终完成。

为了解决这个问题,当前多数的Coding Agent都会有一个称为“Subagent”的概念。就好比一个进程如果只能使用4GB的内存,而要做完一件事需要16GB,最好的办法就是开5个进程。Subagent是一种类似子进程的,在独立的上下文空间中运行,与主任务仅进行必要信息交换的工作机制

再回到上面的案例,在Subagent的加持下,我们可以将它变成以下的过程:

  1. 启动一个Subagent,给定目标“找到Webpack文件拆分的代码”。

    1. 读取6个 webpack.config.ts
    2. 读取10个被 import 的模块。
    3. 确定目标文件 optimization.ts
    4. 返回总结:在 optimization.ts 中有文件拆分的配置,当前配置为……。
  2. 启动一个Subagent,给定目标“修改 optimization.ts ,使产出的JS体积平均,执行 npm run build 并返回不平均的文件“。

    1. 修改 optimization.ts
    2. 执行 npm run build,得到命令输出。
    3. 分析输出,找到特别大的JS文件,返回总结:配置已经修改,当前 xxx.js 体积为平均值的3倍(723KB),其它文件体积正常。
  3. 启动一个Subagent,给宝目标“分析 dist/stats.json,检查 xxx.js 中的模块,修改 optimization.ts 使其分为3个250KB左右的文件,执行 npm run build并返回不平均的文件”。

    1. ……
    2. ……
  4. 继续启动6次Subagent,直到结果满意。

不难看出来,这种模式下主体的Coding Agent实际是在"指挥"Subagent做事,自身的上下文占用是非常有限的。而Subagent仅“专注”于一个小目标,也不需要太多的上下文,最终通过这类不断开辟新上下文空间的方式,将一个复杂的任务完成。

4.4 注意力优化

如果你经常使用Coding Agent,或在业界早期有过比较多的使用经验,你可能会发现这种情况:Coding Agent在完成一个任务到一半时,忘了自己要做什么,草草地结束了任务,或偏离了既定目标产生很多随机的行为。

会发生这样的情况,有一定可能是裁剪、压缩等策略使有效的上下文信息丢失了,但更多是因为简单的一个用户需求被大量的代码内容、命令输出等推理过程所掩盖,权重弱化到已经不被大模型“注意到”,因此最初的目标也就完全丢失了。

Coding Agent一个很重要的任务,就是在长时间运作的同时随时调整大模型的注意力,使其始终聚焦在最终目标、关注当前最需要做的工作,不要偏离预先设定的路线。为了实现这一效果,Coding Agent产品提出了2个常见的概念。

第一称为TODO,在很多的产品中,你会看到Agent先将任务分解成几个步骤,转为一个待办列表。这个列表在界面上始终处于固定的位置,随着任务的推进会逐步标记为完成。这个TODO实际上并不是给用户看的,而是给模型看的

在实际的实现中,每一次调用模型时,在最后一条消息(一般就是工具调用的结果)上,除了原始消息内容外,会增加一个称为“Reminder”的区域。这个区域因为始终出现在所有消息的最后,通常来说在模型的注意力中优先级更高,而且绝对不会受其它因素影响而消失

Reminder中可以放置任意内容,比较经典的有:

  1. TODO及进度。用于模型时刻理解目标、进展、待办。
<reminders>
- Planned todos:
  - [x] Explore for code related to "print" function
  - [x] Add "flush" parameter to function
  - [ ] Refactor all "print" function calls to relect the new parameter
</reminders>
  1. 工具子集。如前面《缓存》相关的描述,因为修改工具定义会使缓存失效,因此当切换模式使得可用的工具减少时,一般仅在Reminder中说明部分工具不可用,由模型来遵循这一约束,而不是直接删除部分工具。
<!-- 切换至Ask模式 -->
<reminders>
- You can ONLY use these tools from now on:
  - read
  - list
  - grep
  - bash
</reminders>
  1. 行为指示。例如当模型连续多次给出名称、参数都一模一样的工具调用时,说明模型处在一种不合理的行为表现上,此时在Reminder中增加提示,让模型感知到当前状态的错误,就有可能调整并脱离错误的路线。
<!-- Assistant -->
read(file)

<!-- User -->
The file content: ...

<!-- Assistant -->
read(file)

<!-- User -->
The file content: ...

<reminders>
- Your are using read tool the second time with exactly the same parameters, this usually means an unexpected situation, you should not use this tool again in your response.
</reminders>
  1. 状态提示。例如激活某一个Skill时,Reminder中可以提示“当前正在使用名为X的Skill“,这种提示可以让模型更加专注于完成一个局部的工作。
<reminders>
- You are currently working with the skill "ppt" active, be focused on this task until you quit with exit_skill tool.
</reminders>

需要额外注意的是,Reminder仅在最后一条消息中出现,当有新的消息时,旧消息上的Reminder会被移除。基于这一特征,我们知道Reminder是永远无法命中缓存的,因此Reminder部分的内容长度要有控制,避免造成过多的成本消耗。

4.5 冲突管控

随着Coding Agent能力的发展,当下执行的任务时间越来越长、编辑的文件越来越多,同时更多的用户也习惯于在Agent工作的同时自己也进行编码工作,甚至让多个Agent任务并发执行。这种“协同”形态下,不少用户曾经遇到过这样的问题:

自己将Agent生成的代码做了一些修正,但之后Agent又把代码改了回去。

这个现象的基本原因也很清楚,就是Agent并不知道你改动过代码。例如以下的过程使Agent读取并编辑了一个文件:

<!-- Assistant -->
read(file)

<!-- User -->
The file content:
...
console.log('hello');
...
<!-- Assistant -->
edit(file, hello -> Hello)

<!-- User -->
Edit has been applied successfully.

这个时候,在模型见到的上下文中,这个文件中的代码显然是console.log('Hello'); 。假设乃又将它改成了console.trace('Hello'); ,后面模型依然会基于.log 来修改代码,用户看起来就是代码“改了回去”。

解决这种共同编辑文件的冲突,实际上有多种方法:

  • 加锁法。当Agent读取、编辑一个文件时,更新模型认知的文件内容的快照。当这个Agent再一次编辑这个文件时,读取文件当前的实际内容,和快照做比对,如果内容不一样,拒绝这一次编辑,随后要求Agent重新读取文件(更新快照与实际内容一致)再进行编辑。这是一种主流的做法,不过Agent实现上的细节比较重
<!-- Assistant -->
edit(file, console.log...)

<!-- User -->
This edit is rejected, the file has been modified since your last read or edit, you should read this file again before executing any write or edit actions.

<!-- Assistant -->
read(file)

<!-- User -->
The file content: ...

<!-- Assistant -->
edit(file, console.trace...);
  • 推送法。监听所有模型读取、编辑过的文件的变更,当文件发生变更时,在下一次模型调用时,不断通过Reminder区域追加这些变更,让模型“实时”地知道文件有所变化,直到文件被下一次读取。这种方式能让模型更早地感知变化,但推送信息可能过多影响成本和推理速度。
<!-- Assistant -->
run_command(ls)

<!-- User -->
The command output: ...

<reminders>
- These files have been modified since your last read or edit, you should read before write or edit to them:
  - file
  - file
  - ...
</reminders>
  • 隔离法。使用Git Worktree方案,直接让不同的Agent任务在文件系统上隔离,在一个独立的Git分支上并行工作,相互不受干扰。在任务完成后,用户检查一个任务的全部变更,在采纳时再合并回实际的当前Git分支,有冲突的由用户解决冲突。这种方法让Agent根本不需要考虑冲突问题,但缺点是系统资源占用高,且有合并冲突风险

文件编辑冲突只是一个比较常见的现象,实际上用户和Agent、多个Agent并行工作,可能造成的冲突还有很多种,例如:

用户敲了半行命令 ls -,Agent直接在终端里敲新的命令 grep "print" -r src执行,导致最后的命令是 ls -grep "print" -r src ,是一个不合法的命令。

终端的抢占也是一种冲突,但相对更容易解决,只要让每一个Agent任务独占自己的终端,永远不与用户、其它Agent任务相交叉即可。

4.6 持久记忆

我们都知道,模型是没有状态的,所以每一次Agent执行任务,对整个项目、对用户的倾向,都是从零开始的过程。这相当于历史经验无法积累,很多曾经调整过的细节、优化过的方向都会被重置。虽然可以通过比如Rule这样的方式去持久化这些“经验”,但需要用户主动的介入,使用成本是相对比较高的。

因此当前很多Coding Agent产品都在探索“记忆”这一能力,争取让Agent变得用的越多越好用。记忆这个话题真正的难点在于:

  1. 如何触发记忆。
  2. 如何消费记忆。
  3. 什么东西算是记忆。

首先对于“如何触发”这一问题,常见于2种做法:

  1. 工具型。定义一个 update_memory 工具,将记忆作为一个字符串数组看待,工具能够对其进行增、删改,模型在任务过程中实时地决定调用。往往模型并不怎么喜欢使用这类工具,经常见于用户有强烈情感的描述时才出现,比如“记住这一点”、“不要再……”。
  2. 总结型。在每一次对话结束后,将对话全部内容发送给模型,并配上提示词进行记忆的提取,提取后的内容补充到原本记忆中。总结型的方案往往又会过度地提取记忆,将没必要的信息进行持久化,干扰未来的推理。
  3. 存储型。不进行任何的记忆整理和提取,而是将所有任务的原始过程当作记忆,只在后续“消费”的环节做精细的处理。

然后在“如何消费”的问题下,也常见有几种做法:

  1. 始终附带。记忆内容记录在文件中,Agent实现中将文件内容附带在每一次的模型请求中。即模型始终能看到所有的记忆,这无疑会加重模型的认知负担,也占用相当多的上下文空间,因为很多记忆可能是与当前任务无关的。
  2. 渐进检索。本身不带记忆内容到模型,但将记忆以文件系统的形式存放,Agent可以通过readlistgrep 等工具来检索记忆。配合“存储型”的触发方式,能让全量的历史任务都成为可被检索的记忆。但这种方式要求模型有比较强的对记忆的认知,在正确的时刻去找相关的记忆。但往往因为根本不知道记忆里有什么,进而无法知道什么时候应该检索,最终几乎不触发检索。

而最终的问题,“什么东西是记忆”,是当下Coding Agent最难以解决的问题之一。错误的、不必要的记忆甚至可能造成实际任务效果的下降,因此精确地定义记忆是Agent实现的首要任务。

通常来说,记忆会分为2种大的方向:

  1. 事实型。如“使用4个空格作为缩进”、“不要使用any 类型“,这些都是事实。事实是无关任何情感、不带主观情绪的。
  2. 画像型。如”用户更喜欢简短的任务总结“就是一种对用户的画像。画像是单个用户的特征,并不一定与项目、代码、架构相关。

在Coding Agent上,往往更倾向于对”事实型“的内容进行记忆,而不考虑用户画像型的记忆。

同时,从业界的发展,可以看到越来越多的模型厂商在从底层进行记忆能力的开发,如最近Google的Titan架构就是一种记忆相关的技术。可能未来某一天,Agent实现上已经不需要再关注记忆的逻辑与实现,模型自身将带有持久化的记忆能力。

05 能力扩展

在实际应用中,还需要一些机制来让Agent更好地适应特定的项目、团队和个人习惯。当前主流的Coding Agent产品都提供了Rule、MCP、Skill这三种扩展能力,它们各有侧重,共同构成了Agent的能力增强体系。

5.1 Rule

当面对业务的repo往往存在一些领域相关的知识而非模型的知识库中已有的内容,这些往往需要凭借老员工的经验或者读取大量代码库的信息进行总结后才能明白,这些内容便适合放到Rule中,作为静态的不会频繁改动的内容放入Environment Context中长期Cache。

好的Rule应当足够精简、可操作且范围明确,人看不懂的规则或者描述不清的规则模型是一定搞不定无法遵守的。

  • 将Rule控制在 500 行以内。
  • 将较大的规则拆分为多个可组合的规则,采取按需的方式,按照 文件路径/关键场景 激活Rule;对于特定场景激活的Rule,采取编写索引的方式创建Rule,让模型渐进式激活,比如项目针对网络请求和错误处理相关做了项目维度的封装处理,但这种情况并不是每个文件ts/tsx文件都会遇到的诉求,比如在项目的rules目录下创建index.mdr(curso是.mdc文件),编写下面的激活的条件:

    • 需要进行API调用获取数据
    • 处理异步操作的错误和加载状态

-   当编码涉及以下任一情况时,必须立刻阅读 \[08-api-error-handling.mdc\](mdr:.cursor/rules/08-api-error-handling.mdc)
    
  • 提供具体示例或参考文件,针对xx情况正确的方式是\`code\`。
  • 避免模糊的指导,比如交互式的东西模型交互不了,不需要写进去。
  • 为了模型能够积极验证每次改动是否符合预期,告知模型改动后可以执行的正确的构建命令,以及某些自定义命令(比如自动化测试)引导模型在后台启动命令,在xx秒后读取日志文件的内容进行结果的判断。

5.2 MCP

MCP(Model Context Protocol)是Anthropic提出的一种标准化的工具扩展协议,它允许开发者以统一的方式为Coding Agent添加新的能力。

与Rule的"声明式约束"不同,MCP是一种实时工具调用协议,即通过MCP server的方式进行连接,来扩展Agent可以做的事情。

一个典型的场景是集成外部服务。比如你的项目托管在GitHub上,可以让Agent直接访问GitHub实现创建Issue、查询PR状态、添加评论等功能:

{
    "mcpServers": {
        "github": {
            "command": "npx",
            "args": ["-y", "@modelcontextprotocol/server-github"],
            "env": {
                "GITHUB_PERSONAL_ACCESS_TOKEN": "<your-github-token>"
            }
        }
    }
}

配置好后,Agent就能在代码审查过程中自动创建Issue记录问题、查询相关PR的讨论、甚至根据代码变更自动生成commit message。

MCP的另一个优势是实现门槛低。一个MCP Server本质上就是一个标准输入输出的程序,它通过JSON-RPC协议与Agent通信,当模型需要外部能力的时候,调用MCP Server,而模型无需关心其内部代码实现,Agent只需要按照固定的协议去连接获取内容。

5.3 Skill

5.3.1 什么是Skill

随着模型能力的提升,使用Agent完成的任务复杂度逐渐增加,使用Coding Agent可以进行本地代码执行和文件系统完成跨领域的复杂任务。但随着这些Agent的功能越来越强大,我们需要更具可组合性、可扩展性和可移植性的方法,为它们配备特定领域的专业知识,因此Agent Skill作为一种为Agent扩展能力的标准诞生。Skill 将指令、脚本和资源的文件夹打包,形成专业领域的知识,Agent在初始化的时候会获取可用的Skills列表,并在需要的时候动态加载这些内容来执行特定任务。

随着 Skill 复杂性的增加,它们可能包含过多的上下文信息,无法放入单个配置文件中 SKILL.md,或者某些上下文信息仅在特定场景下才相关。在这种情况下,Skill可以在当前目录中bundle额外的文件,并通过文件名引用这些文件,这些额外的文件提供了更多详细信息,Coding Agent 可以根据需要选择浏览和查找这些信息。Skill 是渐进式触发的, 因此 SKILL.mdnamedescription很关键,这会始终存在于Agent的环境上下文中提供给模型,模型会根据这些描述信息来决定是否在当前任务中触发该Skill,当你明确希望使用某个Skill完成任务,可以在prompt中指定“使用xxxx Skill完成xx任务”。

5.3.2 Skill和代码执行

LLM在很多任务上表现出色,但许多操作需要使用编写代码 -> 代码执行的方式,带来更高效的操作、确定性的以及可靠性的结果。生成式的模型常常通过生成可执行代码的方式去验证/计算结果。

代码既可以作为可执行工具,也可以作为文档。Skill中应该明确让模型是应该直接运行脚本,还是应该将其作为参考信息读取到上下文中。

5.3.3 如何创建Skill

每个Skill由一个必需的 SKILL.md 文件和可选的bundle资源组成,Skill 应该只包含完成任务所需的信息。

skill-name/
├── SKILL.md (必需)
│   ├── YAML frontmatter 元数据 (必需)
│   │   ├── name: (必需)
│   │   ├── description: (必需,这是 skill 的主要触发机制,帮助模型理解何时使用该 skil)
│   │   └── compatibility: (可选)
│   └── Markdown 说明 (必需)
└── bundle的资源 (可选)
    ├── scripts/          - 可执行代码 (Python/Bash/等)
    ├── references/       - 需要时加载到上下文的文档
    └── assets/           - 用于输出的文件 (模板、图标、字体等)

举一个具体的例子,比如当我们需要进行批量项目的技术栈migrate,比如将less迁移postcss,中间涉及一系列的复杂步骤,比如:

  • 安装postcss以及postcss plugin的依赖
  • 配置postcss的config
  • 分析项目用到了哪些less varibale替换成css vars
  • 删除mixin并替换
  • 一系列的其他兼容less的语法转换...
  • 替换文件后缀

上面的工作可以通过清晰的流程描述,并配合脚本实现,因此可以作为一个Skill将经验变成可复制的,一个less-to-postcss的skill的结构:

5.3.4 Skill的使用

人人都可以创建Skill,也可以让Agent来编写Skill,这是Skill非常便捷的地方。Skill通过instructions和code赋予Coding Agent新的能力。虽然这使其功能强大并有很高的自由度,但也意味着恶意SKill可能会在其使用环境中引入漏洞,诱使模型窃取数据并执行非预期操作。仅从可信来源安装Skill,如果无法确信来源可信,在使用前请务必进行彻底审核。

Skill的出现并不是替代MCP的出现,而是相互配合,在合适的场景下选取Skill或是MCP。某些任务Skill和MCP Server均可完成,但Skill通过执行代码的方式可以一次性加载完整流程,但MCP Server要经历多次查询和多轮对话往返,这种情况下Skill更为合适,但这不意味着绝对的优势,比如标准化文档创建这个典型的场景,创建PPT/Word/Excel在本地使用Skill即可完成,但数据的提供则需要借助MCP Server进行查询。因此Skill擅长的是在本地通过执行 code的方式完成复杂任务,在用户私有数据、动态数据查询这些情况下Skill就无法搞定了,这和用户的数据库以及隐私强关联,需要让模型无法感知在执行过程中的隐私信息,Skill能够与MCP Server互补完成更为复杂的流程。

我的工作流是一个围绕 superpowers 插件Loop,superpowers 的理念是:先思考再动手。当你提出一个需求,不会急于写代码,而是先退一步问你"你真正想要实现什么",通过对话梳理出完整的设计方案,再分步执行。

核心设计是 masterworker 分离。

  • 脑暴会话 (master):专注于思考和设计,输出高质量的设计文档和执行计划
  • 执行会话 (worker):专注于代码实现,执行详细的计划

分享一下我的 ClaudeCode 工作流:Kitty + Zed + superpowers,可以减少和 AI 的反复拉扯,一次做对1

1、需求录入 - 首先我会在 Zed 上进行需求录入,采用 md 格式。这一步非常重要,我大概有 30% 的时间花在需求录入上,我会把能想到的关于此需求的背景、最终目标、可行的技术方案、风险点、外部 API 文档等等一切资源,都在需求文档中说明。对于需求文档,我不会太在意格式,会有比较多口语化的表达。

2、脑暴阶段 - 把需求 MD 喂给 Claude,调用 /superpowers:brainstorm 和 claude 进行思维碰撞。这个阶段不写任何代码,只讨论设计方案和实现细节,最终输出 design.mdimplement.md,保证最终的实现方案是完美符合我的预期的。

3、 执行阶段 - 这里我会选择新起一个 ClaudeCode 会话,而不是在脑暴会话中进行代码实现。新会话的好处:一、原先脑暴会话已经经过多轮对话了,一般情况下上下文会比较满,新会话响应更快,并且不会“犯傻”;二、implement.md 足够详细,无需额外上下文

4、 CodeReview - 在 Zed 中进行代码审查和功能验收。关于代码审查,对于一些代码细节和实现原理,这里我会使用 zed-agent 来辅助我进行代码 review,当然,你也可以在终端新建一个 ClaudeCode 会话或者使用 Zed 的 Claude Agent。原则是尽量不在脑暴和执行会话中引入太多不必要的问题,保持这两个会话的「干净」。发现问题后,将改进项写入新的需求 MD

5、 LOOP - 改进项 MD 喂回脑暴会话,开始下一轮脑暴迭代

非常简单,但是效果超群。充分的前期设计可以提升 AI 的效率和质量,避免多次的来回拉扯。

举个真实案例:我用这套工作流将个人博客从 Quarz 框架迁移到 Astro 框架。脑暴阶段确认好设计方案后,我让 Claude 执行计划,然后就去睡午觉了。醒来发现 Claude Code 已经完美完成任务——中间零中断,一次成功,共计 5000+ 行代码变更。

本文通过一个真实的商品分类审核场景,详细讲解 Claude Code Subagent 的创建、调用,以及大规模数据处理时的上下文管理技巧。

背景

我需要维护一份商品标准化分类体系表。随着业务发展,商品清单不断增加,我需要定期检查:哪些商品还没有被现有分类覆盖?

手上有两个文件:

  • goods.txt:商品清单,一行一个商品名,约 5000 个
  • 标准化分类体系表.md:现有的分类表,markdown 表格格式

目标很简单:找出所有未被覆盖的商品,并给出分类建议

解决方案对比

面对这个需求,我尝试了三种方案:

方案 1:直接 Prompt 让 AI 处理

最直接的想法:把 goods.txt标准化分类体系表.md 都丢给 Claude,让它一次性处理。

核心问题:

  • 上下文溢出:5000 个商品 + 分类表 + 审核结果,轻松超出上下文限制
  • 输出不稳定:AI 处理到第 3000 个商品时,可能 "忘记" 前面的判断标准
  • 无法并行:只能串行处理,5000 个商品要等很久
  • 结果难以解析:AI 输出格式不一致,后续脚本难以处理

方案 2:使用 Skills

把审核逻辑封装成一个 Skill,让主 Agent 调用。

改进点:

  • Skill 可以复用,审核逻辑标准化
  • 可以分批处理,降低单次上下文压力

依然存在的问题:

  • 上下文累积:每批的输入输出仍然留在主对话中
  • 无法真正隔离:所有 Skill 调用共享主 Agent 的上下文
  • 并发受限:Skill 仍是串行执行,无法并行加速

方案 3:使用 Subagent(本文方案)

为审核任务创建独立的 Subagent,每个 Subagent 处理一批商品。

核心优势:

  • 上下文隔离:每个 Subagent 拥有独立的上下文,互不干扰
  • 可并行处理:多批审核可以同时进行(理论上)
  • 输出标准化:Subagent 严格按照定义的 JSON 格式输出
  • 主 Agent 轻量:主 Agent 只做调度,不接触数据内容

Subagent 方案

传统方案

📦 5000 商品挤在一个上下文

❌ AI 输出格式不稳定

⏳ 串行处理,效率低

🤖 主 Agent 直接处理数据

📦 每批 30 个,独立上下文

✅ 严格 JSON 格式输出

🚀 可批次并行处理

🎯 主 Agent 只做调度

什么是 Subagent?

简单来说,Subagent 就是一个可以被主 Agent 调用的 "专家助手"

你可以把它理解为:

  • 函数:定义好输入输出,主 Agent 调用时传参
  • 独立进程:拥有自己的上下文,不污染主 Agent
  • 专业顾问:每个 Subagent 可以有自己的专业领域和工具集

不过,不要太纠结概念。接下来通过实战,你会直观理解 Subagent 是怎么工作的。

为什么选择 Subagent?

最初我想直接让 Claude Code 处理,但很快发现问题:

  1. 上下文爆炸:5000 个商品 + 分类表 + 审核结果,轻松超出上下文限制
  2. 串行处理慢:逐个商品判断,效率太低
  3. 结果混乱:长对话中,Claude 容易 "忘记" 之前的判断标准

Subagent 天然解决这些问题:

痛点Subagent 方案
上下文爆炸每个 Subagent 独立上下文,互不干扰
处理效率批次并行处理
结果一致性每批独立审核,标准统一

实战开始

第一步:设计 Subagent

.claude/agents/ 目录下创建 category-reviewer.md

---
name: category-reviewer
description: 商品分类覆盖审核专家。当需要批量检查商品是否被现有分类表覆盖时使用。
tools: Read, Write
model: sonnet
---
你是商品分类覆盖审核专家。 ## 输入说明 调用时会提供: - batch_id: 批次编号
- input_
file: 商品列表文件路径 - output_file: 结果输出文件路径

## 执行步骤
1. 使用 Read 读取 input_
file,获取商品列表 2. 使用 Read 读取 `doc/goodscategoryreview/标准化分类体系表.md` 3. 逐一判断每个商品的分类覆盖情况 4. 将结果 JSON 写入 output_file
5. 回复确认信息

## 判断标准
- **covered**:商品名称能够明确归入现有某个分类
- **uncovered**:商品无法归入任何现有分类

## 输出格式
写入 output_
file 的 JSON: { "batch_id": "xxx",
"results": [
{"name": "商品A", "status": "covered", "matched_
category": "匹配的分类"}, {"name": "商品B", "status": "uncovered", "reason": "原因", "suggested_category": "建议分类"}
]
}
## 注意事项 - 每个商品都必须有一条记录,不要遗漏 - status 只能是 "covered" 或 "uncovered" - 相似商品的 suggested_category 保持一致

## 完成回复
只需回复:`✓ {batch_
id} 完成,已写入 {output_file}`

几个关键设计点:

  1. tools 精简:只给 ReadWrite,不需要 Bash 等其他工具
  2. 输入输出分离:通过文件路径传递数据,而不是直接传内容
  3. 明确输出格式:JSON 结构固定,方便后续脚本处理
  4. 简洁回复:只回复确认信息,不要长篇大论

第二步:上下文管理 —— 这是关键!

5000 个商品,如果直接让主 Agent 读取并分组,商品列表就会进入上下文。167 个批次的调用记录,又会持续膨胀上下文。

错误做法

1. 读取 goods.txt(5000 商品进入上下文)
2. 分组并逐批调用 subagent(调用记录堆积)

预估上下文:~150,000 字符,非常臃肿。

正确做法

核心原则:主 Agent 只做调度,不接触数据内容

1. 用脚本预处理数据(主 Agent 不读商品内容)
2. 主 Agent 只传递文件路径给 Subagent
3. 用脚本汇总结果(主 Agent 不读 batch 文件)

预估上下文:~20,000 字符,精简高效。

第三步:完整的主流程 Prompt

这是我最终优化后的 Prompt:

我需要找出商品清单中所有未被现有分类覆盖的商品。

## 文件路径
- 商品清单:doc/goodscategoryreview/goods.txt
- 分类表:doc/goodscategoryreview/标准化分类体系表.md
- 审核结果目录:doc/goodscategoryreview/review_output/

## 任务流程

### 第一步:准备工作
编写并执行 Python 脚本 `prepare.py`:
1. 创建 doc/goodscategoryreview/review_output/input/ 目录
2. 读取 goods.txt,按 30 个一批分组
3. 将每批商品写入单独文件:
   - doc/goodscategoryreview/review_output/input/batch_001.txt
   - doc/goodscategoryreview/review_output/input/batch_002.txt
   - ...
4. 输出批次总数到 doc/goodscategoryreview/review_output/batch_count.txt

### 第二步:批量审核
1. 读取 batch_count.txt 获取批次总数 N
2. 循环 i 从 1 到 N,对每个批次调用 category-reviewer subagent:

使用 category-reviewer subagent 审核:

batch_id: batch_{i:03d}
input_file: doc/goodscategoryreview/review_output/input/batch_{i:03d}.txt
output_file: doc/goodscategoryreview/review_output/batch_{i:03d}.json

注意:
- 不要读取 input 文件内容,只传递文件路径
- 不要读取 output 文件内容,只确认完成

### 第三步:汇总统计
编写并执行 Python 脚本 `summarize.py`:
1. 读取所有 batch_*.json
2. 只提取 status = "uncovered" 的记录
3. 按 suggested_category 分组
4. 统计总数、未覆盖数、覆盖率
5. 保存到 summary.json

### 第四步:生成报告
读取 summary.json(精简数据),生成 report.md:
- 审核概览
- 未覆盖商品清单(按建议分类分组)
- 分类调整建议

### 第五步:等待确认
展示对 标准化分类体系表.md 的修改建议,等待我确认后再执行修改。

## 关键约束
- 主对话中绝不读取商品列表原文
- 主对话中绝不读取 batch 输入/输出文件内容
- 所有数据处理通过 Python 脚本完成
- 只关注未覆盖的商品

请开始执行。

第四步:执行过程

执行后,Claude Code 会:

1. 生成预处理脚本 prepare.py

2. 逐批调用 Subagent

每个 Subagent 独立执行,主 Agent 只收到简短确认:

✓ batch_001 完成,已写入 doc/goodscategoryreview/review_output/batch_001.json
✓ batch_002 完成,已写入 doc/goodscategoryreview/review_output/batch_002.json
...

3. 生成汇总脚本 summarize.py

4. 输出最终报告

最终文件结构

doc/goodscategoryreview/
├── goods.txt                      # 原始商品清单
├── 标准化分类体系表.md              # 分类表
└── review_output/
    ├── input/
    │   ├── batch_001.txt          # 第1批输入
    │   ├── batch_002.txt          # 第2批输入
    │   └── ...
    ├── batch_001.json             # 第1批结果
    ├── batch_002.json             # 第2批结果
    ├── ...
    ├── batch_count.txt            # 批次总数
    ├── summary.json               # 汇总统计
    └── report.md                  # 最终报告 

进阶拓展:从 "主副二人组" 到 "多人团队"

本文的实战方案中,只有审核环节用了 Subagent,其他步骤(数据准备、汇总统计、报告生成)还是通过 Python 脚本完成的。

其实,我们可以把整个流程的每个环节都交给专门的 Subagent 处理,这样就形成了一个多人协作的 AI 团队:

AI 团队协作架构项目经理负责任务调度和协调 data-preparercategory-reviewerresult-mergerreport-generator 读取商品清单并分组批量审核商品分类合并审核结果并统计生成审核报告

踩坑与经验总结

踩坑 1:让 AI 直接计数

错误:让 Subagent 输出 “已覆盖数量: 25, 未覆盖数量: 5”

问题:AI 不擅长精确计数,容易出错

正确:让 Subagent 逐条输出每个商品的判断结果,用脚本统计数量

踩坑 2:主 Agent 读取 Subagent 输出

错误:每批完成后,主 Agent 读取 JSON 文件检查结果

问题:167 个批次的 JSON 内容全部进入上下文,直接爆炸

正确:主 Agent 只确认完成,最后用脚本统一汇总

踩坑 3:在调用时传递商品列表内容

错误

使用 category-reviewer subagent 审核:
products:
商品1
商品2
...(30个商品名)

问题:167 次调用 × 30 个商品名 = 大量内容进入主 Agent 上下文

正确:只传文件路径

使用 category-reviewer subagent 审核:
input_file: xxx/batch_001.txt

经验总结

原则说明
数据不入上下文大量数据通过文件传递,主 Agent 只传路径
脚本做重活分组、统计、汇总等操作交给 Python 脚本
Subagent 输出到文件不要让 Subagent 返回大量内容
只关注异常只汇总 "未覆盖" 的商品,忽略正常的
确认再执行修改重要文件前,先展示建议等待确认

Subagent 适用场景

通过这次实战,我总结了 Subagent 的最佳适用场景:

适合用 Subagent

  • 大量同质化任务(如本文的批量审核)
  • 需要隔离上下文的独立子任务
  • 可并行处理的工作
  • 需要专业化 prompt 的特定领域任务

不适合用 Subagent

  • 简单的一次性任务
  • 需要持续对话、多轮迭代的任务
  • 强依赖上下文连贯性的任务

结语

Subagent 是 Claude Code 中非常强大的功能,但用好它需要注意上下文管理。核心思路就是:

主 Agent 是调度员,不是搬运工。数据流转靠文件,不靠对话。

希望这篇实战分享对你有帮助。如果你也有类似的大规模数据处理场景,不妨试试 Subagent + 脚本的组合方案。


本文基于 Claude Code 实际使用经验撰写,欢迎交流讨论。


📌 转载信息
转载时间:
2026/1/10 18:59:58