一个 Agent 看完 30 个文件后,为什么该让它先退出?

发布时间:2026/7/29 3:12:12
一个 Agent 看完 30 个文件后,为什么该让它先退出? 本文是「从零理解 Claude Code20 个 Agent Harness 机制」系列的第 6 篇。源码仓库shareAI-lab/learn-claude-code本文基于开源仓库学习整理具体实现以仓库代码为准。上一章的 TodoWrite 解决了一个问题复杂任务不能只靠模型临场判断需要把待办事项和进度保留下来。不过待办列表只能告诉 Agent 还剩什么工作并不能减少某一项工作本身的复杂度。例如计划里有一项确认项目使用的测试框架并找出相关配置。这句话看起来只是一个待办项执行起来却可能意味着搜索项目结构、读取依赖声明、查看测试目录、检查配置文件最后还要判断哪些内容才是主 Agent 真正需要的结论。如果这些搜索过程、文件内容和中间判断全都留在主对话里主 Agent 的上下文会越来越长。前面读过哪些文件尝试过哪些搜索词哪些路径最终被排除这些过程对完成子任务很有价值但对子任务结束后的主流程往往只剩下一两句结论有用。这就是子 Agent 要处理的场景。一、待办可以拆任务但不能隔离上下文假设主 Agent 正在修一个认证模块的问题。它先列出计划1. 定位认证调用链 2. 找到失败原因 3. 修改代码 4. 运行相关测试 5. 检查回归影响第一项定位认证调用链这个逻辑本身可能就很重。为了查清楚调用关系Agent 也许需要读取几十个文件搜索多个关键字反复调整判断。主对话里会持续累积文件内容、命令输出和工具结果。这时主 Agent 后面还要做修改、测试和收尾但上下文里已经塞进了大量定位过程的细节。问题不在于这些细节没有价值。它们对定位调用链的那个过程很重要任务完成后主 Agent 通常只需要知道调用链经过哪些模块、问题大概在哪个位置以及有没有需要注意的约束。因此这里需要的不是再写一张更长的待办列表而是把一段独立的探索工作交出去并在结束后只收回结论。二、子 Agent 的基本做法这一章新增了一个task工具。主 Agent 调用它时程序会创建一个新的子 Agent。子 Agent 有自己的消息列表从一条明确的任务描述开始运行它可以继续读文件、搜索代码、执行命令并在完成后返回一段总结。父 Agent 不会拿到子 Agent 全部的工具调用历史那父 Agent 会拿到什么呢其实只是拿到子 Agent 的返回结果而已。用一个简单的例子表示主 Agent 请确认项目使用什么测试框架并找出相关配置。 子 Agent 读取 pyproject.toml → 搜索 tests 目录 → 查看配置文件 → 得出结论 主 Agent 收到总结继续执行后续计划。这里最容易被忽略的一点是子 Agent 的价值并不只是多了一个执行者它还建立了一条上下文边界。子 Agent 可以在自己的对话中搜索、试错和排除路径主 Agent 只保留对后续决策有价值的信息。这样主对话不会因为某个局部调查任务而持续膨胀。下面这张图展示了父 Agent、子 Agent 和总结结果之间的关系。三、子 Agent 从一份新的 messages 开始这章的实现没有让子 Agent 复用父 Agent 的历史消息。创建子 Agent 时程序只给它一条任务描述defspawn_subagent(description:str)-str:messages[{role:user,content:description,}]for_inrange(30):responseclient.messages.create(modelMODEL,systemSUB_SYSTEM,messagesmessages,toolsSUB_TOOLS,max_tokens8000,)messages.append({role:assistant,content:response.content,})# 工具调用和结果回填逻辑# 与主 Agent 的循环类似这里的messages是一份新的列表。子 Agent 看不到父 Agent 前面读取过哪些文件、写过哪些待办也不会受到父对话中大量中间结果的影响。它只知道当前被交付的子任务是什么以及可以使用哪些工具。例如父 Agent 可以交给它阅读 agents/ 目录下所有 Python 文件说明每个文件的职责并指出主要入口文件。子 Agent 会围绕这一件事展开搜索和阅读。任务结束后程序会提取最后一条文本结论returnextract_text(messages[-1][content])主 Agent 看到的可能是agents/ 目录包含 4 个模块。 其中 runner.py 是主入口tools.py 负责工具注册 memory.py 管理会话状态config.py 读取运行配置。至于子 Agent 具体读了哪些文件、尝试过哪些搜索词、哪次判断被推翻这些过程不会回填到父 Agent 的消息历史中。四、只返回总结实际上是一种信息筛选把完整过程全部返回给主 Agent看似信息更全实际会削弱拆分任务的意义。子 Agent 读取了十个文件主 Agent 未必需要再读一遍十个文件的全部内容。它需要的是能够支持下一步决策的信息。例如子任务是确认测试框架。父 Agent 最终需要的可能只是项目使用 pytest 测试位于 tests/ 配置写在 pyproject.toml 运行命令为 pytest。这些信息足以让主 Agent 后续运行测试、定位测试文件或修改配置。当然只返回总结也有代价。如果子 Agent 的总结遗漏了关键证据父 Agent 无法直接查看完整的推理过程。这意味着父 Agent 对重要结论不能完全照单全收尤其是涉及代码修改、权限操作或架构判断时仍然应该进行必要验证。子 Agent 的总结更适合被当成调查结果而不是不可质疑的事实。五、上下文隔离不等于执行环境隔离新的 messages 列表只隔离了对话上下文并没有创建一个完全独立的工作空间。子 Agent 和父 Agent 仍然在同一个项目目录下运行。如果子 Agent 调用了write_file或edit_file文件修改会真实保留在工作区中如果它执行了命令命令也会作用于同一个环境。因此父 Agent 与子 Agent 之间的关系可以这样理解内容是否隔离消息历史隔离模型的中间推理过程不返回父 Agent文件系统修改共享Shell 命令影响共享权限检查继续生效仓库里的子 Agent 在调用工具前仍然会经过PreToolUseHook。这意味着子 Agent 即使拥有独立上下文也不能绕过前面建立的权限边界。它请求执行高风险命令时权限 Hook 仍然会阻止或要求确认。如果上下文隔离被误解成单独开了一个安全环境后续很容易出现错误设计。举个例子父 Agent 让子 Agent 排查某个接口为什么返回 500。子 Agent 为了验证猜测可能会修改一份本地配置文件或者执行一条启动测试服务的命令。虽然这些操作发生在子 Agent 自己的消息历史里但文件修改仍然会写入当前项目目录启动的进程也仍然会占用同一个运行环境。子 Agent 完成调查后父 Agent 只会收到一段总结例如“问题出在数据库连接配置”。但父 Agent 随后读取项目文件时看到的已经是子 Agent 修改后的版本。因此新的messages只意味着父 Agent 看不到子 Agent 的完整调查过程。为了避免这两层概念混在一起可以看下面这张图。六、为什么教学代码不允许子 Agent 继续创建子 Agent主 Agent 的工具列表里有task。子 Agent 的工具列表里没有task只保留了bash、read_file、write_file、edit_file和glob。这样设计的原因很直接如果子 Agent 也可以继续创建新的子 Agent任务树可能在缺少约束的情况下持续向下展开。父任务没有完成子任务又派生出更多子任务最后很难判断谁负责收尾也很难控制总调用次数和成本。这章的实现还给每个子 Agent 设置了最多 30 轮的限制。限制并不能保证任务一定完成但可以防止一个局部任务因为反复调用工具而无限运行。达到上限后程序会返回已有结果或者说明子 Agent 没有在限制内产出最终答案。生产系统通常还会增加超时、取消信号、预算限制和更细的任务状态管理。这一章先保留最基础的两层控制子 Agent 不能递归创建新的子 Agent单个子 Agent 的工具循环有最大轮数。七、子任务描述决定了子 Agent 的上限子 Agent 拿到的是一条独立指令。这条指令如果范围太大上下文隔离也救不了它。例如分析整个项目找出所有问题并修复。这不是一个适合直接委派的子任务。它没有明确边界也没有可验证的完成条件。子 Agent 可能花大量时间阅读文件最后给出一份泛泛的总结。更合适的描述应该包含范围、目标和交付形式阅读 agents/ 目录下的 Python 文件 说明每个模块的职责 找出程序入口文件 只返回不超过 200 字的总结不修改任何文件。这个描述明确了项目内容范围agents/目录动作阅读和分析目标模块职责与入口文件输出简短总结副作用不修改文件子 Agent 并不会自动把模糊任务变清楚。父 Agent 负责拆分任务子 Agent 负责在给定边界内完成调查、修改或验证。任务描述越具体父 Agent 收到的结果越稳定。八、什么时候值得使用子 Agent子 Agent 有启动成本也会增加一次结果汇总过程因此不适合所有操作。读取一个配置文件、改一个变量名、运行一次测试像这些简单任务直接由主 Agent 完成通常更简单。下面几类任务更适合拆出去场景适合委派的原因阅读一组文件并总结结构中间文件内容多最终结论相对短调查某个调用链或依赖关系搜索和排除过程容易占满主上下文独立实现一个边界明确的小模块可以限定输入、输出和验收方式运行一组专项检查主 Agent 只需要检查结果和异常项判断标准不在于任务听上去是否复杂而在于它是否能形成一个相对独立的工作单元。如果子任务结束后父 Agent 只需要一段总结或一个明确的产物就值得考虑交给子 Agent。九、跑一下这章代码进入仓库目录后执行python s06_subagent/code.py可以先试一个只读任务Use a subtask to find what testing framework this project uses.终端中会看到子 Agent 被创建、调用工具并结束。主 Agent 最终只拿到总结文本。再试一个范围更明确的任务Delegate: read all .py files in agents/ and summarize what each one does.如果希望观察共享工作区的效果可以尝试Use a task to create s06_subagent/example/string_tools.py with a slugify(text: str) function, then verify it from the parent agent.子 Agent 创建文件后父 Agent 仍然可以读取和验证这个文件因为两者操作的是同一个工作区。小结TodoWrite 把复杂任务拆成了可跟踪的步骤。子 Agent 进一步把其中一部分工作放到独立消息历史中完成并将结果压缩为一段可供父 Agent 使用的结论。这套设计包含三个需要同时成立的条件子任务的边界足够清楚子 Agent 的中间过程不会持续占用主对话文件修改和权限控制仍然处于统一的工作环境中。下一篇将讨论 Skill Loading。子 Agent 解决了任务拆分和上下文隔离的问题。不同类型的任务仍然需要不同知识例如前端组件规范、数据库表结构或项目约定。把所有知识都塞进系统提示词会让上下文越来越重后续会通过按需加载技能的方式处理这个问题。