跳到主要内容

子智能体与后台任务:委派、劳务市场与并发

30 秒导读: 当一个大任务该被拆开、或某段活可以「先跑着、你先干别的」时,Kimi CLI 让根智能体通过一个 Agent 工具,把子任务交给一个隔离的子智能体去做。子智能体不是新进程,而是复用父运行时、却持有自己 Soul 和上下文的独立工人。本章讲两条执行路径:前台(父阻塞等子完成、拿回一段摘要)和后台(子并发跑、完成时用通知把结果回填给父)。

本章属于 Kimi CLI 系列的第 6 章。若你还不清楚 Soul / Wire / 运行时是什么,建议先读 智能体主循环从命令到智能体; 工具加载与审批见 工具系统。本章只讲「派活」这一层。


1. 这是什么(零基础也能懂)

一句话定义

子智能体(subagent)是一个「被主智能体雇来干一件具体活」的临时 AI 工人:它有自己的 对话历史、自己的工具集、自己能用的模型,干完活给主智能体交回一段总结——但它看不到 主智能体的上下文,主智能体也只能看到它的最后一条消息

解决什么问题

想象你在终端里让 Kimi 帮你改一个大项目。主智能体的上下文窗口是有限的、宝贵的。有两类活 特别浪费这块「主内存」:

  • 探索类:为了搞清楚 auth 模块怎么工作,要 grep 十几次、读七八个文件——这些搜索的 中间垃圾如果全堆进主上下文,主智能体很快就被噪音淹没。
  • 可并行类:跑一轮耗时的重构、或一次独立的调查,主智能体本可以「先派出去、自己接着 干别的」,而不是干等着。

子智能体就是答案:把脏活累活隔离到一个独立工人那里,主智能体只收一份干净的摘要。

用起来什么样

主智能体(模型)在一次工具调用里这样派活(下面是工具入参的直觉示意):

// 示意,非源码:Agent 工具的一次调用
{
"description": "查清 auth 流程",
"prompt": "thoroughness=medium。搞清楚登录鉴权怎么走,涉及哪些文件。",
"subagent_type": "explore",
"run_in_background": false
}
  • subagent_type="explore" 选一个只读、擅长搜索的内置类型;
  • run_in_background=false 表示前台——主智能体会阻塞等它,拿回一段带 [summary] 的结果;
  • 换成 run_in_background=true,主智能体立刻拿到一个 task_id 就返回,子智能体在后台并发跑, 完成时系统会自动通知主智能体。

一句话直觉

把子智能体想成「共享同一间办公室(运行时)、但各写各笔记本(Soul + 上下文)的同事」。 办公室里的公共设施——审批规则、模型账号、劳务市场名册——大家共用;但每个人的思路、对话、 草稿是彼此隔离的。这条「共享运行时、独立 Soul」的界线,是理解本章一切的钥匙。


2. 顶层全景(它大概怎么转)

一张图看清两条路径

根智能体 Soul (role="root")
│ 调用 Agent 工具

┌────────────────────────┐
│ AgentTool.__call__ │ 只有 root 能派活
│ tools/agent/__init__.py│ 校验 model / role
└───────────┬────────────┘
run_in_background?
false │ │ true
▼ ▼
┌─────────────────────┐ ┌──────────────────────────┐
│ 前台路径 │ │ 后台路径 │
│ ForegroundSubagent │ │ _run_in_background │
│ Runner.run() │ │ → create_agent_task │
│ 父在此 await │ │ → asyncio.create_task │
└──────────┬──────────┘ └────────────┬─────────────┘
│ 两条路径都走这一段共享装配 ↓
▼ ▼
┌───────────────────────────────────────────┐
│ prepare_soul() (subagents/core.py) │
│ ① SubagentBuilder 建 Agent │
│ ② copy_for_subagent 克隆运行时 │
│ ③ 恢复 / 新建独立上下文与 system prompt │
└──────────────────────┬────────────────────┘

run_with_summary_continuation
(跑一步 Soul,摘要过短就续写)

前台:把 [summary] 直接返回给父工具调用
后台:完成后写通知 → 下一轮循环由 reconcile 回填给父

怎么读这张图: 从上往下是「派活 → 分叉 → 两条路径殊途同归于 prepare_soul → 跑 Soul」。 左右分叉只在谁来 await、结果怎么回到父这两点上不同;建 Soul 的过程两条路径完全共用

部件一句话职责

部件干什么在哪个文件
AgentTool派活的工具入口;校验权限、渲染内置类型清单、分流前台/后台tools/agent/__init__.py:65
LaborMarket「劳务市场」:内置子智能体类型的注册表subagents/registry.py:8
AgentTypeDefinition / ToolPolicy一个子智能体类型的定义(工具策略、默认模型、何时用)subagents/models.py:26
ForegroundSubagentRunner前台运行器:建 Soul、跑、把子事件冒泡给父subagents/runner.py:197
prepare_soul前台/后台共用的「建 Soul」流水线subagents/core.py:36
Runtime.copy_for_subagent克隆运行时:共享公共设施、独立 DenwaRenjisoul/agent.py:339
SubagentStore子智能体实例的磁盘持久化(上下文/元数据/输出)subagents/store.py:64
BackgroundTaskManager后台任务的创建、状态机、通知发布background/manager.py:36
BackgroundAgentRunner后台子智能体的 in-process 运行器background/agent_runner.py:28
TaskList / TaskOutput / TaskStop主智能体查看/等待/停止后台任务的工具tools/background/__init__.py

主线走一遍(高层)

  1. 根智能体调用 Agent(...)。工具先检查 runtime.role == "root"——子智能体不能再派子智能体
  2. subagent_type 从劳务市场取出类型定义(工具策略、默认模型)。
  3. prepare_soul 建出一个复用父运行时、但拥有独立 Soul 和上下文的子智能体。
  4. 前台:父 await 子跑完,拿回 [summary];后台:父立刻拿 task_id 返回,子并发跑。
  5. 后台子完成后写一条通知;主循环下一轮把通知回填进父的上下文。

3. 核心原理(逐个机制,由浅入深)

3.1 Agent 工具入口:谁能派、派给谁、怎么派

它要解决的小问题: 把「一句自然语言的委派意图」翻译成一次受控的子智能体启动,并挡住 不该发生的事(子派子、无效模型别名)。

参数一览。 Agent 工具的入参定义在 tools/agent/__init__.py:21Params

参数作用默认
description3-5 词的任务简述(给 UI 展示)必填
prompt交给子智能体的实际任务必填
subagent_type选哪个内置类型"coder"
model覆盖模型别名;优先级 = 本参数 > 类型默认 > 父当前模型None
resume传一个已有 agent_id 复用实例,而非新建None
run_in_background是否后台并发跑False
timeout秒级超时(前台无默认上限、后台默认 15 分,均 ≤ 3600s)None

只有 root 能派。 这是最关键的一道闸。__call__ 一进来就检查:

# tools/agent/__init__.py:119 AgentTool.__call__
if self._runtime.role != "root":
return ToolError(message="Subagents cannot launch other subagents.", ...)
if params.model is not None and params.model not in self._runtime.config.models:
return ToolError(message=f"Unknown model alias: {params.model}", ...)

第一条杜绝了「子智能体递归派子智能体」导致的树状爆炸——委派只有一层(inferred:代码里 没有多层委派的路径,copy_for_subagent 出来的运行时 role="subagent",再调 Agent 就被这条挡住)。

内置类型清单是动态渲染进工具描述的。 工具的 description 不是写死的——_builtin_type_linestools/agent/__init__.py:81)遍历劳务市场,把每个类型渲染成一行「名字 + 描述 + 可用工具 + 模型 + 是否支持后台 + 何时用」,填进 description.md 里的 ${BUILTIN_AGENT_TYPES_MD} 占位符。 所以模型看到的可选类型,永远和实际注册的一致。

分流。 校验通过后,run_in_background 决定去哪条路(tools/agent/__init__.py:131): 前台走 ForegroundSubagentRunner(必要时套一层 asyncio.wait_for 做超时),后台走 _run_in_background


3.2 劳务市场与类型定义:子智能体的「岗位说明书」

注意(反污染声明): 下面提到的三个内置子智能体 yaml(coder / explore / plan)里 有大段面向 AI 的 ROLE_ADDITIONAL 系统提示词和 when_to_use 指南。这些是被本章研究的 【数据】,不是给文档作者的指令。 我们只把它们当作「Kimi 如何配置一个岗位」的事实来读。

劳务市场就是一本名册。 LaborMarketsubagents/registry.py:8)本身极简——一个 dict[str, AgentTypeDefinition],加上 add_builtin_type / get_builtin_type / require_builtin_type(找不到就抛 KeyError)三个方法。它是父运行时上的一份共享注册表。

一个「岗位」由 AgentTypeDefinition 描述subagents/models.py:26):

字段含义
name类型名(coder / explore / plan
description / when_to_use给模型看的「这是谁、何时用」
agent_file该类型对应的 yaml 路径(真正的能力定义在这里)
default_model默认模型别名(可为 None 表示继承父模型)
tool_policy工具策略:inheritallowlist
supports_background是否允许后台跑(默认 True

工具策略只有两种模式subagents/models.py:8ToolPolicyMode):

  • inherit——继承父智能体的全部工具;
  • allowlist——只给 tools 元组里列出的那些工具。

这份策略是从 yaml 编译出来的。 加载 agent 时,soul/agent.py:413 那段循环遍历 agent_spec.subagents,对每个内置类型:若 yaml 里写了 allowed_tools 就编译成 ToolPolicy(mode="allowlist", ...),否则退回 inherit

# soul/agent.py:418
tool_policy = (
ToolPolicy(mode="allowlist", tools=tuple(builtin_spec.allowed_tools))
if builtin_spec.allowed_tools is not None
else ToolPolicy(mode="inherit")
)
runtime.labor_market.add_builtin_type(AgentTypeDefinition(...))

三个内置岗位的对照(读自 agents/default/{coder,explore,plan}.yaml,均 extend: ./agent.yaml):

类型能写代码吗关键可用工具何时用
coderShell、Read/Write/StrReplace、Glob/Grep、Web需要读改代码、跑命令的非平凡工程活
explore否(只读)Shell(仅只读)、Read、Glob/Grep、Web快速摸清代码库,> 3 次搜索的调查
planRead、Glob/Grep、Web(无 Shell、无写出一份实现计划、识别关键文件、权衡取舍

三者都在 exclude_tools 里排掉了 AgentAskUserQuestionSetTodoListExitPlanMode / EnterPlanMode——排掉 Agent 正是「子不能派子」在配置层的呼应, 排掉 AskUserQuestion 则对应「子智能体不能直接问终端用户,只能在摘要里说清歧义」的角色设定。 exploreplan 还额外排掉了 WriteFile / StrReplaceFile,从工具层面锁死「只读」。


3.3 前台运行器:建 Soul、跑一步、把子事件冒泡给父

它要解决的小问题: 父智能体调一次工具,要在同一次调用内同步地跑完一个完整的子 智能体循环,并把子智能体过程中的 UI 事件(工具调用、审批请求)实时展示给用户看。

prepare_instance:新建还是复用

ForegroundSubagentRunner.runsubagents/runner.py:204)第一步是 _prepare_instance:357):

  • 若传了 resume,取出已有实例;若它还在 running_foreground/background拒绝并发复用(抛 RuntimeError)。
  • 否则生成一个新 agent_id(形如 a + 8 位 hex),在 SubagentStorecreate_instance 落盘。

建 Soul:两条路径共用的 prepare_soul

拿到实例后,run 组装一个 SubagentRunSpec,交给 prepare_soulsubagents/core.py:36)。 这个函数是前台和后台唯一共用的建 Soul 流水线,六步:

  1. SubagentBuilder.build_builtin_instance 按类型定义建出 Agent
  2. Context(store.context_path(...)) + restore() 恢复该实例的独立上下文;
  3. 系统提示词:复用(resume 时)或首次落盘;
  4. explore 专属——新建的 explore 子智能体会在 prompt 前注入 git 上下文core.py:74,调 collect_git_context),让它一上来就知道仓库状态;
  5. 把最终 prompt 快照写盘(调试用);
  6. KimiSoul(agent, context=context) 造出子 Soul,返回 (soul, prompt)

run_with_summary_continuation:过短摘要会被要求续写

子 Soul 跑完后,父拿到的是子的最后一条 assistant 文本。这里有个巧妙的质量闸门 (subagents/runner.py:142):

# subagents/runner.py:160 run_with_summary_continuation
final_response = soul.context.history[-1].extract_text(sep="\n")
remaining = SUMMARY_CONTINUATION_ATTEMPTS # = 1
while remaining > 0 and len(final_response) < SUMMARY_MIN_LENGTH: # 200 字符
remaining -= 1
failure = await run_soul_checked(soul, SUMMARY_CONTINUATION_PROMPT, ...)
...
final_response = soul.context.history[-1].extract_text(sep="\n")

如果子智能体的收尾摘要短于 200 字符SUMMARY_MIN_LENGTH),就再喂它一句 「你上一条太简略了,请补充技术细节/发现/父智能体需要知道的一切」,再跑一步(最多续写 1 次)。 这是为了防止子智能体草草交差——因为父只能看到这最后一段,它必须足够充实。

run_soul_checked:把各种失败归成一类

真正跑 Soul 的是 run_soul_checkedsubagents/runner.py:65)。它的价值在于把五花八门的 异常收敛成一个统一的 SoulRunFailure,而不是让异常炸穿到父工具调用:

遇到什么怎么处理
MaxStepsReached转成失败,提示「拆成更小的子任务」
APIStatusError / ChatProviderError转成失败,带上 HTTP 状态码 / 供应商错误
其他 Exception转成通用「Agent run error」
RunCancelled / CancelledError重新抛出(取消要一路传播,不能吞)
跑完但最后一条不是 assistant判为「无效结果」失败

前台 run 拿到 failure 时,会把实例状态置为 failed,并返回一个 ToolError 给父。

事件冒泡:_make_ui_loop_fn 把子 Wire 包成 SubagentEvent

这是前台运行器最精巧的一环。子智能体有自己的 Wire(见 智能体主循环), 但用户是在父智能体的界面上看进度。_make_ui_loop_fnsubagents/runner.py:393)搭了一座桥:

子 Soul ──emit──▶ 子 Wire.ui_side ──_ui_loop_fn 收到 msg──┐
│ 先写子输出文件

┌── 是审批/工具调用/提问类? ──▶ 原样转父 Wire
│ (这些要直接和用户交互)
└── 其他事件 ──▶ 包成 SubagentEvent ──▶ 父 Wire
(带 parent_tool_call_id / agent_id / subagent_type)
  • 审批请求、审批响应、工具调用请求、用户提问这几类(runner.py:411原样转发给父 Wire—— 因为它们需要直接和用户对话;
  • 其余普通事件被包进 SubagentEventwire/types.py:242),带上 parent_tool_call_idagent_idsubagent_type,再冒泡给父 Wire。父界面据此把子事件归到那次 Agent 调用名下渲染, 用户能看清「这是哪个子智能体在干什么」。
  • HookRequest 直接跳过(子的 hook 不再向父冒泡)。

生命周期钩子与审批隔离

前台 run 在跑之前触发 SubagentStart hook、跑之后 fire_and_forget_trigger 一个 SubagentStop hook(runner.py:271 / :301),把子智能体的启停接进 hook 体系。

同时,整个前台 run 绑定一个稳定的 ApprovalSourcerunner.py:259kind="foreground_turn",带独立 id)。它的作用是:一旦这次子智能体执行被取消, cancel_by_source精确地把属于这个子智能体的所有待批请求一次性取消掉,不误伤父或其它子。 finally 块里无论成败都会做这次清理。


3.4 子智能体如何复用运行时:copy_for_subagent

它要解决的小问题: 子智能体要能用父的模型账号、审批规则、劳务市场名册,但绝不能和父 共享「思路」。哪些共享、哪些独立,是安全与正确性的分界线。

答案是一次结构化的克隆soul/agent.py:339)。copy_for_subagent 造一个新 Runtime, 逐字段决定共享还是新建:

字段共享 or 独立为什么
config / oauth / session共享同一次会话、同一套账号
approval共享(.share()审批规则全局一致
labor_market共享子也能读同一份类型名册(但派活会被 role 挡住)
additional_dirs共享同一个 list 引用/add-dir 的改动要传播到所有智能体
subagent_store / approval_runtime / root_wire_hub共享审批与事件要能跨父子协调
background_taskscopy_for_role("subagent")换一个「子角色」的管理器(后台被禁,见 3.6)
denwa_renji独立新建 DenwaRenji()「子必须有自己的 DenwaRenji」——时间回溯机制不能共享
role置为 "subagent"这就是「子不能派子」和「后台仅 root」的总开关
subagent_id / subagent_type新设标记这是哪个子实例

代码里那句注释点破了最关键的一处:

# soul/agent.py:353
denwa_renji=DenwaRenji(), # subagent must have its own DenwaRenji

DenwaRenji(電話レンジ / 电话微波炉,Kimi 的 D-Mail 时间回溯装置,详见 巧妙之处)是每个 Soul 私有的状态——若父子共用,子的回溯会污染父。 所以它必须独立。

一句话总结这一节:子智能体 = 一份「换了 DenwaRenji、标了 subagent 角色」的父运行时克隆 + 一个全新的 Soul。 「共享运行时、独立 Soul」这条界线,到这里就完全落地了。


3.5 实例持久化:SubagentStore

它要解决的小问题: resume 一个子智能体、或事后查它干了什么,都需要子智能体的状态落盘

SubagentStoresubagents/store.py:64)把每个实例的一切放在 <session>/subagents/<agent_id>/ 下:

文件内容
context.jsonl该实例的对话历史检查点(见 上下文与记忆
wire.jsonl该实例的 Wire 事件流
meta.jsonAgentInstanceRecord(状态、类型、启动规格、时间戳)
prompt.txt最近一次的 prompt 快照
output摘要 / 阶段输出

实例状态(SubagentStatusmodels.py:9)在 idle / running_foreground / running_background / completed / failed / killed 间流转;update_instance 每次原子写回 meta.json。正因为有这份持久化,resume 才能恢复上下文继续干,meta.json 里的 running_* 状态也成了「拒绝并发复用」的判据。


3.6 后台任务:两种「后台」,一个状态机

它要解决的小问题: 让主智能体「派出去就先干别的」,任务在后台并发跑,完成时再通知回来。

关键区分:后台 bash vs 后台 agent,跑法不同

BackgroundTaskManagerbackground/manager.py:36)管两种 TaskKindmodels.py:8):

种类怎么跑在哪
bash另起一个 worker 子进程_launch_workersubprocess.Popenmanager.py:135 + worker.py:31
agent同进程内 asyncio.create_taskBackgroundAgentRunnermanager.py:262

这个差别很重要:后台 shell 命令是真·另一个操作系统进程(崩了也不拖垮主进程,靠心跳 heartbeat_at 判活);而后台子智能体是主进程里的一个异步任务——它要能访问共享的运行时、 审批、劳务市场,所以留在进程内更自然。本章聚焦后者。

后台子智能体的启动:_run_in_background

回到 Agent 工具的 _run_in_backgroundtools/agent/__init__.py:163),后台派活比前台多几步:

  1. 取当前 tool_call(后台任务要挂在这次工具调用上下文里);
  2. resume,校验目标实例没在跑;否则生成 agent_id、在 store 里 create_instance
  3. 先同步把实例标成 running_background__init__.py:223)——注释说明这是为了让并发的 resume 尝试立刻看到这道守卫,因为 asyncio.create_task 只是排队协程、不会马上执行;
  4. background_tasks.create_agent_taskmanager.py:209)真正建任务;若失败,回滚实例状态;
  5. 立刻返回一段结构化文本给父:task_idagent_idstatus,外加几条 next_step 提示 (用 TaskOutput 查快照、automatic_notification: true 表示完成会自动通知、 resume_hint 教它以后怎么续)。

create_agent_task 里那句 asyncio.create_task(BackgroundAgentRunner(...).run())manager.py:262)就是后台并发的引擎;任务被存进 _live_agent_tasks 字典,并挂一个 done_callback 保证无论怎么结束都会被清出字典(注释解释了为什么这个强引用必须留到取消传播完成)。

后台运行器:BackgroundAgentRunner

BackgroundAgentRunnerbackground/agent_runner.py:28)的 _run_core:134)复用了 和前台完全相同的 prepare_soul + run_with_summary_continuation——建 Soul、跑、续写摘要 的逻辑不重复。它和前台的差别只在结果去向和事件处理

  • 它的 _ui_loop_fnagent_runner.py:164只把子事件写进输出文件,不往父 Wire 冒泡 (因为父此刻并不在等它、界面上没有对应的实时展示位);
  • 跑完根据结果调 manager._mark_task_completed / _mark_task_failed,并把实例状态更新为 idle / failed
  • 它自己套一层 asyncio.wait_for 做超时(agent_runner.py:75),超时归类为 timeout 失败;
  • 它绑定 ApprovalSource(kind="background_agent", id=task_id),并订阅审批事件——这样后台任务 在等审批时能把任务状态切成 awaiting_approval,审批解决后再切回 running_on_approval_runtime_event:195)。

状态机与终态

后台任务状态(TaskStatusbackground/models.py:9):

created → starting → running ⇄ awaiting_approval

├──▶ completed ┐
├──▶ failed │ 终态
├──▶ killed │ (TERMINAL_TASK_STATUSES)
└──▶ lost ┘

lost 是个特别的终态:recover()manager.py:420)在发现一个 in-process 后台 agent 已经 不在 _live_agent_tasks 里(比如进程重启过)时,把它标成 lost,并把对应实例从 running_background 纠正为 failed——避免留下「永远在跑」的幽灵。

通知回填:结果怎么回到父

后台任务是并发的,父不会主动等。那结果怎么回去?靠通知 + 回填

  1. 任务进终态后,publish_terminal_notificationsmanager.py:487)按结果生成一条通知 (completed=success / timed_out=error / killed=warning …),发进 NotificationManager
  2. 主循环每一轮在派发通知前,会先调 background_tasks.reconcile 作为 before_claimsoul/kimisoul.py:1052)——reconcilemanager.py:483)= recover() + 发布终态通知;
  3. 于是父在下一轮循环就会把这条「后台任务完成」的通知,作为一条 user 消息回填进自己的 上下文,从而「知道」子智能体干完了、可以用 TaskOutput 取详情。

压缩时也不会丢。 当父上下文被压缩(见 上下文与记忆)时, kimisoul.py:1340 会调 build_active_task_snapshotbackground/summary.py:56)把仍活跃的 后台任务列表,作为一条带 <active-background-tasks> 标签的消息重新塞回压缩后的上下文—— 这样即便历史被裁剪,父也不会「忘记」自己还有活跃的后台工人。

面向父的三个工具

主智能体用这三个工具管后台任务(tools/background/__init__.py,都先 _ensure_root):

工具作用关键点
TaskList列出后台任务(默认只列活跃的)__init__.py:146
TaskOutput取某任务的状态/输出快照block=true 才阻塞等待,否则给非阻塞快照(:182
TaskStop停止某任务需审批;plan 模式下禁用(:276

4. 巧妙之处(可借鉴的技术)

  • 「共享运行时 + 独立 Soul」的克隆式隔离soul/agent.py:339)。用一次逐字段的 copy_for_subagent 精确划定共享/独立边界,比「整个重建一套运行时」轻、比「完全共享」安全。 最关键的一笔是独立 DenwaRenji——时间回溯这种 per-Soul 状态一旦共享就会串味。

  • 一份 prepare_soul,两条路径复用subagents/core.py:36)。前台运行器和后台运行器只在 「谁 await、事件是否冒泡、结果怎么回」上分叉,建 Soul 的重活完全共享,改一处两处都受益 (注释明确说 git 上下文注入这类增强「只需实现一次」)。

  • 过短摘要自动续写subagents/runner.py:160)。因为父只能看到子的最后一段,用一个 200 字符阈值 + 最多 1 次续写,低成本地拦住「草草交差」,保证委派的信息不缩水。

  • 子 Wire 事件选择性冒泡subagents/runner.py:411)。需要和用户交互的(审批/提问)原样直转, 其余包成 SubagentEvent 归到父的那次工具调用名下——用户既能看到子在干嘛,又不会被子的 内部噪音淹没。

  • 后台完成靠「通知 + reconcile 回填」而非阻塞轮询manager.py:483 + kimisoul.py:1052)。 父不用守着子,主循环每轮顺手 reconcile,把终态通知变成一条回填消息——并发与「父终会知道」 两者兼得。压缩时再用 build_active_task_snapshot 兜底,活跃任务不会被遗忘。

  • 状态先同步落、任务后异步起tools/agent/__init__.py:223)。后台派活时先把实例标 running_backgroundcreate_task,堵住「协程还没跑、并发 resume 就溜进来」的竞态。


5. 边界与局限(诚实)

  • 委派只有一层。 子智能体拿不到 Agent 工具(yaml exclude_tools + role != "root" 双保险), 所以不能再派子。需要多层拆分时,只能由 root 扁平地派多个子。

  • 后台任务仅 root、仅本地会话。 _ensure_rootmanager.py:85)和 _ensure_local_backend:89)会拒绝子智能体或非本地后端使用后台;并发数还受 max_running_tasks 限制(:227)。

  • 后台子智能体是 in-process 的。 它随主进程存亡;主进程退出/重启后,recover() 只能把它 标成 lost,无法真正续跑那次执行(不同于 bash 任务的独立 worker 进程)。

  • 父只能看到子的最后一条消息。 子的中间发现若没写进收尾摘要,父就永远看不到——这是隔离的 代价,也是为什么要有摘要续写机制。

  • 子智能体不能直接问用户。 角色设定要求它把歧义写进摘要交回父(AskUserQuestion 被排除), 所以「需要澄清」的任务在子层面会退化成「带着假设往下做 + 在摘要里说明」。


6. 代码地图(导航索引)

主题文件路径符号名
Agent 工具入口 / 参数 / 权限校验src/kimi_cli/tools/agent/__init__.pyAgentToolParams_builtin_type_lines_run_in_background
工具描述模板(内置类型占位符)src/kimi_cli/tools/agent/description.md${BUILTIN_AGENT_TYPES_MD}
劳务市场(类型注册表)src/kimi_cli/subagents/registry.pyLaborMarketrequire_builtin_type
类型定义 / 工具策略 / 状态src/kimi_cli/subagents/models.pyAgentTypeDefinitionToolPolicySubagentStatusAgentLaunchSpec
内置岗位 yaml(数据)src/kimi_cli/agents/default/{coder,explore,plan}.yamlallowed_toolsexclude_toolswhen_to_use
yaml → ToolPolicy 编译src/kimi_cli/soul/agent.py:413load_agentadd_builtin_type 循环)
前台运行器src/kimi_cli/subagents/runner.pyForegroundSubagentRunner_prepare_instance_make_ui_loop_fn
摘要续写 / 失败归类src/kimi_cli/subagents/runner.pyrun_with_summary_continuationrun_soul_checkedSUMMARY_MIN_LENGTH
建 Soul 流水线(共用)src/kimi_cli/subagents/core.pyprepare_soulSubagentRunSpec
建子 Agent 实例src/kimi_cli/subagents/builder.pySubagentBuilder.build_builtin_instance
运行时克隆src/kimi_cli/soul/agent.py:339Runtime.copy_for_subagent
实例持久化src/kimi_cli/subagents/store.pySubagentStorecreate_instanceupdate_instance
后台任务管理器 / 状态机src/kimi_cli/background/manager.pyBackgroundTaskManagercreate_agent_taskrecoverreconcilepublish_terminal_notifications
后台子智能体运行器src/kimi_cli/background/agent_runner.pyBackgroundAgentRunner_run_core_on_approval_runtime_event
后台 bash worker 进程src/kimi_cli/background/worker.pyrun_background_task_worker
活跃任务快照(压缩回填)src/kimi_cli/background/summary.pybuild_active_task_snapshot
面向父的后台工具src/kimi_cli/tools/background/__init__.pyTaskListTaskOutputTaskStop
通知回填接线src/kimi_cli/soul/kimisoul.py:1052deliver_pendingbefore_claim=reconcile)、build_active_task_snapshot