Skip to content

Architecture: 抽取 Delegated Agent Run 运行时构造,补全 delegated-* 模块族 #201

Description

@suntianc

来源:#199 架构审查 grilling 后缓抽的独立加深项(2026-07-20)。本 issue 承接该共识,独立于 #199 推进。
依赖:建议在 #199(装配收拢)落地后再做。

背景

src/main/deepagent/runtime.tsbuildDeepAgentRuntime(约 line 633)内有一个 ~120 行的 delegatedRuntimeAdapter.run 闭包,它是 ADR-0061「每个 Delegated Agent Run 拥有隔离运行时」的实现本体——子 model / child graph / MemorySaver / 工具 scope 收窄 / 审批门控全在其中。

deepagent 已形成 8 个聚焦的 delegated-* 模块(configuration-snapshot / run-coordinator / run-repository / model-selection / run-failure / subagent-adapter / tool-action-repository / tool-approval-scheduler),此闭包是唯一仍陷在装配 god-file 里的委派逻辑

为什么从 #199 缓抽(而非遗漏)

它是审批/工具门控的业务最高风险路径,承载:

  • ADR-0061 隔离运行时
  • ADR-0062 工具 scope 只收窄不扩大
  • ADR-0063 审批模式继承自父运行
  • Delegated Failure Isolation(失败隔离)

把它 relocation 捆进 #199 那种「行为保持」的结构重构,会使任何审批回归无法归因(结构改动 vs 逻辑改动)。它是可干净分离的独立加深,应单独做、先补测试再动结构。

方案

把闭包抽成显式工厂,补全 delegated-* 族:

createDelegatedRuntimeAdapter(parentContext): DelegatedRuntimeAdapter
  • DelegatedRuntimeAdapter 接口已存在delegated-agent-run-coordinator.ts:69,单方法 run(request)),seam 非 hypothetical——coordinator 已依赖它。
  • 抽出后获得专属测试面:可单测「配置快照 + 父上下文 → 隔离运行时」,不必整体起一个 root run。

关键设计输入(动结构前需先解决)

  1. 父→子继承契约要收窄成刻意设计的窄接口。闭包当前隐式捕获约 10 项父级上下文:currentApprovalModebuiltInToolNamesallMcpServersmcpServersskillSnapshotproviderprojectoverridessessionIddelegatedRunCoordinator。需甄别哪些是子运行真正需要继承的(对应 CONTEXT.md 的 Delegated Permission Context / Agent Tool Scope / Conversation Skill Snapshot 传播),哪些是偶然捕获,收进一个内聚的 parentContext,避免做成「把父运行内部全漏给委派模块」的浅 seam。
  2. 解开 coordinator ↔ adapter 循环:当前 adapter(633) 在闭包体内引用 coordinator(760),coordinator(760) 又持有 adapter,靠闭包延迟绑定。抽成独立模块后需改为 coordinator 以参数/thunk 注入。

前置 / 验收

  • Architecture review 01 — Collapse the Agent Run assembly god-file #199 装配收拢已落地、测试全绿。
  • 先为审批继承(ADR-0063)、工具 scope 收窄(ADR-0062)、失败隔离补专属单测
  • 抽出后行为零变更,现有集成测试(parallel-delegated-runtime.deepagents.integration.test.tsdelegated-agent-run.deepagents-contract.integration.test.ts)全绿。

不做(domain-modeling 纪律)

  • 不更新 CONTEXT.md:用到的 Delegated Agent Run / Agent Tool Scope / Delegated Permission Context 均为既有领域词。
  • 不立 ADR:这是排序决策的承接项,非「拒绝且防重提」。

Spec(/to-spec 调研补充,2026-07-26)

调研结论:问题仍然存在,且前置已满足——#199 装配收拢已合入(装配模块已抽出),但 delegatedRuntimeAdapter 闭包原封未动仍在 runtime god-file 内(约 130 行),是 issue 预判的"唯一仍陷在装配文件里的委派逻辑"。
前置验收项的现状盘点:

  • Architecture review 01 — Collapse the Agent Run assembly god-file #199 落地 ✅;两个集成测试(parallel-delegated-runtime、delegated-agent-run contract)均存在。
  • 纯函数层已有专属单测 ✅:工具 scope 收窄(agent-tool-scope)、审批中断解析(shared-infra 的 interrupt 解析)、模型选择(delegated-model-selection)、配置快照、coordinator、审批调度器。
  • 缺口:闭包的组合行为(配置快照 + 父上下文 → 隔离运行时)没有任何单元级测试面,只能靠整跑集成测试覆盖;失败分类模块(delegated-run-failure)无专属测试。这正是本票要创造的测试面。
  • 循环确认:闭包体内引用 coordinator(审批中间件构造处),coordinator 又持有 adapter,靠闭包延迟绑定——与 issue 描述一致。
  • 闭包实际隐式捕获的父级上下文核对为:审批模式、父内建工具名集、父 MCP server 集与全量 MCP 目录、Conversation Skill Snapshot、provider、模型 overrides(含 allowedTools)、Project 与 Agent 文件根、Conversation 标识、coordinator(循环项)。与 issue 所列一致。

Problem Statement

Delegated Agent Run 的隔离运行时构造——业务上审批/工具门控风险最高的路径——是装配文件里一个约 130 行的匿名闭包。维护者无法单独测试"给定配置快照与父上下文,子运行时是否正确隔离、正确收窄、正确继承审批模式";任何审批回归都无法归因到结构改动还是逻辑改动;父→子的继承契约是隐式的闭包捕获,谁也说不清哪些捕获是刻意设计、哪些是偶然。

Solution

把闭包抽成显式工厂,补全 delegated-* 模块族:工厂接收一个刻意设计的窄父上下文接口,返回 coordinator 已依赖的既有 DelegatedRuntimeAdapter(单方法 run(request))。父→子继承项从隐式捕获升级为显式契约字段;coordinator ↔ adapter 的闭包延迟绑定改为显式注入(审批门控能力以延迟提供者注入 adapter,而非让 adapter 持有整个 coordinator)。行为零变更;装配文件只剩组合调用。

User Stories

  1. 作为 CDF 维护者,我想让"配置快照 + 父上下文 → 隔离运行时"可以脱离完整 root run 单独测试,以便审批/门控回归能在单元层被拦住。
  2. 作为 CDF 维护者,我想让父→子继承契约成为一个显式的窄接口,以便评审时能看出每个继承项对应哪条领域规则(Delegated Permission Context / Agent Tool Scope / Conversation Skill Snapshot 传播),而不是从闭包捕获里逆推。
  3. 作为 CDF 用户,每个 Delegated Agent Run 继续拥有隔离运行时:子模型、子图、内存 checkpointer、受限文件后端各自新建,父运行状态不被子运行污染(ADR-0061 行为不变)。
  4. 作为 CDF 用户,Delegated 工具 scope 继续只收窄不扩大:子运行可用工具永远是父可用集合经目标 Agent 配置与快照排除后的子集(ADR-0062 行为不变)。
  5. 作为 CDF 用户,Delegated 运行继续继承父 Conversation 的审批模式:父运行需要审批的工具类别,子运行同样进审批门控,不多不少(ADR-0063 行为不变)。
  6. 作为 CDF 用户,单个子运行失败继续被隔离为结构化失败结果,不拖垮父运行与并行批次中的其他子运行(Delegated Failure Isolation 行为不变)。
  7. 作为 CDF 用户,子运行的 Skill 预载继续只能从 Conversation Skill Snapshot 内选择,快照冻结语义不变。
  8. 作为 CDF 用户,单发与并行委派继续共享同一 coordinator 的并发窗口、持久身份与运行时工厂,进度与审批投影行为不变。
  9. 作为 CDF 维护者,我想让审批回归可归因:结构迁移提交不含逻辑改动,逻辑改动(若未来有)发生在有专属测试面的模块里。
  10. 作为 CDF 维护者,我想消除 coordinator ↔ adapter 的隐式循环,让依赖方向可从构造签名读出。
  11. 作为测试作者,我想用假的图执行器与假的 MCP 装载在 adapter 接缝上断言子运行时的组成(工具集合、门控集合、系统提示、模型选择、checkpointer 隔离),不真正调用模型。
  12. 作为 CDF 维护者,我想为失败分类补上专属单测,明确各类子运行失败(无效结构化输出、中断、超限、异常)映射到的结构化结果。

Implementation Decisions

  • 既有 seam 复用:不新增接口形状——工厂返回 coordinator 已依赖的 DelegatedRuntimeAdapter(单方法 run(request)),DelegatedRuntimeRequest 契约不变。全库新增 seam 数为一(工厂本身)。
  • 窄父上下文接口:显式字段限定为调研核对出的继承项——审批模式(ADR-0063)、父内建工具名集与父 MCP 标识(ADR-0062 收窄基线)、全量 MCP 目录(供快照排除后的子候选集)、Conversation Skill Snapshot、provider 标识与模型 overrides(委派模型选择输入)、Project 根与 Agent 文件根(文件系统限域)、Conversation 标识。不得把父运行其余内部状态漏进该接口。
  • 解循环:adapter 需要的审批门控能力(为门控工具构造审批中间件)以延迟提供者/回调注入,coordinator 构造后完成绑定;adapter 不再引用 coordinator 整体。
  • 模块归属:新工厂加入 delegated-* 模块族,与配置快照、run coordinator、模型选择、失败分类、审批调度器并列;装配文件(runtime)只做组合:构造父上下文 → 建工厂 → 建 coordinator → 注入门控提供者。
  • 可注入执行依赖:图构建(deepagents createDeepAgent)与 MCP 工具装载在工厂依赖中可注入,生产默认用真实实现——这是获得单元测试面的前提,不改变生产行为。
  • 行为零变更:子运行时组成、审批继承、scope 收窄、失败隔离、结构化结果解析、进度回调、并发窗口全部逐行为保持;本票不顺手修任何逻辑。
  • 提交纪律:先补测试(见下),后做纯结构迁移,两个提交独立可回滚。

Testing Decisions

  • 好的测试只验证外部行为:给定配置快照 + 父上下文 + 假执行依赖,断言子运行时的可观察组成与结果——子工具集合是父集合的收窄结果、门控集合等于按父审批模式解析的集合、Skill 预载 ⊆ 快照、模型选择输出、无效结构化输出降级为截断摘要成功结果、中断出现时报审批不可用错误;不断言闭包内部结构或调用次序。
  • 结构迁移前先在现有整体(经 coordinator + 假 adapter 或集成测试)补齐三条领域规则的行为锚点:审批继承(ADR-0063)、scope 只收窄(ADR-0062)、失败隔离——迁移后同样的断言在工厂接缝上重写为单元测试。
  • 为失败分类模块补专属单测(各失败形态 → 结构化结果字段),填补现有空白。
  • 先例:coordinator 测试的假 adapter 注入写法、delegated-model-selection 的纯函数测试写法、两个 deepagents 集成测试(迁移后必须全绿,作为零行为变更的最终判据)。
  • 验证:pnpm test 覆盖 deepagent 全簇;重点回归 parallel-delegated-runtime 与 delegated-agent-run contract 两个集成测试。

Out of Scope

Further Notes

Metadata

Metadata

Assignees

No one assigned

    Labels

    architecture架构 deepening 候选(来自架构评审)ready-for-agentFully specified, ready for an AFK agent

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions