USER:

读下:https://blog.csdn.net/Code_LT/article/details/163849301?spm=1011.2415.3001.5331 (下文中的gpt学习.md)
并研究deepseek harness的subAgent机制。
Codex:
一、整体架构

SubAgent 是一个完整的 capability seam:

model-facing tool
        |
        v
ctx.subagents
        |
        +-- spawn
        +-- fork
        +-- acp
        +-- codex
        +-- claude-code
        +-- dsh-sdk

核心服务见 subagent/src/index.ts,公共类型见 types.ts

它不是 Agent Loop 的内部特殊分支,而是:

  • Service Definition:ctx.subagents
  • Provider:不同运行方式
  • Consumer:subagentsend_messageinterrupt_agentlist_agentsreport

多个 provider 可以同时注册,模型通过不同的工具名使用它们;模型不会直接选择 provider。

二、One-shot:一次性委派

调用:

ctx.subagents.start(provider, request)

返回:

{
  id,
  localAgent,
  result,
  dispose
}

它的语义是:

start
  -> 创建一个 child
  -> 执行一次 prompt/turn
  -> 返回一个 result
  -> 调用方 dispose

SubagentRun 的定义在 types.ts

重要边界:

  • provider 在 start() 完成前拥有创建资源;
  • start() 成功后,资源所有权转移给调用方;
  • child-level 模型失败通常不会让 result reject,而是返回 stopReason: 'error'
  • 调用方必须调用 dispose()
  • 一次性运行没有 follow-up、steering 或 cold resume。

模型层面的 backgroundMode: one-shot 仍然可以后台运行,但它使用普通 ctx.jobs,不是 continuable Activation。

三、Continuable:持久化子 Agent

调用:

ctx.subagents.startContinuable(...)

返回的不是 SubagentRun,而是:

{
  childId,
  messageId
}

返回时只保证:

初始消息已经被 child inbox 接受。

它不等待模型请求开始,也不等待消息进入 Session Log。实现见 continuation.ts

模型可以把它理解成:

一个持久化 Child Session
        |
        +-- 当前最多一个 Activation
                |
                +-- 一个 Agent
                +-- 一个 FIFO inbox
                +-- 若干 owned child Activations

Activation 不是一次请求,也不是结果对象。它只是:

当前 child Agent 被加载在本进程中的驻留周期。

因此:

  • 一个 Activation 可以执行多轮;
  • child 空闲后 Activation 可以被释放;
  • Session 仍然保留;
  • 后续 send_message 可以重新 cold resume;
  • 同一个 childId 跨多个 Activation 保持不变。

这也是当前实现最关键的设计。

四、Follow-up 路由

followup() 根据 child 当前是否驻留来决定路径:

child 有 Activation 且正在运行
    -> 进入同一个 Agent inbox

child 有 Activation 但 waiting
    -> 唤醒同一个 Agent

child 没有 Activation
    -> 从持久化 Session cold resume
    -> 恢复 descriptor / persona / toolFilter / model
    -> 投递下一轮

实现见 continuation.tscontinuation.ts

所有 follow-up 都是后续 FIFO turn:

  • 不会加入当前正在执行的 turn;
  • 不会修改已经 claim 的消息;
  • 不会重新调用 provider;
  • provider 只参与第一次 continuable creation 的准备。

五、Provider 实际拥有的东西很少

continuable provider 只需要实现:

prepareContinuable(request): Promise<ContinuableCreateSpec>

返回值当前主要是:

{
  seed?: SessionEvent[]
}

它不返回:

  • Agent
  • AgentHandle
  • result
  • dispose
  • resume 方法
  • prompt 投递器

这些全部由 continuation manager 管理。类型定义在 types.ts

因此,历史上“provider 负责完整子 Agent 生命周期”的理解已经不准确。现在更准确的说法是:

Provider 负责传输和初始创建差异;continuation manager 负责持久化 child 的完整生命周期。

六、Cold Resume 的真正依据:Descriptor

每个 session-backed child 都会写入一个模型不可见的:

subagent/descriptor

当前版本:

SUBAGENT_DESCRIPTOR_VERSION = 2

descriptor.ts

continuable descriptor 保存:

  • provider
  • label
  • child model provider
  • child model
  • persona
  • toolFilter
  • mode

它不保存:

  • subagentDepth
  • outputSchema
  • 单次 Activation 的 maxTokens
  • report 工具本身

恢复过程是:

SessionPersistence.inspect(childId)
        |
校验 parentSession
        |
fold subagent/descriptor
        |
ctx.agents.resume()
        |
applyChildComposition()
        |
followup()

fork child 还要使用 seedLength,只从 child 自己的 suffix 中寻找 descriptor,避免把祖先 Session 的 descriptor 当成自己的。

这体现了一个很强的原则:

持久化 child 的身份和恢复所需组合必须从 child 自己的日志重建,不能依赖父级当前配置。

七、权限和深度继承

进程内 child 会通过 child-agent.ts 统一组合:

  1. 继承父级 preset;
  2. 添加 subagent:delegation 作用域声明;
  3. 应用 child persona;
  4. 应用 toolFilter。

权限不是简单复制父级当前状态,而是在委派边界捕获:

captureDelegatedPolicyOverrides(parent)

行为:

  • 父级显式 sandbox override 会被写入 child log;
  • approval capability 存在时,child approval 固定为 'never'
  • 父级之后改变 sandbox,不会追溯影响已经创建的 child;
  • cold resume 读取 child 自己已经持久化的策略。

深度则是绝对深度:

childDepth = parentDepth + 1

权威值是:

SessionHeader.delegationDepth

所以 child 恢复后不会被错误当成 top-level agent。

八、Report 和 Settlement Notice 是两条不同通道

显式 report

child -> direct parent

child 自己决定是否发送、发送几次、发送什么内容。

有两种模式:

  • quiet:注入 parent,不启动 parent turn;
  • wakeup:进入 parent 的下一轮。

实现见 tool-subagent-report/src/index.ts

它有严格限制:

  • 只在 continuable child scope 中注册;
  • child 不能指定 recipient;
  • recipient 从 durable parentSession 推导;
  • 不能伪造 sender;
  • nested child 只向 direct parent 上报一层;
  • report 不会结束 child turn;
  • report 不是 final result。

另一条是 runtime 自动产生的 settlement notice:

continuation manager -> direct parent

每个已经成功返回 childId 的 child,在 Activation 结束时都会收到一条通知,包含:

  • stop reason;
  • child 最终 assistant 内容;
  • 是否完成、被中断、失败或达到 token 上限。

它的来源单独标记为:

{ kind: 'subagent-settled' }

避免把 runtime 写的总结误认为 child 自己说的话。实现见 continuation.ts

这两者必须分开:

report          = child 主动选择的内容
settlement      = runtime 对 child 结局的事实通知

九、Interrupt 不是 Dispose

interrupt_agent 只中断当前 turn:

Agent.cancel(cause, { keepInbox: true })

它不会:

  • 销毁 Activation;
  • 删除 child;
  • 丢弃未 claim 的 follow-up;
  • 影响已经发布的 descendants。

被中断的当前 turn 不会自动重排;未 claim 的 follow-up 会保留,等待下一次 waking send_message

权限上:

  • direct parent 可以中断 child;
  • live ancestor 可以中断 descendant;
  • sibling、陌生 Agent、self 会被拒绝;
  • stale Agent 对象也会被拒绝;
  • 对不存在、one-shot、自然完成的 child 是 accepted no-op。

相关测试集中在 tool-subagent-control.spec.ts

十、Provider 差异

Provider 运行方式 继承父级 completed history continuable
spawn 同进程新 Agent 支持
fork 同进程,带 completed prefix seam 支持,但 shipped 配置不用
acp 子进程 ACP 不支持
codex Codex app-server one-shot
claude-code Claude Agent SDK/CLI one-shot
dsh-sdk 另一个 DSH 进程 one-shot

spawnfork 支持:

outputSchema
depthLimit
toolFilter
persona

远程 provider 都是:

NO_START_CAPABILITIES
inheritsParentContext = false

所以远程 provider 不能由父级强制:

  • output schema;
  • maxDepth;
  • persona;
  • toolFilter。

必须设置:

maxDepth: provider-managed

十一、Fork 为什么生产配置固定 one-shot

fork 的核心价值是复用父级 completed history 的 token/KV prefix。

但 continuable child 会额外拥有:

  • child-local report tool schema;
  • tool:report prompt section。

这些内容位于继承历史之前,会破坏 prefix reuse。因此示例和 bundle 都配置为:

provider: fork
backgroundMode: one-shot

具体配置见 examples/acp-agent/cordis.ymlpackages/bundle/base/cordis.patch.yml

所以不是 fork 技术上不能 continuable,而是:

当前 report 作用域设计会破坏 fork 最重要的 prefix reuse 优势。

十二、对 gpt学习.md 历史结论的修正

历史内容中这些判断仍然成立:

  • SubAgent 是 capability seam;
  • provider abstraction 很重要;
  • Session Log 是事实账本;
  • model-visible 内容必须 logged;
  • Claude Code/Codex 可以作为外部 provider;
  • Agent 生命周期应该通过扩展点组织。

但需要修正四点:

  1. SubAgent 不只是“一次调用 + 一个结果”。
    Continuable child 是持久化会话,不返回 SubagentRun.result

  2. Remote provider 不是可恢复 child。
    ACP、Codex、Claude Code、DSH SDK 当前都只能做 one-shot。

  3. Report 不是 final result。
    它是 child 主动选择的回传消息;最终结局由 settlement notice 独立表达。

  4. Child 不会自动继承父级全部上下文。
    fork 只继承 completed history;工具、preset、persona、sandbox、approval 都需要独立组合和持久化。

十三、对你自己的 Agent/Memory 系统最值得借鉴的点

我认为最值得迁移的不是“做一个 SubAgent API”,而是这四个设计:

  1. 把短任务和持久会话分成两种对象。
    不要让一个 Task 同时承担后台任务、可恢复会话和多轮 Agent。

  2. 把“驻留状态”与“持久身份”分开。
    Activation 可以消失,但 child Session 和 child id 不变。

  3. 把恢复所需配置写进 child 自己的日志。
    不要 cold resume 时重新读取父级当前配置。

  4. 把 child 输出、runtime 结局、parent 控制分成独立消息来源。
    这样 transcript 才不会把系统生成的内容错误归因给模型。

验证状态

我审阅了 continuation、inheritance、report、control、list-agents 相关 focused tests,测试覆盖非常完整,尤其覆盖:

  • inbox acceptance;
  • cold resume;
  • FIFO;
  • depth;
  • policy snapshot;
  • report delivery;
  • settlement notice;
  • interrupt authorization;
  • child-first disposal;
  • descriptor corruption;
  • list diagnostics。

但本轮没有取得实际 pass/fail 运行结果:当前 workspace 的 pnpm 链接不完整,根目录缺少可用 .bin 和常规依赖链接,直接运行 focused Vitest 会卡在依赖解析/安装阶段。因此上面的结论以源码、配置和测试断言为依据,没有把未完成的测试运行误报成通过。


USER:

读下 https://blog.csdn.net/Code_LT/article/details/163879232?spm=1011.2415.3001.5331(下文中的subAgent学习.md),你看看里面的内容是否正确,帮我总结

Codex:

我读完了 subAgent学习.md。总体判断:主线基本正确,核心心智模型已经接近当前源码;大约 85%~90% 的内容可以保留。 主要问题不是方向错误,而是少数地方把实现的保障说得过于绝对。

核心总结

DeepSeek Harness 的 SubAgent 不是单纯的“调用一个子 Agent 的工具”,而是由以下部分组成的运行时:

Provider Registry
+ Child Session
+ Activation Manager
+ Agent Inbox
+ Parent/Child Control
+ Report Channel
+ Lifecycle Events

最重要的区分是:

one-shot
= 一次性委派,有 result,结束后 dispose,没有 follow-up/resume

continuable
= 有 durable child Session 的多轮子 Agent,会通过 Inbox 接收后续消息

Continuable 子 Agent 的实际模型是:

Durable Session
    -> 可选的 process-local Activation
        -> AgentHandle
            -> Agent
                -> Inbox

其中:

  • Session 保存身份、历史、descriptor、policy 和 Inbox 事件。
  • Activation 表示当前是否加载在进程内。
  • Agent 负责真正消费 Inbox、执行模型和工具。
  • AgentHandle 表达生命周期所有权。
  • Inbox 是 Agent 唯一的 FIFO 消息队列。
  • Provider 在 continuable 模式下只提供创建所需的 detached seed,生命周期由 continuation manager 管理。

因此:

Agent identity != Agent residency

释放 Activation 不会删除 Child Session;以后可以通过 cold resume 创建新的 Activation。

稿件中基本正确的部分

  1. ctx.subagents 已经不只是 provider registry,也承担 continuable child 的编排。
  2. one-shot 和 continuable 是两种不同生命周期。
  3. send_message 是加入下一轮 FIFO 消息,不是实时 steering。
  4. interrupt_agent 只取消当前执行,保留未消费 Inbox。
  5. report 是 Child 主动发送的消息,不等于完成,也不等于 one-shot result。
  6. settlement notice 和 report 使用不同 provenance,语义区分正确。
  7. spawnfork、Claude Code、Codex、ACP、DSH SDK 的 provider 分层理解基本正确。
  8. fork 只继承已完成的 conversation prefix,不等于继承权限、工具或整个 Parent scope。
  9. delegation policy 在创建时捕获并写入 Child Session,cold resume 不重新读取 Parent 当前 policy。
  10. Session + Inbox + Activation 类似 Event Sourcing、Actor 和进程驻留模型,这个类比很有帮助。

需要修正的重点

  1. startContinuable() 的 descriptor 时序写错了。

稿件多处写成:

reserve childId
-> 写入 descriptor
-> prepareContinuable()

实际是:

reserve childId
-> 在内存中 snapshot descriptor
-> capture delegated policy
-> await provider.prepareContinuable()
-> 把 seed + descriptor 组成 creation seed
-> ctx.agents.create()
-> 再进入 Child Session

也就是说,descriptor 在 prepareContinuable() 前只是被构造和校验,并没有已经持久化到 Child Session。源码见 continuation.ts:403descriptor-seed.ts:23

  1. Inbox 的崩溃恢复能力被写得过强。

正确的是:

已经写入 Session Log 的 Inbox mutation
可以在 resume 时重建

但不是:

只要 followup 被接受,就一定不会丢

测试明确覆盖了“accepted but unlogged message 没有自动 replay”的情况:如果消息已被内存接受,但进程在它进入 Session Log 前退出,恢复时可能不存在。见 continuation.spec.ts:1062inbox.ts:25

  1. Settlement notice 不是“这一轮执行结束”。

一个 Activation 可以执行多轮 FIFO turn。Settlement notice 表示:

当前 Activation / residency epoch 结束

而不是每一轮结束都会发送。建议把:

Child #123 这一轮执行结束了

改成:

Child #123 的当前 Activation 已结束
  1. Parent 不会自动收到完整 transcript,但不是完全没有自动输出。

Child 的完整工具历史、reasoning、中间过程不会自动复制给 Parent;但 Activation 结束时,runtime 会自动发送 settlement notice,其中可以包含最终 assistant 内容。

更准确的说法是:

不会自动复制完整 transcript、tool output 和 reasoning;
但 runtime 会自动发送一条结算通知。
  1. SettlementWatcher 更准确地说是“状态推导器”,不是独立状态机。

稿件把它称为“小型状态机”作为类比没问题,但实现并没有维护一套独立状态,而是根据:

Agent.status
accepted.size
ownedChildren.size

动态推导:

running
waiting
settled

源码见 continuation.ts:850

  1. Cold resume 后模型看到的是派生 history,不是原始完整 Event Log。

稿件写:

完整有效 Session History + 新消息

容易误解。更准确的是:

从 Session Log 派生出的 model-visible history
+ 新的 user message

descriptor、policy、Inbox mutation 等事件可能只服务 runtime,不进入模型上下文;compaction 也可能改变模型实际看到的历史。

  1. childId 一开始只是预留身份,不代表 Child 已经成功建立。

如果 materialization 或初始 Inbox admission 在成功返回前失败,整个创建会回滚,调用方不会得到有效的 Child。只有 startContinuable() 成功返回 { childId, messageId } 后,才可以把它视为已建立的 continuable child。

  1. Capability flags 的适用范围需要限定。

outputSchemadepthLimittoolFilterpersona 这些静态 capability flags 主要检查 one-shot start()。Continuable 是否支持,判断标准是 provider 是否实现 prepareContinuable(),不是简单复用这些 flags。

一句话版

DeepSeek Harness 的 SubAgent 是把一次性委派和可继续会话统一到同一个 Service 下:one-shot 通过 SubagentRun 返回一次性结果;continuable 则通过 durable Session、process-local Activation 和 Agent Inbox 实现多轮交互。Provider 负责后端差异和初始创建数据,Continuation Manager 负责身份、排队、权限、恢复和释放;Session Log 提供恢复依据,但只有已经写入日志的 Inbox 状态才具备崩溃恢复保障。

另外,这份文件实际上是“两轮研究稿”叠在一起:前半部分偏架构理解,后半部分偏源码走读,内容有不少重复。作为个人研究笔记没问题;如果以后整理成正式文档,建议保留后半部分的源码时序,把前半部分压缩成一页总览。


USER:

继续研究下里面提到的两个更深的问题:

① Child 到底继承 Parent 哪些 Service / Tool / Prompt / Memory,Cordis Scope 是怎么做到“共享 composition,但局部 shadow”的;

spawn/fork/continuable 和 Session Surface / Compaction 怎么互动,尤其是 fork 为什么能共享历史但又不能简单共享 Parent 当前 Context。

Codex:

先给结论:subAgent学习.md 的主线判断基本正确,但需要把三个概念彻底分开:

Composition:Child 能使用哪些 Service / Tool / Prompt
History:Child 的模型能看到哪些 Session 消息
Runtime:哪个 Agent、Scope、Inbox、Activation 正在运行

DSH 只在创建时复用部分 composition,fork 只复制一次稳定的历史 prefix;它们都不会复制 Parent 的 live runtime。

一、Child 继承什么

核心入口是 applyChildComposition

childCtx.agentPresets.composeFrom(childCtx, parent.ctx)
childCtx.systemPrompt.context(...)
childCtx.systemPrompt.section(...)  // optional persona
childCtx.tools.restrict(...)        // optional filter

关键点是 composeFrom()

standingMountFor(parentCtx)
bindScopeParent(childScopeKey, parentStandingScopeKey)

composeFrom

它不是:

Child Scope -> Parent Agent Scope

而是:

Parent Agent Scope -> Parent Preset Standing Scope
Child  Agent Scope -> 同一个 Preset Standing Scope

因此正确的结构是:

Child Agent Scope
        │
        ▼
Parent Preset Standing Scope
        │
        ▼
Global / Host Scope

Child 不会继承 Parent 自己的临时 scope registrations、Parent 的 tool restriction 或 Parent 当前 persona。它们只有在显式作为 Child composition 参数传入时才会出现。

Cordis 与 dsh-scope 是两层机制

这点非常重要:

  1. Cordis Context.extend() 使用 JavaScript 原型链,让子 Context 读取父 Context 的属性;子 Context 的 own property 可以 shadow 父属性。
  2. Cordis Fiber 管理插件生命周期、Service 注册、依赖解析和销毁。
  3. dsh-scope 另外维护 scopeParents,给 Tool、Prompt、Event 等 scope-aware registry 做可见性路由。

也就是说,dsh-scope 的父链不是通用的 Service 复制机制。官方文档也明确说,只有实现了 scope-aware API 的注册表才会继承;任意 Cordis Service 不会因为 Context 带了 scope tag 就自动隔离或继承。见 dsh-scope README

继承矩阵

内容 Child 的实际行为
Host/global Service 通常可见,但这是共享运行时可达性,不是从 Parent 复制
Preset 的 Tool / Prompt 注册 可见,多个 Agent 共享同一个 standing composition
Parent Agent 自己的局部 Service 不自动可见
Parent 自己的 Tool restriction 不自动继承
Child-local Service / Tool 新建,只属于 Child
Provider / Model / maxTokens 默认从 Parent route 继承,可被 request override
Sandbox / approval policy 在 delegation 时快照成 Child 自己的 session events
Conversation / Memory spawn 不继承,fork 复制一次历史 prefix
Parent 后续新增内容 Child 不可见

Tool 的 shadow 规则

ToolRuntime.view() 会按:

global
→ preset standing scope
→ child scope

合并工具;近层同名工具覆盖远层同名工具。见 tools.view

Child 的 toolFilter 只过滤它继承来的工具。多个 restriction 会相交,但 Child 自己注册的工具不会被自己的 restriction 删除,例如 continuable Child 的 report 工具和 structured-output 工具。

因此:

Parent 禁止 bash

不等于:

Child 也禁止 bash

Child 只有在创建请求中显式收到相同 toolFilter 时才会禁止 bash。

同时,Tool restriction 只是可见性和执行查找过滤,不是安全边界。真正的 sandbox、approval、filesystem policy 仍由对应 capability 和 policy service 执行。

Prompt 的 shadow 规则

System prompt assembly 会合并 global 和 scope-chain 的 sections,同名时近层覆盖远层。见 systemPrompt.assemble

Child 通常得到:

Global prompt
+ Parent preset prompt sections
+ Child-local subagent:delegation context
+ Child persona shadow
+ Child-local tool guidance

其中:

  • deployment:persona 可以由 Child persona 覆盖;
  • subagent:delegation 只注册在 Child;
  • prompt variables 也按 scope shadow;
  • complete: true 的 persona 可以使 Child 只使用该完整 system prompt;
  • dynamic runtime context 会在 Child 的 request 中重新计算,并在 shipped loop 中作为 user/message snapshot 写入 Child Session。

所以 Prompt 是“共享注册内容,按 Child 重新 assembly”,不是把 Parent 已经生成好的 system prompt 字符串永久复制给 Child。

二、Memory、Session Surface 与 fork

DSH 没有一个独立的“Parent Memory 对象”可以直接传给 Child。真正的权威来源是 Session Event Log:

Session Log
    ↓
Surface Fold
    ↓
Session.deriveMessages()
    ↓
模型历史

deriveMessages() 只投影:

user/message
assistant/message
tool/result

turn/*assistant/chunkrequest/header、policy event、descriptor、compaction metadata 等仍然保存在 log 中,但不直接进入模型消息。见 Session.deriveMessages

spawn

spawn provider 不提供 seed:

Child Session = empty
Child Agent = new
Child Scope = new
Child Surface = empty

它仍然会加入 Parent 当前使用的 preset composition,但没有 Parent transcript。

fork

fork provider 的 seed 规则是:

lastEnd = parent.session.events.findLast(e => e.type === 'turn/end')
seed = parent.session.events.slice(0, lastEnd.seq + 1)

因此它复制的不是简单的 deriveMessages() 数组,而是:

从 seq 0 开始的完整、连续、可验证的 raw event prefix

Child Session 构造时会重新校验、深拷贝 seed,并追加 session/end-seed。见 Session constructor

所以 fork 的准确语义是:

复制 Parent 截止 fork 时最后一个完整 turn 的事件历史
+
Child 自己重新组装 runtime

不是实时共享历史。

如果 Parent 当前正在执行:

turn/start
assistant/tool-call

但还没有:

tool/result
turn/end

这个 turn 就不会进入 fork seed。否则 Child 会得到一个未闭合、无法正常 replay 的 Session。

continuable

continuable 不是和 spawn/fork 并列的第三种 Provider。它是由 SubagentContinuationManager 管理的生命周期模式。

Provider 只实现:

prepareContinuable(): Promise<{ seed?: SessionEvent[] }>

之后由 continuation manager 负责:

稳定 childId
→ durable Session
→ Agent
→ Scope
→ Inbox
→ Activation
→ cold resume
→ dispose

第一次创建时:

  • spawn 的 seed 为空;
  • fork 的 seed 是 Parent 已完成 turn prefix;
  • manager 额外写入 Child descriptor;
  • initial prompt 进入 Child Inbox;
  • startContinuable() 在 Inbox 接受后返回,不等待模型完成。

之后:

Activation running  -> 直接排入 Inbox
Activation waiting  -> 唤醒原 Agent
Activation 不存在   -> 从持久化 Session cold resume

startContinuablecoldResume

因此 continuable Child 的“Memory”是:

同一个 durable Child Session
+ 多次追加自己的 turn

而不是每次 followup 都重新 fork Parent。

三、为什么 fork 能共享历史,却不能共享 Parent 当前 Context

因为两者的稳定性完全不同。

Parent 当前 Context 可能包含:

当前 Agent Scope
当前 tool registrations
当前 prompt providers
当前 policy overrides
当前 Inbox
当前 AbortController
当前正在执行的 tool
当前未完成 turn
临时 Cordis fibers
动态 runtime context

这些东西不能作为一个整体复制。

最关键的原因有五个:

  1. 未完成 turn 不可重放

    当前 tool call 可能只有 assistant call,没有 result 和 turn/end。复制它会产生不平衡 Session。

  2. Scope 的所有权不同

    Parent Scope 由 Parent Agent 生命周期拥有。Child 必须有自己的 Scope、自己的 disposer 和自己的事件路由。

  3. Service 不是纯数据

    Cordis Service 可能包含连接、监听器、worker、缓存、AbortSignal、Fiber disposer。复制对象并不能复制正确的生命周期。

  4. 权限不能随知识自动继承

    Parent 当前的 tool restriction、sandbox、approval 状态可能已经发生变化。把历史和当前 authority 绑定在一起会让 Child 使用不清晰的权限状态。

  5. Parent 未来会继续变化

    fork 后 Parent 可能继续追加消息、执行工具或 compaction。Child 若共享 Context,就会出现两个 Agent 同时改变同一份可变状态。

所以 inheritsParentContext = true 只能理解为:

inherits completed conversation seed

不能理解为:

inherits Parent Service / Tool / Permission / Scope / Runtime

四、Compaction 如何参与

Compaction 不是删除 raw log,而是一个持久化日志事务:

compaction/start
compaction/summary
user/message(surfaceOp = replace)
compaction/end

新的 user/message 会替换 Surface 上的一段旧节点;旧事件仍在 raw log 中。见 commitCompactionBody

因此:

Parent 先 compaction,再 fork

Child 复制 Parent 当时的 raw event prefix。Child 重新 fold 后看到的是:

summary checkpoint
+ 未被压缩的 retained tail

不是被 shadow 的原始消息。

fork 后 Parent 再 compaction

Child 已经拥有 detached seed,Parent 的 Surface replacement 不会影响 Child。

Child 自己 compaction

只改变 Child 自己的 Surface 和 replaceGeneration,不会自动把 summary 写回 Parent。

Child 若要让 Parent 知道结果,必须通过显式通信:

report tool
quiet inject
wakeup followup
settlement notice

这些才会以新的 Parent Session message 或 notice 进入 Parent 的历史。

还有一个细节:如果 fork seed 中已经包含 Parent 之前写入的 runtime-context snapshot,Child 会继承那条历史消息;但 Child 的 RuntimeContextProjection 是自己的实例,之后会根据 Child 当前 Scope 和 Session 重新判断是否需要追加新 snapshot,不会共享 Parent 的 projection 对象。

五、一个完整时序

Parent:
  turn 1 完成
  turn 2 正在调用工具

fork:
  只复制 turn 1 的完整 event prefix
  不复制 turn 2
  创建 Child Session
  创建 Child Agent / Scope / Inbox
  加入 Parent 的 preset standing scope
  应用 Child persona / toolFilter
  Child 接收新任务

之后:
  Parent 继续写入 turn 2
  Parent 修改 runtime context
  Parent 执行 compaction
  Parent 注册或撤销局部工具

结果:
  已创建的 Child 都不会自动看到这些变化

因此,对 subAgent学习.md 最需要收紧的表述是:

Child 继承的不是 Parent Context,而是 Parent 当前 preset 的 composition,以及 fork 在创建瞬间截取的、已经完成且可重放的 Session event prefix。
Child 的 Service visibility、Tool visibility、Prompt assembly、Session history 和 runtime ownership,始终是四套不同机制。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐