feat: 深挖子智能体执行核(issue #54) #58

Merged
Yushu merged 3 commits from feature/54-delegate-subagent-core into main 2026-07-24 06:15:41 +00:00
Member

Closes #54

这个 PR 做了什么

实现 issue #54「深挖子智能体执行核:隔离任务上下文 + 多轮工具循环」。这是深挖任务委托(issue #52 PRD)的后台执行体——本片只交付执行核本身,不接前台触发(issue #55delegate_task 工具)也不接完成后的送回(issue #53 的 Callback)。

隔离任务上下文(subagent.py

  • assemble_task_context:安全基线 + 任务执行指令 + 任务描述三段——不带 persona/画像/关系网/情感态/历史对话(ADR-0020"隔离任务上下文,不隔离人格")。无 IO 纯函数,同 drive_tick.py::assemble_light_context 既有测试模式
  • 安全基线仍然保留:子智能体会调用宿主注册的工具、处理工具返回的外部数据,面对 prompt injection 的风险不比前台反应式对话小

多轮工具调用循环

  • run_delegated_task:工具集恒为 get_tools("background") 返回的 host 工具(RuntimeContext.background 这个既有契约取值首次被真正接入调用点)——从不调用 build_tool_schemas(),子智能体的可见工具集结构性只包含 host 工具,不含任何 core 自己的输出类工具(send_message/self_recall/edit/delegate_task 本身)
  • 循环终止条件是子智能体自己给出最终文本(不再调用工具)
  • 新增独立静态硬顶 arise_delegate_max_rounds(不复用前台 arise_run_limit):超过即撤走工具集、追加一次无工具调用逼模型收尾——用它自己对已收集信息的综合当蒸馏结果,不是简单拼接原始 tool_response
  • 工具调用失败按工具港既有哲学处理:失败即一次普通 tool_response,不自动重试,不中断整个循环
  • llm_client.complete() 本身调用异常时 fail-closed 收尾(同 reflection.py/persona_drift_guard.py 既有先例),不让异常无声打断后台任务

消除重复(承接 issue #53 review 的同一条纪律)

  • llm.py/tool_port.py:把 tool-calling 协议序列化的小助手函数(assistant message / tool result / host tool schema)从 runtime_loop.py 私有作用域提升为公开函数,RuntimeLoop 与新的 subagent 执行核共用同一套
  • tool_port.py 新增 call_host_tool(调用 host 工具 + 错误包装),同样两处共用

测试

新增 3 个测试文件:纯函数(assemble_task_context 结构断言)、ScriptedLLMClient 驱动的多轮循环(单轮/多轮工具调用、失败工具、未知工具名、超硬顶强制收尾、LLM 调用异常 fail-closed、只提供 background 工具不含核心输出工具)。

全部 822 个测试通过,ruff check/ruff format 干净。

Code review

跑过 /code-review(Standards + Spec 两轴并行子智能体):

  • Spec 轴:8 条 AC 全部落实,未发现越界,未发现遗漏
  • Standards 轴发现两处问题,均已修复:① run_delegated_task 的 LLM 调用缺 fail-closed 处理(不同于 reflection.py/persona_drift_guard.py 同类先例);② RuntimeLoop._handle_host_tool 与 subagent 循环内的错误包装逻辑重复,抽出 call_host_tool 共用;③(次要)导入别名 import X as _y 掩盖了函数已跨模块公用的事实,改为直接用公开名
Closes #54 ## 这个 PR 做了什么 实现 issue #54「深挖子智能体执行核:隔离任务上下文 + 多轮工具循环」。这是深挖任务委托(issue #52 PRD)的后台执行体——本片只交付执行核本身,不接前台触发(issue #55 的 `delegate_task` 工具)也不接完成后的送回(issue #53 的 Callback)。 ### 隔离任务上下文(`subagent.py`) - `assemble_task_context`:安全基线 + 任务执行指令 + 任务描述三段——不带 persona/画像/关系网/情感态/历史对话(ADR-0020"隔离任务上下文,不隔离人格")。无 IO 纯函数,同 `drive_tick.py::assemble_light_context` 既有测试模式 - 安全基线仍然保留:子智能体会调用宿主注册的工具、处理工具返回的外部数据,面对 prompt injection 的风险不比前台反应式对话小 ### 多轮工具调用循环 - `run_delegated_task`:工具集恒为 `get_tools("background")` 返回的 host 工具(`RuntimeContext.background` 这个既有契约取值首次被真正接入调用点)——从不调用 `build_tool_schemas()`,子智能体的可见工具集结构性只包含 host 工具,不含任何 core 自己的输出类工具(`send_message`/`self_recall`/`edit`/`delegate_task` 本身) - 循环终止条件是子智能体自己给出最终文本(不再调用工具) - 新增独立静态硬顶 `arise_delegate_max_rounds`(不复用前台 `arise_run_limit`):超过即撤走工具集、追加一次无工具调用逼模型收尾——用它自己对已收集信息的综合当蒸馏结果,不是简单拼接原始 tool_response - 工具调用失败按工具港既有哲学处理:失败即一次普通 tool_response,不自动重试,不中断整个循环 - `llm_client.complete()` 本身调用异常时 fail-closed 收尾(同 `reflection.py`/`persona_drift_guard.py` 既有先例),不让异常无声打断后台任务 ### 消除重复(承接 issue #53 review 的同一条纪律) - `llm.py`/`tool_port.py`:把 tool-calling 协议序列化的小助手函数(assistant message / tool result / host tool schema)从 `runtime_loop.py` 私有作用域提升为公开函数,`RuntimeLoop` 与新的 subagent 执行核共用同一套 - `tool_port.py` 新增 `call_host_tool`(调用 host 工具 + 错误包装),同样两处共用 ### 测试 新增 3 个测试文件:纯函数(`assemble_task_context` 结构断言)、`ScriptedLLMClient` 驱动的多轮循环(单轮/多轮工具调用、失败工具、未知工具名、超硬顶强制收尾、LLM 调用异常 fail-closed、只提供 background 工具不含核心输出工具)。 全部 822 个测试通过,`ruff check`/`ruff format` 干净。 ### Code review 跑过 `/code-review`(Standards + Spec 两轴并行子智能体): - Spec 轴:8 条 AC 全部落实,未发现越界,未发现遗漏 - Standards 轴发现两处问题,均已修复:① `run_delegated_task` 的 LLM 调用缺 fail-closed 处理(不同于 `reflection.py`/`persona_drift_guard.py` 同类先例);② `RuntimeLoop._handle_host_tool` 与 subagent 循环内的错误包装逻辑重复,抽出 `call_host_tool` 共用;③(次要)导入别名 `import X as _y` 掩盖了函数已跨模块公用的事实,改为直接用公开名
实现 issue #54「深挖子智能体执行核:隔离任务上下文 + 多轮工具循环」——
深挖任务委托(issue #52 PRD)后台执行体,本片只交付执行核本身,不接
前台触发(issue #55 delegate_task 工具)也不接完成后送回(issue #53
Callback)。

- 新增 subagent.py:assemble_task_context 纯函数(安全基线+任务指令+
  任务描述三段,不带 persona/画像/关系网/情感态/历史)、
  run_delegated_task 驱动独立多轮工具循环——工具集恒为
  get_tools("background") 返回的 host 工具(RuntimeContext.background
  首次真正接入调用点),结构性不含任何 core 输出类工具;工具调用失败
  按工具港既有哲学处理(失败即普通 tool_response,不重试,不中断);
  超过 arise_delegate_max_rounds 静态硬顶即撤走工具集强制收尾一次,
  拿已收集信息的综合当蒸馏结果
- config.py:新增 arise_delegate_max_rounds(不复用前台 arise_run_limit)
- llm.py/tool_port.py:把 tool-calling 协议序列化的三个小助手函数
  (assistant message / tool result / host tool schema)从 runtime_loop.py
  私有作用域提升为公开函数,供 RuntimeLoop 与新的 subagent 执行核共用,
  避免重复实现同一套协议转换(承接 issue #53 code review 里"消除重复"
  的同一条纪律)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- subagent.py:run_delegated_task 的两处 llm_client.complete() 调用补上
  fail-closed 处理(同 reflection.reflect_on_attempt/
  persona_drift_guard.judge_persona_drift 既有先例——LLM 调用异常不该让
  后台任务无声崩溃),DelegatedTaskResult 新增 failed 字段
- tool_port.py:新增 call_host_tool(调用+错误包装),RuntimeLoop 与
  subagent 共用同一套失败处理,消除两处几乎逐字重复的 try/except
- runtime_loop.py:去掉 `import X as _y` 别名写法(render_tool_result/
  render_assistant_message/render_tool_schema 直接用真实公开名),让
  "这几个函数已经是跨模块公用 API"这件事在代码里可见,不藏在导入别名里

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Yushu merged commit 6382e71367 into main 2026-07-24 06:15:41 +00:00
Yushu deleted branch feature/54-delegate-subagent-core 2026-07-24 06:15:42 +00:00
Sign in to join this conversation.
No description provided.