1. 从 Nanobot 源码看 SubAgent 到底隔离了什么
如果你正在读 Nanobot 的源码,大概率会卡在SubagentManager这个类上:它明明复用了主 Agent 的provider、bus、workspace,为什么还要单独搞一套工具集和消息循环?答案就藏在「上下文隔离」四个字里。SubAgent 不是主 Agent 的克隆,而是一个共享基础设施、但拥有独立执行上下文的轻量执行单元。它解决的核心问题是:当主 Agent 的对话历史越来越长、工具集越来越杂时,如何把「耗时且独立」的子任务切出去,让主 Agent 的 Context Window 始终保持精简。
这篇文章不打算逐行翻译源码,而是把 Nanobot 里 SubAgent 的隔离机制拆成可复制的配置骨架,结合 TaoToken 统一 Key/API 通道,在 Cline 或 CC Switch 里跑通一个最小可用的 SubAgent 隔离示例。适合已经用过 Agent 工具、想理解上下文边界设计、并且希望自己动手验证隔离效果的开发者。读完之后,你应该能说清楚:哪些资源是共享的、哪些是隔离的、隔离边界画在哪里、以及怎么用一份settings.json或config.toml把边界固化下来。
Nanobot 是 HKUDS 开源的超轻量级个人 AI 助手框架,定位是「Ultra-Lightweight OpenClaw」,代码量小、结构清晰,非常适合拿来学习 Agent 架构。它的 SubAgent 实现有一个很典型的设计:复用主 Agent 的 LLM provider,但限制工具集和迭代次数,通过消息总线把结果异步通知回主 Agent。这个设计用一句话概括就是——主 Agent 保持对话专注,复杂任务委派给后台子代理执行,子代理的上下文在任务结束后被丢弃。
2. TaoToken 前置:统一 Key 与 API 通道
在动手配 SubAgent 之前,先把模型调用通道理顺。Nanobot 的 SubAgent 和主 Agent 共用同一个LLMProvider实例,这意味着你只需要维护一份 API 配置,主 Agent 和子代理都会走这条通道。我习惯用 TaoToken 来做这件事,原因是它把多家模型的调用收敛成一个统一的 Key 和 Base URL,配置一次就能在 Cline、CC Switch、Nanobot 之间复用,不用每个工具单独填一遍。
TaoToken 的 API 地址是https://taotoken.net/api,官网入口在https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。你需要先在控制台创建一个 API Key,然后把它填到各个工具的配置里。对于 Nanobot 这种需要主/子 Agent 共享 provider 的场景,统一通道的价值特别明显:子代理执行任务时不会因为 Key 不一致而报 401,也不会出现主 Agent 能调、SubAgent 调不了的尴尬。
具体操作上,先到控制台生成 Key,再确认你要用的模型名。Nanobot 的SubagentManager初始化时会接收model、temperature、max_tokens这几个参数,它们默认继承主 Agent 的配置,但你可以单独覆盖。如果你打算在 Cline 里做对照实验,建议把主 Agent 和 SubAgent 的模型分开配:主 Agent 用响应快的模型负责对话,SubAgent 用推理强的模型负责后台任务。这样既能验证隔离效果,又能直观感受到不同模型在子任务上的表现差异。
3. 可复制配置:settings.json 与 config.toml 骨架
这一节给出两份可以直接抄的配置骨架。第一份是 Cline 的settings.json,第二份是 Nanobot 风格的config.toml。两份配置的核心思路一致:把 provider 的 base_url 和 api_key 抽出来,让主 Agent 和 SubAgent 共享;把工具集和迭代次数分开,让 SubAgent 的边界清晰可见。
先看 Cline 的settings.json。Cline 本身不直接叫 SubAgent,但你可以通过自定义 API Provider 的方式,把 TaoToken 作为统一通道接进去,然后在任务描述里显式区分「主对话」和「后台子任务」。下面这份配置的关键字段是apiProvider、baseUrl、apiKey和model:
{ "apiProvider": "openai", "baseUrl": "https://taotoken.net/api", "apiKey": "sk-你的TaoToken密钥", "model": "claude-sonnet-4-20250514", "temperature": 0.7, "maxTokens": 4096, "subAgent": { "enabled": true, "model": "claude-sonnet-4-20250514", "maxIterations": 15, "restrictToWorkspace": true, "allowedTools": [ "read_file", "write_file", "edit_file", "list_dir", "exec", "web_search", "web_fetch" ], "deniedTools": [ "message", "spawn", "cron" ] } }这份配置里,subAgent.allowedTools对应 Nanobot 源码里 SubAgent 注册的那 7 个工具,deniedTools对应被排除的MessageTool、SpawnTool、CronTool。maxIterations设成 15,和源码里的max_iterations = 15保持一致。restrictToWorkspace打开后,子代理的文件操作会被限制在工作空间内,避免越界。
再看 Nanobot 风格的config.toml。如果你直接跑 Nanobot 源码,配置文件大概长这样:
[agents.defaults] model = "claude-sonnet-4-20250514" temperature = 0.7 max_tokens = 4096 max_iterations = 40 memory_window = 20 [agents.subagent] model = "claude-sonnet-4-20250514" temperature = 0.7 max_tokens = 4096 max_iterations = 15 restrict_to_workspace = true [tools.exec] timeout = 60 path_append = "" [tools] restrict_to_workspace = true [provider] base_url = "https://taotoken.net/api" api_key = "sk-你的TaoToken密钥"注意[agents.defaults]和[agents.subagent]两段:max_iterations一个是 40、一个是 15,这就是隔离边界在配置层面的体现。[provider]段只有一份,主 Agent 和 SubAgent 共用。如果你在 CC Switch 里做类似配置,思路是一样的:把 provider 抽成公共段,把 agent 行为拆成独立段。
提示:
api_key不要直接提交到 Git 仓库。建议用环境变量TAOTOKEN_API_KEY注入,配置文件里写api_key = "${TAOTOKEN_API_KEY}",Nanobot 和 Cline 都支持这种占位符写法。
4. 验证请求:跑通一次 SubAgent 隔离
配置写完之后,必须验证隔离是否真的生效。验证分三步:先确认主 Agent 能正常对话,再触发一次 SubAgent 任务,最后检查子代理的上下文是否被丢弃。
第一步,启动主 Agent 并发一条普通消息。如果你用 Nanobot,直接跑python -m nanobot或者对应的启动命令;如果用 Cline,在对话框里发一句「你好,确认通道正常」。预期结果是模型正常回复,说明base_url和api_key配置正确。如果这里就报 401 或连接超时,先回到第 5 节排查。
第二步,触发 SubAgent。在 Nanobot 里,主 Agent 会通过SpawnTool派生子代理,你可以直接给一个需要后台执行的任务,比如「帮我分析当前工作空间下所有 Python 文件的函数定义,整理成一份清单」。主 Agent 收到后应该返回类似Subagent [分析Python文件] started (id: a1b2c3d4). I'll notify you when it completes.的提示,这说明spawn()方法被调用了,子代理已经在后台跑起来。
第三步,观察结果回传。子代理完成后,会通过MessageBus发布一条InboundMessage,主 Agent 收到后转述给你。你会看到一条自然语言总结,比如「已分析完 12 个 Python 文件,共提取 47 个函数定义,清单已保存到 workspace/functions.md」。这条消息里不应该出现「subagent」「task_id」这类技术细节,因为源码里的announce_content明确要求主 Agent「Summarize this naturally for the user. Keep it brief」。
如果你想更直观地验证上下文隔离,可以在子代理任务执行前后分别查看主 Agent 的对话历史。Nanobot 的SessionManager会把主 Agent 的会话持久化到 JSONL 文件,而 SubAgent 的messages列表只存在于内存中,任务结束后就被丢弃。你可以在_run_subagent()返回后检查self._running_tasks,对应的task_id应该已经被_cleanup回调移除。这个「主 Agent 历史变长、子代理历史消失」的对比,就是上下文隔离最直接的证据。
5. 本篇常见错排查
配置和验证过程中,最容易踩的坑集中在通道、工具集和迭代次数三块。下面按报错现象倒推原因。
报错一:401 Unauthorized 或 invalid api key。最常见的原因是base_url写成了https://taotoken.net/api/带了尾部斜杠,或者api_key里混入了空格。检查settings.json和config.toml里的 URL 是否和https://taotoken.net/api完全一致,Key 是否从控制台完整复制。如果主 Agent 能通、SubAgent 报 401,说明子代理没有继承到 provider 配置,检查SubagentManager初始化时provider参数是否传了同一个实例。
报错二:SubAgent 启动后一直不返回结果。先看max_iterations是不是设得太小。源码里子代理默认 15 次迭代,如果你的任务需要读很多文件,15 次可能不够,子代理会在达到上限后返回Task completed but no final response was generated.。这时候要么调大max_iterations,要么把任务拆得更细。另一个可能是restrict_to_workspace打开后,子代理试图访问工作空间外的路径被拦截,工具调用失败但没抛异常,导致循环空转。
报错三:子代理试图发消息给用户,但工具不存在。这是设计使然,不是 bug。SubAgent 的工具集里没有MessageTool,它只能通过_announce_result()把结果交给主 Agent,由主 Agent 统一输出。如果你在子代理的 System Prompt 里看到「Do not initiate conversations」,就是在强调这个边界。排查时确认deniedTools里包含message、spawn、cron,这三个工具被排除是隔离机制的一部分。
报错四:会话级取消不生效。Nanobot 的cancel_by_session()依赖_session_tasks这个映射。如果spawn()调用时没有传session_key,子代理就不会被关联到会话,/stop命令自然取消不了它。检查你的调用代码,确保session_key参数有值。在 Cline 里做类似实验时,也要确认任务描述里带了会话标识。
注意:如果你在排查过程中需要重新生成 Key 或查看调用日志,直接去控制台的 API Keys 页面操作,不要在主 Agent 的对话里粘贴 Key,避免 Key 进入会话历史被持久化。
6. 把隔离边界固化下来
SubAgent 的上下文隔离,本质上是一次「资源分配决策」:哪些东西必须共享(provider、bus、workspace),哪些东西必须隔离(工具集、消息历史、迭代次数)。Nanobot 用一份SubagentManager把这个决策写死在代码里,而我们要做的是把这个决策翻译成配置文件,让它在 Cline、CC Switch、Nanobot 之间保持一致。
如果你打算长期用这套架构跑编码任务或 Agent 工作流,建议把 provider 配置和 agent 配置彻底分开管理:provider 走 TaoToken 统一通道,agent 行为按主/子角色拆成独立段。这样换模型时只改一处,调隔离边界时也只改一处。对于需要长时间运行的编码场景,可以进一步了解 Coding Plan 这类按周期计费的方案,把主 Agent 和 SubAgent 的调用成本控制住。
最后留一个可以立刻动手的验证动作:把上面那份config.toml里的max_iterations从 15 改成 5,重新跑一次「分析 Python 文件」的任务,观察子代理是不是更早返回「未生成最终响应」。这个对比能让你直观感受到迭代次数对隔离边界的影响——边界画得太紧,任务做不完;画得太松,隔离就失去了意义。找到那个刚好够用的值,就是配置骨架真正落地的时候。