ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

learn claude code S04 Subagent 详解笔记:用 TaoToken 统一 Key 打通工具调用与上下文隔离

2026/9/25 12:11:25 拓冰建站 浏览量
learn claude code S04 Subagent 详解笔记:用 TaoToken 统一 Key 打通工具调用与上下文隔离 1. 为什么 Subagent 的上下文隔离值得单独写一篇Claude Code 的 S04 里Subagent 是一个很容易被低估的机制。表面上看它只是多了一个task工具实际上它解决的是 Agent 跑复杂任务时最头疼的问题上下文被中间数据撑爆。你让 Agent 调研一个仓库里所有 Python 文件用了哪些第三方库它会 grep 出几千行、读十几个文件、跑 pip list这些中间结果全堆在 messages 里到第十几轮时原始目标已经被压到上下文底部模型开始重复读文件、分析无关代码、最后给一个残缺结论。Subagent 的做法是父代理不自己干脏活而是派一个子代理去干。子代理从空白上下文开始自己跑完探索只把最后一段摘要返回给父代理。几万行中间数据留在子代理的sub_messages里函数返回后直接被回收父代理的上下文只多了两条消息——一次 task 调用和一次摘要结果。这篇笔记面向正在用 Claude Code 做多 Agent 协作、或者自己写 Agent Harness 的人。我会先讲清楚 Subagent 的隔离边界到底在哪然后给出用 TaoToken 统一 Key 接入settings.json的可复制配置骨架最后演示一个 Subagent 调用外部工具的完整验证动作让你能亲手跑通一次调用链。TaoToken 在这里的作用是提供一个统一的 API 通道父代理和子代理共用同一个客户端实例Key 和 base_url 只配一次。2. TaoToken 前置统一 Key 与 API 通道在讲配置之前先把 TaoToken 的定位说清楚。它是一个统一的模型 API 接入通道你可以在官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后拿到一个 Key然后用这个 Key 同时驱动父代理和子代理。对于 Subagent 这种一个进程里跑多个独立会话的场景统一 Key 的好处很直接父代理和子代理共享同一个Anthropic客户端实例不用为每个子代理单独配一套凭证。S04 源码里这两行是关键client Anthropic(base_urlos.getenv(ANTHROPIC_BASE_URL)) MODEL os.environ[MODEL_ID]父代理和子代理共用这个client。也就是说只要环境变量里配好了 TaoToken 的 base_url 和 Key子代理自动继承不需要在run_subagent()里再传一次凭证。这就是统一 Key 打通工具调用的含义——工具调用走的是同一个通道隔离的是上下文不是连接。你需要准备的东西一个 TaoToken Key在控制台的 API Keys 页面创建地址是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite一个你想用的模型 ID以及 Python 环境里的anthropic包。模型对话功能可以先在 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 里试一下确认 Key 能用再往下走。注意TaoToken 的 API 端点是 https://taotoken.net/api配置时不要带 UTM 参数UTM 只用于官网和 deep link 的跳转追踪。3. 可复制配置settings.json 与 Subagent 骨架3.1 settings.json 里的统一 Key 配置Claude Code 的settings.json支持通过env字段注入环境变量。把 TaoToken 的 base_url 和 Key 写进去父代理和它派发的所有子代理都会读到同一份配置。下面是一个可以直接复制的骨架{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的TaoTokenKey, MODEL_ID: claude-sonnet-4-20250514 }, permissions: { allow: [ Bash(grep:*), Bash(find:*), Bash(pip show:*), Read, Write, Edit ] } }这里有几个点值得说明。ANTHROPIC_BASE_URL指向 TaoToken 的 API 端点ANTHROPIC_API_KEY填你在控制台创建的 Key。MODEL_ID是父代理和子代理共用的模型S04 源码里MODEL os.environ[MODEL_ID]读的就是它。permissions.allow里我放开了 grep、find、pip show 这几个命令因为 Subagent 做调研时最常用的就是它们如果你不想让子代理跑 shell可以把 Bash 相关的条目删掉只留 Read/Write/Edit。如果你用的是 Claude Code 的 coding-plan 模式配置入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 那边的 Key 和这里的ANTHROPIC_API_KEY是同一套。3.2 Subagent 的核心骨架S04 的run_subagent()是整个机制的心脏。下面是我按源码逻辑整理的可运行版本重点看sub_messages的初始化和工具列表的差异import os from anthropic import Anthropic WORKDIR os.getcwd() client Anthropic( base_urlos.getenv(ANTHROPIC_BASE_URL), api_keyos.getenv(ANTHROPIC_API_KEY), ) MODEL os.environ[MODEL_ID] SUBAGENT_SYSTEM ( fYou are a coding subagent at {WORKDIR}. Complete the given task, then summarize your findings. ) CHILD_TOOLS [ {name: bash, description: Run a shell command, input_schema: {type: object, properties: {command: {type: string}}, required: [command]}}, {name: read_file, description: Read a file, input_schema: {type: object, properties: {path: {type: string}, limit: {type: integer}}, required: [path]}}, ] def run_subagent(prompt: str) - str: sub_messages [{role: user, content: prompt}] for _ in range(30): 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: output dispatch(block.name, block.input) results.append({ type: tool_result, tool_use_id: block.id, content: str(output)[:50000], }) sub_messages.append({role: user, content: results}) return .join(b.text for b in response.content if hasattr(b, text)) or (no summary)sub_messages [{role: user, content: prompt}]这一行就是上下文隔离的全部魔法。子代理从零开始父代理的历史一条都看不到。CHILD_TOOLS里没有task所以子代理无法再派子代理递归深度被锁死在 1 层。range(30)是硬上限防止子代理陷入死循环烧 token。3.3 父代理的工具列表与 task 分发父代理比子代理多一个task工具这是唯一的区别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]}}, ]在父代理的agent_loop里工具分发多了一个分支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: output dispatch(block.name, block.input)run_subagent()是同步阻塞调用。父代理派发后等子代理跑完拿到摘要把摘要当成普通工具结果塞进自己的 messages。这个设计很简单没有并发、没有回调适合先把机制跑通。4. 验证请求跑通一次完整的 Subagent 调用链4.1 准备一个测试仓库先造一个有多文件、多依赖的小项目方便观察子代理的探索过程mkdir -p /tmp/subagent-demo/src cd /tmp/subagent-demo cat src/main.py EOF import requests import rich from pydantic import BaseModel class Item(BaseModel): name: str def fetch(url): return requests.get(url).text EOF cat src/utils.py EOF import requests from rich.console import Console console Console() def download(url): return requests.get(url).content EOF cat requirements.txt EOF requests rich pydantic EOF这个仓库里requests出现在两个文件rich出现在两个文件pydantic出现在一个文件。子代理需要 grep、读文件、可能还要 pip show 确认哪些是第三方库。4.2 发起一次 task 调用在父代理的 REPL 里输入帮我找出这个仓库里所有 Python 文件中使用到的第三方库列出每个库的名称、用途、以及哪些文件用到了它父代理会判断这是调研任务调用task工具prompt大致是探索当前目录下所有 Python 文件找出所有第三方库的 import/from 语句。终端会打印 task (调研依赖): 探索当前目录下所有 Python 文件找出所有第三方库的 import/from 语句4.3 观察子代理的探索过程子代理启动后sub_messages从空白开始。它的第一轮 API 调用会决定先 grepgrep -rn ^import\|^from --include*.py .返回结果追加到sub_messages。第二轮它可能读requirements.txt第三轮对不确定的库跑pip show确认。整个过程最多 30 轮中间数据全部留在子代理自己的上下文里。4.4 检查返回结果与父代理上下文子代理完成后run_subagent()返回最后一段文本类似已完成调研以下是发现的第三方库 1. requests (2 个文件): src/main.py(fetch), src/utils.py(download) 2. rich (2 个文件): src/main.py(输出), src/utils.py(Console) 3. pydantic (1 个文件): src/main.py(数据模型) 共计 3 个第三方库。父代理收到这个摘要展示给你。此时父代理的 messages 只有三条用户问题、task 工具调用、摘要结果。你可以加一行调试代码打印len(history)确认print(f[debug] parent messages count: {len(history)})跑完一次调研父代理的 messages 数量应该是个位数。如果不用 Subagent同样的任务会让父代理的 messages 涨到二三十条里面塞满 grep 输出和文件内容。4.5 用模型对话快速验证 Key如果你只想先确认 TaoToken 的 Key 和模型通道是通的不用跑整个 Harness直接在模型对话页面发一条消息即可https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。确认能正常返回后再回到代码里跑 Subagent 调用链。5. 本篇常见错排查5.1 子代理报 401 或 Key 无效最常见的原因是ANTHROPIC_API_KEY没被读到。S04 源码里client Anthropic(base_urlos.getenv(ANTHROPIC_BASE_URL))只传了 base_urlKey 依赖anthropic包自动读环境变量。如果你在settings.json里配了env但启动 Claude Code 的 shell 里没有 export子代理可能读不到。排查方法是在run_subagent()开头打印print([debug] base_url:, os.getenv(ANTHROPIC_BASE_URL)) print([debug] key prefix:, (os.getenv(ANTHROPIC_API_KEY) or )[:8])如果 key prefix 是空的说明环境变量没注入。回到settings.json检查env字段的拼写或者直接在 shell 里 export 一次再跑。5.2 子代理跑满 30 轮还没结束range(30)是硬上限跑满说明任务太大或者模型卡住了。常见原因是 prompt 太模糊子代理不知道该找什么反复 grep 相同模式。解决办法是把 task 的 prompt 写具体比如只统计 import 和 from 语句不要读文件内容用 grep 一次拿到所有结果。如果任务确实大拆成多个 task 调用让父代理分两次派发。5.3 父代理上下文还是膨胀了检查run_subagent()的返回值。如果返回的是sub_messages而不是最后一段文本隔离就失效了。正确的返回是return .join(b.text for b in response.content if hasattr(b, text)) or (no summary)只取response.content里的 text block不返回sub_messages。另外确认sub_messages是函数内的局部变量函数返回后会被 GC 回收不要把它挂到全局或父代理的 messages 上。5.4 子代理调用了不存在的工具CHILD_TOOLS里没有task如果子代理的响应里出现task调用说明你误把PARENT_TOOLS传给了run_subagent()。检查client.messages.create(toolsCHILD_TOOLS)这一行确认传的是子代理的工具列表。这个错误会导致递归派发token 消耗会失控。5.5 工具结果被截断导致子代理误判run_read()里有个limit参数超过时会加... (N more)提示。如果子代理读大文件时没看到这个提示可能误以为文件只有前 N 行。检查你的run_read()实现确保截断时带上剩余行数提示。同理run_bash()的[:50000]截断也要保留否则一次 grep 输出几万行会直接撑爆子代理的上下文。5.6 路径逃逸报错safe_path()会在路径超出WORKDIR时抛ValueError。如果你在测试时传了绝对路径比如/tmp/subagent-demo/src/main.py而WORKDIR是/tmp/subagent-demo拼接后resolve()的结果可能不在WORKDIR下。解决办法是统一用相对路径或者把WORKDIR设成测试仓库的根目录。这个报错本身是安全机制在起作用不要为了图方便把它删掉。6. 继续往下走接入文档与 Coding PlanSubagent 的隔离边界理解清楚后下一步是把它接到真实的多 Agent 协作场景里。如果你在排障或接入过程中遇到问题先看接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有 base_url、Key 创建、模型列表的完整说明。Key 管理在控制台https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。如果你打算长期用 Claude Code 做编码和 Agent 开发Coding Plan 比按量计费更划算配置入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。它和这篇里的settings.json配置是同一套 Key不用重新申请。最后留一个我踩过的坑Subagent 的sub_messages是局部变量但如果你在run_subagent()里用了全局的client而client的timeout设得太短子代理跑到第 20 轮时可能因为单次请求超时中断。建议把Anthropic客户端的timeout设到 120 秒以上和run_bash()的 120 秒超时对齐。这样父代理和子代理的请求节奏一致不会出现子代理还在跑、父代理已经超时的情况。