Subagent 生命周期

对应源码

主题源码
状态、创建、取消和结果注入nanobot/agent/subagent.py::SubagentStatus、SubagentManager
Subagent hooknanobot/agent/subagent.py::_SubagentHook
系统提示和完成通知模板nanobot/templates/agent/subagent_system.md、subagent_announce.md
Subagent Tools 注册nanobot/agent/tools/loader.py、nanobot/agent/tools/spawn.py

学习目标

读完后,你应当能够:

  • 说明 Subagent 使用哪些共享组件、隔离哪些状态;
  • 解释 Subagent 结果如何回到主 Session;
  • 说明并发限制和取消;
  • 避免把当前实现描述为通用多 Agent 编排平台。

前置知识

  • 理解 AgentRunner;
  • 理解 MessageBus 和 pending queue;
  • 理解 Tool scope。

1. SubagentManager 的职责

位置:

  • nanobot/agent/subagent.py::SubagentManager

它管理:

  • 后台 Task;
  • task ID 和状态;
  • 按 Session 关联的任务集合;
  • Subagent ToolRegistry;
  • 独立 AgentRunner 执行;
  • 结果公告;
  • Session 级取消。

2. 共享与隔离

项目行为
Provider默认共享主 Agent 当前 Provider/model
AgentRunner 实现复用同一个 AgentRunner 类
ToolRegistry为 Subagent 单独创建
Tool scopescope="subagent"
workspace默认共享,也可使用 workspace scope
Session history不直接加载主 Session history
system prompt使用 Subagent 专用模板和 Skill 摘要
结果作为新 InboundMessage 回到主 Agent

Subagent 输入主要是:

subagent system prompt
  + task description

它不是复制主 Agent 的完整当前上下文。

3. 创建流程

spawn():

  1. 生成短 task ID;
  2. 创建 SubagentStatus;
  3. 保存 origin channel/chat/session;
  4. asyncio.create_task(_run_subagent(...));
  5. 记录 running task;
  6. 记录 Session 到 task ID 的映射;
  7. 注册 done callback 清理内存状态;
  8. 立即向主 Agent 返回“已启动”文本。
flowchart TD
    MAIN["主 Agent 调用 spawn"] --> ID["创建 task_id/status"]
    ID --> TASK["后台 asyncio Task"]
    TASK --> TOOLS["独立 subagent ToolRegistry"]
    TOOLS --> RUN["AgentRunner.run"]
    RUN --> ANN["announce result"]
    ANN --> BUS["MessageBus.publish_inbound"]
    BUS --> MAIN2["主 Agent 同一 Session"]

4. Subagent ToolRegistry

_build_tools():

  • 创建新的 Registry;
  • 创建新的 ToolContext;
  • 继承必要的 exec/web/file 配置;
  • 应用 workspace restriction;
  • 使用 ToolLoader.load(..., scope="subagent")。

不是所有主 Agent Tool 都会自动暴露给 Subagent。

这避免:

  • MessageTool 等产品层能力被后台任务滥用;
  • Subagent 自己继续无限 spawn;
  • 不适合后台执行的 Tool 被误用。

具体 scope 以 ToolLoader 和每个 Tool 配置为准。

5. Runner 配置差异

Subagent AgentRunSpec 具有一些更严格选择:

  • fail_on_tool_error=True
  • finalize_on_max_iterations=False
  • 自定义 max-iteration message;
  • error_message=None
  • checkpoint 只更新 SubagentStatus;
  • 使用 origin Session 对应的 LLM timeout 策略;
  • 使用独立 hooks 记录阶段和 iteration。

所以“使用同一个 AgentRunner”不等于行为完全相同。

6. 结果如何回主 Agent

_announce_result():

  1. 使用 subagent_announce.md 渲染结果;
  2. 创建 InboundMessage;
  3. channel="system";
  4. sender_id="subagent";
  5. metadata 标记 injected_event=subagent_result;
  6. 使用 session_key_override 对齐主 Agent effective Session;
  7. 发布到 MessageBus inbound。

如果主 Session 仍在运行:

  • 结果进入该 Session 的 pending queue;
  • 作为 mid-turn injection 被 Runner 接收。

如果主 Session 已空闲:

  • 结果作为新的系统入站 Turn 处理。

7. 为什么不直接发 OutboundMessage

Subagent 结果先回主 Agent,而不是直接展示给用户,主 Agent因此可以:

  • 读取结果;
  • 与原任务合并;
  • 继续调用工具;
  • 解释部分失败;
  • 决定最终用户回复。

直接发 outbound 会绕过主 Agent 的整合和 Session 语义。

8. 并发限制

配置:

  • agents.defaults.maxConcurrentSubagents
  • 当前默认值为 1。

达到限制时 spawn Tool 返回错误,由主 Agent决定等待或调整任务。

该限制保护:

  • 本地模型 KV cache;
  • Provider 并发额度;
  • workspace 中并发副作用;
  • 用户对后台任务数量的可控性。

9. 取消和状态

SubagentManager 维护:

  • _running_tasks
  • _task_statuses
  • _session_tasks

支持:

  • 按 task ID 观察;
  • 按 Session 取消所有 Subagent;
  • done callback 清理状态。

这些状态主要在进程内,不是持久化任务队列。进程重启后不能假设后台 Subagent 自动恢复。

10. 当前能力边界

当前实现更准确的描述是:

主 Agent 可以启动有限数量的后台工具型 AgentRunner,并把结果重新注入原 Session。

它不是:

  • 持久化多 Agent 工作流引擎;
  • 带 DAG 的调度器;
  • 多 Agent 共享黑板;
  • 自动共识协议;
  • 跨进程任务系统。

观察任务

  1. 启动一个只读 Subagent;
  2. 记录返回的 task ID;
  3. 在 _build_tools() 查看工具集合;
  4. 比较主 Agent 和 Subagent system prompt;
  5. 查看结果 InboundMessage;
  6. 观察主 Turn 活跃和空闲时的两种回传;
  7. 将并发限制保持为 1,先理解生命周期,不做压力测试。

源码与测试

  • nanobot/agent/subagent.py
  • nanobot/templates/agent/subagent_system.md
  • nanobot/templates/agent/subagent_announce.md
  • nanobot/agent/tools/loader.py
  • tests/agent/test_subagent.py
  • tests/agent/test_subagent_lifecycle.py
  • tests/agent/tools/test_subagent_tools.py

自检

  1. Subagent 为什么不直接读取主 Session history?
  2. 为什么要创建独立 ToolRegistry?
  3. 结果为什么作为 InboundMessage 而不是 OutboundMessage?
  4. 默认并发限制为什么只有 1?
  5. 哪些事实说明它不是持久化多 Agent 编排平台?

下一步

进入“运行表面”,学习 Channel、Gateway、API、SDK 和 WebUI 如何围绕同一个核心 runtime 工作。