资讯动态

learn-claude-code 中的 Subagent 机制:用独立 messages[] 实现子代理上下文隔离

发布时间:2026/9/7 2:16:16 来源:尧图企业网站定制
learn-claude-code 中的 Subagent 机制用独立 messages[] 实现子代理上下文隔离【免费下载链接】learn-claude-codeBash is all you need - A nano claude code–like 「agent harness」, built from 0 to 1项目地址: https://gitcode.com/GitHub_Trending/an/learn-claude-code本文以 learn-claude-code 课程中的 Subagent子代理章节为核心讲解“父代理 独立上下文子代理”这一经典 Agent harness 设计为什么要把大任务拆给子代理、task工具如何注册、run_subagent()如何用全新的messages[]跑完一个嵌套 agent loop 并只把最终文本作为tool_result返回给父会话。读完并对照仓库源码后你可以理解上下文隔离的边界消息隔离而非进程隔离、工具过滤策略子代理无task、禁止递归委派并能在本仓库的遗留版agents/s04_subagent.py与新版s06_subagent/code.py两条代码线上实际运行和验证该机制。1. 问题父会话的 messages[] 会无限膨胀在 learn-claude-code 的设定中一个 agent 的全部状态就是发给模型的messages数组。随着 agent 持续工作这个数组不断增长每一次read_file的完整文件内容、每一条bash命令的输出都会永久留在上下文里。文档用一个典型场景说明问题父代理被问“这个项目用的什么测试框架”为了回答它可能连续读取 5 个文件但父代理真正需要的答案只有一个词——“pytest”。那 5 个文件的全文却永远占据了后续所有轮次的上下文预算挤压模型对当前任务的注意力。这就是子代理要解决的核心矛盾中间过程信息是完成任务所必需的但对父会话却是纯噪声。2. 方案父子双上下文隔离的是消息而不是进程文档给出的解决思路是一个父子双上下文的同步委派结构引自 docs/ja/s04-subagent.mdParent agent Subagent ------------------ ------------------ | messages[...] | | messages[] | -- fresh | | dispatch | | | tool: task | ---------- | while tool_use: | | prompt... | | call tools | | | summary | append results | | result ... | ---------- | return last text | ------------------ ------------------ Parent context stays clean. Subagent context is discarded.流程分三步父代理调用task工具把子任务的prompt派发给子代理子代理从messages[]起步在自己的上下文里独立执行“模型响应 → 执行工具 → 追加结果 → 再调用模型”的标准 agent loop子代理结束后只有最后一段文本摘要作为普通tool_result回到父会话子代理的整段消息历史可能包含 30 次以上工具调用被直接丢弃。这里有一个必须澄清的边界子代理隔离的是会话上下文不是进程或文件系统。父代理与子代理运行在同一个 Python 进程里共享同一个WORKDIR工作目录所以子代理写的文件、执行的命令对父代理立即可见。源码注释把它概括为“Process isolation gives context isolation for free”——反过来讲本机制恰好相反不依赖进程隔离仅用“新的消息列表”就拿到了上下文隔离。这一设计取舍在 agents/s04_subagent.py 的模块 docstring 中有明确说明。3. 工具层设计task 工具与父子工具集3.1 父代理比子代理多一个 task 工具核心规则只有一条父代理的工具集 全部基础工具 task子代理拿到全部基础工具但没有task因此子代理无法再次委派递归派生被从工具层面直接封死。文档给出的工具定义对应 agents/s04_subagent.py 中的真实实现PARENT_TOOLS CHILD_TOOLS [ {name: task, description: Spawn a subagent with fresh context., input_schema: { type: object, properties: {prompt: {type: string}}, required: [prompt], }}, ]在 agents/s04_subagent.py 中CHILD_TOOLS包含 4 个基础工具bash、read_file、write_file、edit_file每个工具都带完整的 JSON Schema 描述TOOL_HANDLERS字典负责把工具名映射到执行函数。父代理循环在遇到block.name task时走特殊分支——取出prompt参数、打印一行派发消息然后同步调用run_subagent(prompt)见 agents/s04_subagent.py其余工具则统一查表执行。也就是说task在父代理眼中只是“返回值比较特殊的普通工具”父循环代码不需要为委派做任何结构性改造。3.2 新版代码线基础工具扩充为 5 个并接入 Hooks当前 17 课主线中的同一机制位于 s06_subagent/code.py它的工具集在遗留版基础上增加了glob基于 s06_subagent/code.py 的BASE_TOOLS共 5 个bash、read_file、write_file、edit_file、glob委派结构则完全一致SUB_TOOLS list(BASE_TOOLS) # 子代理工具集不含 task SUB_HANDLERS dict(BASE_HANDLERS) TASK_TOOL { name: task, description: Run a subagent with fresh conversation context and return its final text., input_schema: { type: object, properties: {prompt: {type: string, minLength: 1}}, required: [prompt], }, } TOOLS [*BASE_TOOLS, TASK_TOOL] TOOL_HANDLERS {**BASE_HANDLERS, task: run_subagent}见 s06_subagent/code.py值得注意的是新版把task直接注册进了TOOL_HANDLERS分发映射task: run_subagent父循环不再需要if block.name task的特判分支——这与课程 s02 确立的“加一个工具 加一个 handler”的分发模式一脉相承。同时prompt参数额外加了minLength: 1约束避免空提示词启动无意义的子代理。4. 核心实现run_subagent() 的嵌套 agent loop4.1 遗留版实现与文档一一对应agents/s04_subagent.py 中的run_subagent()是文档代码的完整落地def run_subagent(prompt: str) - str: sub_messages [{role: user, content: prompt}] # fresh context for _ in range(30): # safety limit response client.messages.create( modelMODEL, systemSUBAGENT_SYSTEM, messagessub_messages, toolsCHILD_TOOLS, max_tokens8000, ) sub_messages.append({role: assistant, content: response.content}) if response.stop_reason ! tool_use: break results [] for block in response.content: if block.type tool_use: handler TOOL_HANDLERS.get(block.name) output handler(**block.input) if handler else fUnknown tool: {block.name} results.append({type: tool_result, tool_use_id: block.id, content: str(output)[:50000]}) sub_messages.append({role: user, content: results}) # Only the final text returns to the parent -- child context is discarded return .join(b.text for b in response.content if hasattr(b, text)) or (no summary)从源码结构看这个函数体现了四个关键设计全新上下文sub_messages从仅含一条 user 消息的列表开始父代理的历史完全不会被复制进来独立 system prompt子代理使用专门的SUBAGENT_SYSTEMYou are a coding subagent ... Complete the given task, then summarize your findings.明确指示它“完成任务后总结发现”而父代理的SYSTEM则指示它“用 task 工具委派探索类子任务”见 agents/s04_subagent.py30 轮安全上限与外层父循环一样是for _ in range(30)而非死循环防止模型陷入无限工具调用只返回最终文本返回语句只拼接最后一条响应里的 text 块没有 text 时返回占位符(no summary)。子代理积累的所有tool_result随函数返回被整体丢弃。4.2 工具输出的截断防线遗留版中父代理的run_bash把输出截断到 50000 字符run_read也带 50000 字符上限agents/s04_subagent.pyrun_subagent内每次工具结果同样执行str(output)[:50000]。这意味着即便子代理连续 30 次读取大文件其上下文增长也被单条结果 50KB 的硬上限约束住——虽然这些结果最终会被丢弃但截断同时保护了子代理自己的上下文。5. 与 s03 相比的变化文档给出了 s03 → s04 的组件级变更对照表这里完整保留组件Before (s03)After (s04)工具5 个5 个基础 task仅父代理上下文单一共享上下文父 子上下文隔离子代理无run_subagent()函数返回值无仅摘要文本这个对照表点明了本章节的最小增量没有引入新的状态机、没有修改父循环结构只增加了一个工具和一个函数就实现了上下文隔离能力。这也是该课程“每课只加一个机制”的教学约束。6. 运行与验证6.1 运行遗留版文档配套代码cd learn-claude-code python agents/s04_subagent.py运行后进入s04 交互提示符文档推荐的三条测试指令依次考察“委派探索”“批量委派”“委派后在父会话验证”三种典型用法Use a subtask to find what testing framework this project uses子代理读文件父代理只拿结论Delegate: read all .py files and summarize what each one doesUse a task to create a new module, then verify it from here由于父/子共享工作目录第 3 条指令能验证“消息隔离但文件系统共享”的边界子代理创建的模块父代理随后可以直接用read_file读到。6.2 运行新版17 课主线 s06仓库 README 说明docs/与agents/属于旧版 12 课过渡线同一主题的当前主线版本是 s06_subagent/cd learn-claude-code python s06_subagent/code.py新版运行时的观察点比遗留版更丰富来自 s06_subagent/README.ja.md 的“试してみよう”一节子代理启动/结束是否打印[Subagent started]/[Subagent done]子代理的每次工具调用是否以[sub] ...前缀单独打印s06_subagent/code.py父代理最终是否只收到task返回的最终文本。6.3 测试用例给出的可验证断言tests/test_s06_subagent.py 用 mock 掉 Anthropic client 的方式对三个关键行为做了精确断言可以作为理解机制的“权威规格”工具集结构BASE_TOOLS恰为{bash, read_file, write_file, edit_file, glob}父代理工具集 基础集 ∪{task}子代理工具集 基础集且断言task not in child_names——直接固化了“禁止递归委派”这条规则全新消息与最终文本返回mock 出“第一轮 tool_use → 第二轮 end_turn”的两次响应断言子代理第一次messages.create收到的messages恰好只有一条 user 消息证明上下文从零开始且run_subagent的返回值就是最后一段文本 The note says child input.权限边界继承子代理调用write_file写工作区外的路径时被permission_hook拦截为 Permission denied by user且文件确实未创建——证明子代理与父代理共用同一套权限检查隔离的是上下文而非安全边界。7. 从 s04 到 s06同一机制的完整设计决策新版 s06_subagent/code.py 在遗留版骨架上补齐了课程 s03/s04 引入的权限与 Hooks 能力README 用一张表总结了子代理机制的五个关键决策这张表是对全文设计意图的最佳归纳来自 s06_subagent/README.md决策点选择理由会话全新的messages[]父会话历史不复制进子代理执行环境同一进程、同一WORKDIR文件系统的变化对两个循环都可见返回值仅最终文本子代理的工具调用与结果不进入父 messages委派深度SUB_TOOLS不含task本机制只允许一层委派工具策略共享 Hooks父与子使用同一套权限检查对照源码新版run_subagent()s06_subagent/code.py相比遗留版有三处增强工具执行统一走execute_tool先触发PreToolUsehooks含 deny list 与危险命令审批的permission_hook、调用日志log_hook再执行 handler最后触发PostToolUse的large_output_hook输出超过 100000 字符时告警见 s06_subagent/code.py。子代理的每一次bash/文件操作因此与父代理享有完全相同的治理策略Stop hook 可强制续跑当stop_reason不再是tool_use时先询问Stophook 是否要注入一条强制 user 消息继续循环否则才打印[Subagent done]并返回文本——与父循环的行为一致超限时显式告警30 轮未得到最终答案时返回明确文案Subagent stopped after 30 turns without a final answer.并打印[Subagent stopped]而不是遗留版那样静默返回最后一次响应的文本。这提醒父代理子任务可能没有真正完成。8. 小结子代理解决了什么、没有解决什么综合文档与仓库源码Subagent 机制可以浓缩为三条结论隔离的是上下文不是执行环境。子代理与父代理共享进程与工作目录委派来的“写文件、跑命令”类任务结果即时生效被丢弃的只是子代理中间消息历史。因此“探索类”任务读文件、查框架、汇总信息收益最大而需要独立沙箱的任务则超出了本机制的边界实现成本极低。一个task工具 一个带 30 轮上限的嵌套循环函数父循环零改造新版进一步把task收敛进统一的TOOL_HANDLERS分发映射与课程“加工具 加 handler”的核心理念完全一致安全边界不被隔离削弱。测试用例证实子代理的文件工具同样受工作区权限边界约束危险命令同样会被 deny list 拦截。理解这一章之后可以顺着课程继续s07 用“按需加载技能”解决不同子任务需要不同领域知识的问题避免把所有知识堆进 system prompts08 则直接处理“上下文总会满”的压缩策略——两者与本机制共同构成 harness 层的上下文管理体系。【免费下载链接】learn-claude-codeBash is all you need - A nano claude code–like 「agent harness」, built from 0 to 1项目地址: https://gitcode.com/GitHub_Trending/an/learn-claude-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价