1. 从 AgentTool.call 到 runAgent:一次子代理执行到底经历了什么
如果你正在读 claud-code 的源码,大概率会在tools/AgentTool/这一层卡住:入口文件看起来只是解析参数,但真正跑起来之后,工具池、权限、MCP、hooks、后台任务、通知框架全都缠在一起。这篇就沿着 agent 执行流程往下拆,从AgentTool.call一路走到runAgent()里的query()循环,把每一步“谁在调度、谁在收敛、谁在清理”讲清楚。
同时我会把 TaoToken 的配置骨架嵌进这条链路里。原因很直接:claud-code 这类工具最终都要落到一个统一的模型通道上,而 TaoToken 提供的是 OpenAI 兼容的 Key/API 入口,你只要把 base_url 和 key 配好,agent 执行链路里的每一次query()调用都会走同一条通道。官网入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 根地址是 https://taotoken.net/api ,后面配置里会反复用到。
适合谁看:已经跑通过 claud-code 基础对话、想搞清楚 agent 子任务是怎么被调度和结束的开发者;以及想把 agent 执行链路接到统一 Key 通道、不想每个工具单独配一遍的人。下面所有配置都可以直接复制,改掉 key 就能用。
2. TaoToken 前置:统一 Key 与 API 通道在链路里的位置
在拆源码之前,先把“模型通道”这件事定下来。claud-code 的 agent 执行流程里,runAgent()最终会调用query()驱动对话循环,而query()每次请求都要落到一个模型端点上。如果你用的是多个工具(claud-code、Cline、CC Switch 等),每个都单独配 key 会非常乱。TaoToken 的做法是给你一个统一的 API 根地址和一把 Key,所有兼容 OpenAI 协议的工具都指向它。
你需要先拿到两样东西:
- API Key:在控制台的 API Keys 页面创建,形如
sk-...。地址是 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= - API 根地址:
https://taotoken.net/api(注意这个地址不带 UTM 参数,配置里就写这个)
注意:base_url 末尾不要多加
/v1之外的路径,不同工具对路径拼接方式不一样,写错会直接 404。TaoToken 的兼容层会处理/v1/chat/completions这类标准路径。
拿到之后,先别急着改 claud-code 的配置。建议先用模型对话页面验证一下 Key 是否可用,地址在 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。能正常返回内容,再往下接 agent 链路,否则你会分不清是配置问题还是 Key 问题。
3. 可复制配置:settings.json / config.toml 骨架与 CC Switch 片段
这一节给三份可直接复制的骨架。第一份是 claud-code 的settings.json,第二份是通用config.toml,第三份是 CC Switch / Cline 的配置片段。三份都指向同一个 TaoToken 通道。
3.1 settings.json 骨架
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的TaoToken密钥", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" }, "permissions": { "allow": [ "Read", "Grep", "Glob" ], "deny": [] }, "agent": { "maxTurns": 30, "runInBackground": false } }这里ANTHROPIC_BASE_URL指向 TaoToken 的 API 根地址,ANTHROPIC_API_KEY填你在控制台创建的 Key。agent.maxTurns对应源码里runAgent()的maxTurns参数,达到上限会触发max_turns_reachedattachment 然后 break,走正常完成路径。
3.2 config.toml 骨架
[provider] name = "taotoken" base_url = "https://taotoken.net/api" api_key = "sk-你的TaoToken密钥" model = "claude-sonnet-4-20250514" [agent] max_turns = 30 run_in_background = false isolation = "none" [agent.tools] allow = ["Read", "Grep", "Glob", "Edit"] deny = []isolation字段对应源码里的isolation参数,可选none/worktree。如果你在做多 agent 并行,建议先用none跑通,再切worktree做隔离。
3.3 CC Switch / Cline 配置片段
{ "provider": "openai-compatible", "baseUrl": "https://taotoken.net/api", "apiKey": "sk-你的TaoToken密钥", "model": "claude-sonnet-4-20250514", "customHeaders": { "x-agent-source": "claud-code" } }CC Switch 和 Cline 都支持 OpenAI 兼容协议,把baseUrl指向 TaoToken 根地址即可。customHeaders是可选的,方便你在日志里区分请求来源。
提示:三份配置里的 Key 是同一把。TaoToken 的 Key 是跨工具通用的,不需要为每个工具单独申请。如果你要长期跑编码 agent,可以看 Coding Plan 页面 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,按用量规划更省心。
4. 验证请求:确认 agent 执行链路真的走通了
配置写完不代表链路通了。你需要一个能观察到“agent 被触发、工具被调用、结果被收敛”的验证动作。下面给一个最小可复现的验证流程。
第一步,确认基础请求能通。在终端里直接发一个 curl:
curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的TaoToken密钥" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-20250514", "messages": [{"role": "user", "content": "回复 OK 两个字母"}], "max_tokens": 16 }'如果返回里有choices[0].message.content且内容是OK,说明 Key 和通道都没问题。这一步不通,后面 agent 链路一定不通。
第二步,触发一次 agent 调用。在 claud-code 里输入一个需要读文件的指令,比如“读一下当前目录的 package.json,告诉我 name 字段”。观察输出里是否出现工具调用记录(Read 工具被调用)。如果出现,说明AgentTool.call已经走到runAgent(),并且工具池组装成功。
第三步,检查后台任务通知。如果你把run_in_background设为 true,agent 会走runAsyncAgentLifecycle(),完成后会 enqueue 一条<task-notification>。你可以在输出里搜这个标签,出现即代表异步生命周期走通了。
第四步,验证 maxTurns 收敛。把maxTurns临时改成 2,然后给一个需要多轮工具调用的任务。观察是否在第二轮后停止,并且返回的是“部分完成”的结果。这对应源码里max_turns_reached触发 break 的逻辑。
实测下来,这四步能覆盖 agent 执行链路的主要分支:同步、异步、工具调用、轮次收敛。任何一步失败,都能定位到具体环节。
5. 本篇常见错排查:agent 不触发、工具池为空、通知不出现
这一节列几个我在拆源码和配 TaoToken 时踩过的坑,按出现频率排序。
错误一:agent 完全不触发,只返回普通对话。最常见原因是subagent_type没给,且 fork gate 没开。源码里AgentTool.tsx的逻辑是:显式给subagent_type走 normal 路径;省略且 fork gate 开走 fork;省略且 fork gate 关则默认GENERAL_PURPOSE_AGENT。如果你既没给类型又没开 fork,可能落到默认 agent 但工具池为空。检查settings.json里的permissions.allow是否至少包含Read。
错误二:工具池为空,agent 报“no tools available”。这通常是assembleToolPool()拿到的 MCP 工具列表为空。如果你在 agent frontmatter 里声明了requiredMcpServers,源码会等待 pending 连接并验证 server 是否真的有 tools。验证方式是看启动日志里有没有 MCP 连接成功的记录。没连上就先别声明 required。
错误三:后台 agent 跑完但通知不出现。检查runAsyncAgentLifecycle()是否被调用。如果你用的是同步路径(run_in_background: false),不会有<task-notification>,结果直接通过finalizeAgentTool()返回。只有异步路径才会 enqueue 通知。另外,TaskOutput(block=true)会等任务完成才解锁,如果你在等通知的同时又调了 TaskOutput,可能看起来像卡住。
错误四:请求 401 或 404。401 是 Key 问题,去控制台确认 Key 是否启用;404 是 base_url 写错,确认写的是https://taotoken.net/api而不是带其他路径。如果工具自动拼接/v1,你的 base_url 就不要重复带/v1。
错误五:maxTurns 到了但结果丢失。源码里达到 maxTurns 会走正常完成路径,finalizeAgentTool()会抽取最后的文本块作为 result。如果你发现结果为空,可能是最后一轮没有文本输出,只有 tool_use。这种情况下检查extractPartialResult()是否被触发,它会在 abort 场景下尽力抽取已有 messages。
排障时如果涉及接入配置,优先看接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,里面有各工具的完整配置示例。Key 相关的问题直接去 API Keys 页面 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 重新生成一把对比测试。
6. 把 agent 链路接到统一通道:下一步怎么做
拆完AgentTool.call到runAgent()的主链路,你会发现真正影响 agent 行为的其实是三件事:工具池怎么组装、权限模式怎么定、结束条件怎么收敛。这三件事在源码里分别对应resolveAgentTools()、permissionMode处理和finalizeAgentTool()。而模型通道是这三件事之外的基础设施,配一次就能被所有 agent 复用。
如果你只是想让 agent 跑起来,按第 3 节的settings.json骨架填好 TaoToken 的 Key 和 base_url,再用第 4 节的 curl 验证一次,基本就能通。如果你要做多 agent 并行或 worktree 隔离,建议先把isolation设为none跑通单链路,再逐步加复杂度。
长期跑编码 agent 的话,Coding Plan 页面 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 有按用量的方案,比每次单独配 Key 省事。模型对话页面 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 可以随时验证通道是否正常,不用改配置就能测。
最后留一个实用技巧:在settings.json里把agent.maxTurns设成 30 左右,既能覆盖大多数工具调用链,又不会因为某个 agent 卡死而无限跑。配合 TaoToken 的统一通道,你可以在日志里看到每次query()的请求,方便对照源码里的querySource字段定位是哪个 agent 发起的调用。