learn claude code学习记录-S04:用subagent做上下文隔离,task工具实战

📅 发布时间:2026/10/8 6:34:45
learn claude code学习记录-S04:用subagent做上下文隔离,task工具实战
1. 长链路任务把主会话撑爆了怎么办如果你正在用 Claude Code 做稍微复杂一点的事比如「读一遍这个模块、找出所有调用点、再补一组测试」你大概率遇到过这种情况跑到第三四轮模型开始答非所问前面明明读过的文件它说没看过或者把两个函数的逻辑串在一起。这不是模型变笨了而是主会话的 messages 被中间过程的噪声填满了。Claude Code 里的 subagent子智能体配合 task 工具解决的正是这个问题把「读文件、grep、跑命令」这类局部探索放进一个独立上下文里做做完只把一段摘要带回主会话。父智能体继续持有干净的对话历史子智能体的中间过程直接丢弃。这就是上下文隔离。这篇是 learn claude code 学习记录的 S04我会先讲清楚父智能体、子智能体、上下文隔离这三个概念的最小模型然后给出可复制的 subagent 配置片段和 task 调用示例最后用一次「隔离前 vs 隔离后」的对比验证让你能在自己的项目里直接落地。适合已经跑通过基础 agent loop、想进一步控制上下文膨胀的开发者。核心检索词先摆出来Claude Code subagent 上下文隔离、task 工具实战、子代理拆分长链路任务。这三个词基本覆盖了本篇要讲的全部内容。先说结论子智能体的价值不是「多一个模型实例」而是「多一个干净上下文」。理解这一点后面所有配置都是顺理成章的。2. TaoToken 前置准备与 Claude Code 接入配置在动手写 subagent 之前得先让 Claude Code 能稳定调用模型。我这边用的是 TaoToken 作为统一入口官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。它的作用是给你一个兼容 Anthropic 协议的 Base URLClaude Code 和后面要写的 Python harness 都能直接指过去。先拿 Key。打开 https://taotoken.net/api-keys 新建一个 API Key复制出来。注意这个 Key 只在创建时完整显示一次丢了就重新建。然后配置 Claude Code。Claude Code 读取的是环境变量最省事的方式是写进 shell 配置。macOS / Linux 用~/.zshrc或~/.bashrcWindows 用系统环境变量面板。写入下面三件套export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_AUTH_TOKENsk-你刚才复制的Key export ANTHROPIC_MODELclaude-sonnet-4-5-20250929这里 Base URL、Key、Model ID 三件套缺一不可。Base URL 决定请求打到哪Key 决定身份Model ID 决定用哪个模型。改完执行source ~/.zshrc让配置生效再开一个新终端。如果你用的是 Claude Code 的 settings 文件方式也可以写进~/.claude/settings.json{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: sk-你的Key, ANTHROPIC_MODEL: claude-sonnet-4-5-20250929 } }两种方式选一种就行不要同时配否则排查起来容易乱。配完用claude启动随便问一句「你现在用的模型是什么」能正常回话就说明通了。如果你更想先验证模型本身能不能调通可以打开 https://taotoken.net/models 用对话界面直接发一条消息确认 Key 有效、额度正常再回到 Claude Code 里折腾 subagent。这一步能帮你把「Key 问题」和「配置问题」分开省很多时间。对于要长期跑编码任务、频繁派生 subagent 的场景可以考虑 Coding Plan地址是 https://taotoken.net/coding-plan 按套餐走比按量计费更可控尤其是你打算让子智能体反复读大文件的时候。3. 可复制的 subagent 配置与 task 工具调用现在进入正题。Claude Code 里父智能体要能主动说「这个子任务我想外包出去」靠的就是一个 task 工具在真实 Claude Code 里叫 Agent 工具。它的 schema 可以非常简单{ name: task, description: Spawn a subagent with fresh context. It shares the filesystem but not conversation history., input_schema: { type: object, properties: { prompt: {type: string}, description: {type: string, description: Short description of the task} }, required: [prompt] } }父智能体的工具集 基础工具 task。基础工具包括 bash、read_file、write_file、edit_file。子智能体只拿基础工具不给它 task这样它就没法继续派生子智能体防止无限递归。CHILD_TOOLS [ {name: bash, description: Run a shell command., input_schema: {type: object, properties: {command: {type: string}}, required: [command]}}, {name: read_file, description: Read file contents., input_schema: {type: object, properties: {path: {type: string}, limit: {type: integer}}, required: [path]}}, {name: write_file, description: Write content to file., input_schema: {type: object, properties: {path: {type: string}, content: {type: string}}, required: [path, content]}}, {name: edit_file, description: Replace exact text in file., input_schema: {type: object, properties: {path: {type: string}, old_text: {type: string}, new_text: {type: string}}, required: [path, old_text, new_text]}}, ] PARENT_TOOLS CHILD_TOOLS [ {name: task, description: Spawn a subagent with fresh context. It shares the filesystem but not conversation history., input_schema: {type: object, properties: {prompt: {type: string}, description: {type: string}}, required: [prompt]}}, ]子智能体的执行函数是整个隔离机制的核心。注意sub_messages是从一个全新的 user 消息开始的父会话的历史一个字都不带进来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)父智能体的 agent loop 里遇到 task 工具就调run_subagent把返回的摘要塞回 tool_resultdef agent_loop(messages: list): while True: response client.messages.create( modelMODEL, systemSYSTEM, messagesmessages, toolsPARENT_TOOLS, max_tokens8000, ) messages.append({role: assistant, content: response.content}) if response.stop_reason ! tool_use: return results [] for block in response.content: if block.type tool_use: if block.name task: desc block.input.get(description, subtask) prompt block.input.get(prompt, ) print(f task ({desc}): {prompt[:80]}) output run_subagent(prompt) else: 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)}) messages.append({role: user, content: results})关键点只有一个run_subagent返回的是最后一段文本sub_messages整个列表在函数返回后就被丢弃了。父会话的 messages 里只多了一条 tool_result里面是摘要不是子智能体读过的几十个文件内容。如果你想让子智能体继承父会话的部分上下文比如「基于我们刚才讨论的方案去补测试」那就用 fork 模式把父 messages 复制一份再追加子任务sub_messages list(parent_messages) sub_messages.append({role: user, content: prompt})但 fork 要谨慎它会把父会话的噪声也带进去隔离效果打折。建议先把空白上下文版本跑稳再考虑 fork。4. 验证请求与上下文隔离前后对比配置写完了得验证它真的在隔离。我设计了一个对比实验同一个任务一次让父智能体自己干一次用 task 外包给子智能体然后看父会话的 messages 长度和后续回答质量。任务描述「在 src 目录下找出所有调用parse_config的地方读一遍相关文件告诉我这个函数有没有被错误地传了 None。」先跑不隔离版本。父智能体自己 read_file、grep、再 read_file一轮轮下来父 messages 里堆了七八个文件的完整内容。跑完后我打印len(history)和每条消息的字符数父会话总字符数大概在 4 万左右。再跑隔离版本。父智能体第一轮就决定调 task task (find parse_config callers): 在 src 目录下找出所有调用 parse_config 的地方...子智能体在自己的上下文里读文件、grep、分析跑了 6 轮工具调用最后返回一句摘要「parse_config 在 a.py:42、b.py:118、c.py:7 被调用其中 b.py:118 传入了可能为 None 的变量 cfg建议加判空。」父会话的 messages 里这条 task 只贡献了一条 tool_result字符数不到 200。父会话总字符数从 4 万降到 8 千左右。然后我紧接着问父智能体第二个问题「那 b.py 那个调用点改成默认空字典会不会更安全」不隔离版本因为上下文里塞满了无关文件内容模型开始混淆把 a.py 的逻辑扯了进来。隔离版本因为父会话干净模型直接基于摘要和它自己按需读取的 b.py 片段回答准确得多。这就是上下文隔离前后的真实差异。子智能体的中间过程不写回父会话父会话的「信噪比」保住了后续问题才答得准。验证时你可以加一行打印观察父会话增长print(f[parent] messages{len(messages)}, chars{sum(len(str(m)) for m in messages)})放在 agent_loop 每轮开头跑两次对比数字会说话。5. 常见报错排查401、local proxy failed、reading choices配 subagent 的过程中报错基本集中在接入层跟 subagent 逻辑本身关系不大。我把踩过的几个列出来。401 Unauthorized。最常见。原因通常是ANTHROPIC_AUTH_TOKEN没生效或者 Key 复制时带了空格。检查echo $ANTHROPIC_AUTH_TOKEN有没有值注意 Claude Code 有时会优先读ANTHROPIC_API_KEY两个都设了会打架建议只留ANTHROPIC_AUTH_TOKEN。另外确认 Base URL 是https://taotoken.net/api结尾不要多加斜杠。local proxy failed / connection refused。这个报错说明请求根本没出去或者被本地某个配置拦了。先确认没有残留的HTTP_PROXY、HTTPS_PROXY环境变量env | grep -i proxy看一眼有就 unset 掉。然后确认网络能直连taotoken.net用curl -I https://taotoken.net/api看返回码。如果 curl 通但 Claude Code 不通多半是 settings.json 和环境变量冲突清掉一份。Error reading choices / 响应解析失败。这个通常出现在你混用了 OpenAI 格式和 Anthropic 格式的客户端。Claude Code 和本篇的 Python harness 用的都是 Anthropic 的 messages 接口返回结构是content数组不是choices。如果你看到reading choices字样说明请求被路由到了 OpenAI 兼容端点。检查 Base URL 是不是写成了带/v1的 OpenAI 风格地址改回https://taotoken.net/api。OAuth 相关报错。Claude Code 某些版本会尝试走 OAuth 登录流程如果你用的是 API Key 方式需要确保没有残留的登录态。删掉~/.claude下的凭据缓存文件重新用环境变量启动。子智能体跑飞 / 不返回。这不是接入问题是max_turns没设或设太大。run_subagent里的for _ in range(30)就是保护别去掉。如果子智能体一直调工具不收敛把 30 降到 10 试试同时检查它的 system prompt 有没有明确说「完成后给摘要」。排查顺序建议先 curl 验证 Key 和 Base URL再验证 Claude Code 能对话最后才怀疑 subagent 代码。大部分「subagent 不工作」其实是第一步就没通。6. 把 subagent 用起来的几个实用建议跑通之后有几个经验可以帮你少走弯路。子智能体的 system prompt 要写死「完成后总结」否则它可能读了一堆文件然后直接停返回空。我用的模板是You are a coding subagent at {WORKDIR}. Complete the given task, then summarize your findings.task 的 description 字段别省。它虽然不参与模型推理但在终端打印出来能让你一眼看出父智能体在派什么活调试时非常有用。不是所有任务都值得外包。单文件的小改动父智能体自己干更快。真正适合 task 的是「探索型」任务跨多文件搜索、读一堆文件做归纳、跑测试看结果。这类任务的中间过程噪声最大隔离收益最高。如果你想让子智能体能力更强可以给它加只读的 grep 工具但依然不要给 task。递归派生是上下文失控的头号原因。最后父会话的 messages 增长要定期观察。我习惯在 agent_loop 里打印字符数一旦发现某轮暴涨就说明有该外包的活没外包出去。这个习惯帮我省了不少 token。想直接体验模型对话验证配置可以走 https://taotoken.net/models 接入文档在 https://taotoken.net/doc 需要管理多个 Key 就去 https://taotoken.net/api-keys 。长期跑编码和 agent 任务的话Coding Plan 在 https://taotoken.net/coding-plan 。