❝该系列文章基于
github.com/shareAI-lab/learn-claude-code写就,该仓库以大道至简的风格剖析了Claude Code的核心原理,值得大家学习。由于该仓库是基于Python语言,为方便.NET开发者学习,我已经将代码基于.NET 10的dotnet file重写,源码已上传至github,源码地址见文末。
v3: 子代理机制 - 上下文隔离的艺术
❝本文是 Learn Claude Code (C# 版) 系列的第四篇,对应代码文件
v3_subagent.cs。
问题:上下文污染
v2 添加了规划能力。但对于大型任务:
You: 探索代码库的架构,然后重构 auth 模块单 Agent 的执行过程:
[探索中...] cat src/Services/AuthService.cs -> 500 行 [探索中...] cat src/Models/User.cs -> 300 行 [探索中...] cat src/Controllers/AuthController.cs -> 400 行 ... 15 个文件后 ... [现在重构...] "等等,AuthService 里那个方法签名是什么来着?"模型的上下文被探索细节填满了。当真正开始重构时:
关键代码片段已经"滚出"上下文窗口
模型需要重新读取文件
效率低下,成本增加
这就是上下文污染——前置任务的输出污染了后续任务的工作空间。
解决方案:子代理
主 Agent 历史: [Task: 探索代码库] -> 子代理探索 20 个文件(在自己的上下文中) -> 只返回: "Auth 在 src/Services/AuthService.cs,依赖 User, Token 模型..." [现在用干净上下文重构]进程隔离 = 上下文隔离
每个子代理有:
自己的干净消息历史
过滤后的工具集
专门的系统提示
只向父代理返回最终摘要
Agent 类型注册表
record AgentConfig(string Description, string[] Tools, string Prompt); var agentTypes = new Dictionary<string, AgentConfig> { ["explore"] = new( "只读代理,用于探索代码、查找文件、搜索", ["bash", "read_file"], // 没有写权限! "你是一个探索代理。搜索和分析,但不要修改文件。返回简洁的摘要。" ), ["code"] = new( "完整代理,用于实现功能和修复 bug", ["*"], // 所有工具 "你是一个编程代理。高效地实现请求的更改。" ), ["plan"] = new( "规划代理,用于设计实现策略", ["bash", "read_file"], // 只读 "你是一个规划代理。分析代码库并输出编号的实现计划。不要做更改。" ) };类型 | 工具 | 用途 |
|---|---|---|
explore | bash, read_file | 安全探索,不会意外修改 |
code | 全部 | 实际实现 |
plan | bash, read_file | 设计方案,不执行 |
Task 工具定义
var taskTool = new Tool { Name = "Task", Description = $""" 为专注的子任务生成子代理。 子代理在隔离上下文中运行 - 它们看不到父代理的历史。 用这个来保持主对话干净。 Agent 类型: - explore: 只读探索 - code: 完整实现 - plan: 设计策略 示例: - Task(explore): "查找所有使用 auth 模块的文件" - Task(plan): "设计数据库迁移策略" - Task(code): "实现用户注册表单" """, InputSchema = new InputSchema { Type = "object", Properties = new Dictionary<string, JsonElement> { ["description"] = ..., // 简短描述,3-5 词 ["prompt"] = ..., // 详细指令 ["agent_type"] = ... // explore | code | plan }, Required = ["description", "prompt", "agent_type"] } };子代理执行核心
async Task<string> RunTaskAsync(string description, string prompt, string agentType) { var config = agentTypes[agentType]; // 1. 专门的系统提示 var subSystem = $""" 你是一个位于 {workDir} 的 {agentType} 子代理。 {config.Prompt} 完成任务并返回清晰简洁的摘要。 """; // 2. 过滤后的工具集 var subTools = GetToolsForAgent(agentType); // 3. 关键:隔离的消息历史! var subMessages = new List<Message> { prompt.AsUserMessage() }; // 4. 运行相同的 Agent 循环 while (true) { var response = await client.CreateMessageAsync( modelId, subMessages, new MessageParameters { System = subSystem, Tools = subTools }); if (response.StopReason != StopReason.ToolUse) { // 5. 只返回最终文本 return ExtractText(response); } // 执行工具,添加结果... } }关键点:
隔离的
subMessages- 不包含父代理的历史过滤的工具- explore 不能写文件
专门的系统提示- 引导子代理行为
只返回摘要- 父代理不看中间细节
进度显示
Console.WriteLine($" [{agentType}] {description}"); var start = DateTime.Now; var toolCount = 0; // 执行中... Console.Write($"\r [{agentType}] {description} ... {toolCount} tools, {elapsed:F1}s"); // 完成 Console.WriteLine($"\r [{agentType}] {description} - done ({toolCount} tools, {elapsed:F1}s)");输出效果:
> Task: 探索 auth 架构 [explore] 探索 auth 架构 ... 5 tools, 2.3s [explore] 探索 auth 架构 - done (8 tools, 4.1s)用户看到子代理在工作,但不会被中间输出淹没。
典型工作流程
You: 重构 auth 模块使用 JWT 让我先了解现有架构: > Task: 探索 auth 架构 [explore] 探索 auth 架构 - done (12 tools, 5.2s) 摘要:Auth 模块位于 src/Services/AuthService.cs,使用 Session 认证。 依赖:User 模型、TokenRepository。主要方法:Login(), Logout(), ValidateToken()。 接下来设计迁移方案: > Task: 设计 JWT 迁移 [plan] 设计 JWT 迁移 - done (5 tools, 3.1s) 迁移计划: 1. 添加 JWT 依赖包 2. 创建 JwtService 类 3. 修改 AuthService 使用 JwtService 4. 更新 TokenRepository 5. 添加配置项 现在执行实现: > Task: 实现 JWT 认证 [code] 实现 JWT 认证 - done (15 tools, 12.3s) 完成:创建了 JwtService.cs,修改了 AuthService.cs, 更新了 appsettings.json,添加了 3 个单元测试。 任务完成!主要改动: - 新增 JwtService 处理 token 生成和验证 - AuthService 现在使用 JwtService 而非 Session - 配置文件新增 Jwt 配置节深层洞察
分治策略
子代理本质上是分治法的应用:
分:把大任务分解为独立子任务
治:每个子代理独立解决其任务
合:父代理整合子代理的摘要
上下文是稀缺资源
Claude 的上下文窗口有限(128K-200K tokens)。如何使用这个有限资源?
策略 | 效果 |
|---|---|
全部放主 Agent | 快速溢出,早期信息丢失 |
子代理隔离 | 主 Agent 保持干净,按需获取摘要 |
子代理是上下文管理的工具。
工具权限控制
为什么 explore 不能写文件?
安全- 探索时意外修改很危险
专注- 强制子代理只做一件事
可预测- 你知道 explore 任务不会产生副作用
这是最小权限原则的应用。
v0 的递归 vs v3 的子代理
v0 用 bash 调用自身实现子代理:
dotnet run v0_bash_agent.cs "探索 src/"v3 用显式的 Task 工具:
Task(explore, "探索 src/")区别:
v0 更"纯粹"——一切通过 bash
v3 更"可控"——明确的类型和权限
防止无限递归
注意 v3 的设计:
List<Tool> GetToolsForAgent(string agentType) { var allowed = agentTypes[agentType].Tools; if (allowed.Contains("*")) return baseTools; // 不包括 Task 工具! return baseTools.Where(t => allowed.Contains(t.Name)).ToList(); }子代理没有Task工具——不能再生成子代理。这防止了无限递归。
(生产系统可能允许有限深度的嵌套,但需要仔细控制。)
运行示例
Mini Claude Code v3 (with Subagents) - /path/to/project Agent 类型: explore, code, plan 输入 'exit' 退出。 You: 分析这个项目的架构并给出改进建议 让我先探索项目结构: > Task: 分析项目结构 [explore] 分析项目结构 - done (8 tools, 3.5s) 项目是一个 ASP.NET Core Web API: - Controllers/: 3 个控制器 - Services/: 5 个服务类 - Models/: 8 个模型 - Data/: EF Core DbContext 潜在问题:Services 直接使用 DbContext,没有 Repository 层。 接下来让我设计改进方案: > Task: 设计 Repository 模式 [plan] 设计 Repository 模式 - done (4 tools, 2.1s) 改进计划: 1. 创建 IRepository<T> 接口 2. 实现 GenericRepository<T> 3. 为每个实体创建具体 Repository 4. 修改 Services 使用 Repository 而非 DbContext 5. 更新 DI 配置 基于分析,我的改进建议是: 1. 引入 Repository 层分离数据访问 2. Services 应该依赖接口而非实现 3. 考虑添加 Unit of Work 模式管理事务 需要我实现这些改进吗?从 v3 到 v4
v3 解决了上下文隔离问题。但还有一个挑战:
You: 处理这个 PDF 文件 You: 构建一个 MCP 服务器模型怎么知道:
处理 PDF 应该用
pdftotext还是PyMuPDF?MCP 服务器需要什么协议规范?
这些是领域知识,不是工具能力。v4 将引入Skills 机制——按需加载专业知识。
总结
v3 的哲学:
❝分而治之,上下文隔离。干净上下文 = 更好结果。
子代理不是新概念,但用得好能显著提升 Agent 的能力边界。关键是理解:上下文是稀缺资源,需要精心管理。
点击阅读原文,获取仓库地址:👇👇👇