DSHarness 系统拆解 固定基线 47f943859b · 36 已复核 / 0 撰写中 / 36 章
English
核心运行时·第 05 章

Agent 对象、注册表与生命周期

创建、恢复、收件箱、状态和关闭

已复核上游 47f943859b范围: 追踪 Agent 接口、registry、create/resume、live handle、initiator 与取消语义。

结论:Agent 是有作用域的活体执行能力,不是会话记录本身

Session 持有可持久化、可重放的事实;Agent 是当前进程里围绕该 Session 建立的 driver、Inbox、scope 与取消边界;AgentHandle 则是只有所有者持有的 teardown capability。

这三个对象不能互换。Session 可以在没有 live Agent 时存在;同一个持久化 Session 可以在下一次进程生命周期中恢复为新的 Agent;registry 中能查到 Agent 只说明它当前已发布,不代表查询者拥有关闭它的权力。

1. 接口包刻意不知道具体 loop

包边界

@deepseek-ai/dsh-agent 定义 Agent 接口、registry、inbox、initiator scope 和全部 agent/* live event;具体创建和驱动由 dsh-agent-loop 通过唯一 AgentFactory 注册槽提供。Registry 在每次 create/resume 时用调用者 context 重新 trace factory,使 lifecycle effect 归属于真正发起调用的 fiber。AGENT-FACTORY-SEAM

机制推导

UI、ACP bridge、subagent provider 和测试可以只依赖 ctx.agents,而不导入默认 ReactLoopAgent。替换 loop 需要实现同一个 Agent/Factory 生命周期合同,却不要求消费者改写。这是“插件可替换”在核心对象层面的实际落点。

2. Agent 对象的精确构成

成员语义是否耐久
idsession.id 完全相同的 SessionId;不是另一套 Agent UUID身份来自 Session
options本实例的 provider、model 与可选正整数 maxTokens不是 Session log 的固有历史;恢复时可重新选择
sessionappend-only event log 与派生历史的事实源
inbox由 durable splice 事件重建的 pending-work 投影事件耐久,数组是 live projection
ctx该 Agent 独占的 Cordis scope;工具、prompt section、listener 随 scope 一起卸载否,resume 时重新构建
status只暴露 idle / running;disposal 是离开 registry,不是第三种 statuslive-only
公共合同

接口还提供 sendfollowupsteerinjectcancelwhenIdlerunMaintenancerunning 表示整个 driver drain 区间,而不严格等于“某个 turn 仍未关闭”。AGENT-PUBLIC-INTERFACE

3. Live phase 比公开 status 更细

具体实现

ReactLoopAgent 内部有三类 phase:idle(lastTurn)maintenance(abort, wakeRequested)running(abort, turn, step, wakeRequested)。公开层把 idle 和 maintenance 都映射为 idle,只有 running 映射为 running;只有映射结果改变时才发 agent/statusAGENT-RUNTIME-SCOPE

runMaintenance() 从真正 idle phase 同步认领独占权,但不伪造一个模型 turn。期间到达的 waking input 只留在 Inbox,并在 maintenance 结算后唤醒 driver。whenIdle() 循环观察 activityDone 的 identity,因此会跟随当前活动退休前安排的 replacement work,而不是只等待调用瞬间那一个 Promise。

设计评价

公开状态保持简单,内部 phase 保留调度所需的精度。代价是 observer 不能把 idle 解读为“完全没有后台维护”,也不能把 running 解读为“模型正在生成”;真正的语义必须结合 turn/step durable events。

4. 每个 Agent 都拥有一个可整体撤销的世界

生产源码

构造函数先从 Session 事件恢复最后 turn 和 Inbox,再以 Agent 本身作为 scope key 调用 createScope(),并让 agent.ctx 携带自己的 agent 属性。通过这个 context 注册的工具、prompt、变量、限制器和 listener 只对该 Agent 生效。AGENT-RUNTIME-SCOPE

Setup 合同

create/resume 的 setup(agentCtx) 在 Session 和 Agent 均未进入 registry 时运行,可以异步挂载 scoped plugins,并可返回一个同步 commit() 在发布边界再次验证可变资源。Setup 只能组合,不能驱动 Agent;抛错、commit 抛错或 owner 卸载都在未发布状态回滚。AGENT-SETUP-CONTRACT

这让“第一个模型请求之前,Agent 的能力世界必须已经完整”成为结构性保证,而不是依赖 agent/created listener 竞速补装。

5. Create 与 Resume 汇合成同一条原子发布管线

1

Acquire SessionPreparation

Create 验证并快照新 session 的 seed/meta;Resume 从 persistence prepare 已有 Session。两者此时都未进入 live store。

2

Prepare ReactLoopAgent

创建 scope、Inbox 与 driver;在任何资源之前安装 caller signal、owner fiber 和 factory teardown 的融合取消。

3

Await setup

调用 setup(agent.ctx) 并允许它异步完成;对象仍不可被 registry 查询。

4

Synchronous setup commit

在所有 await 之后、任何发布之前执行最后一次不可让出的验证/提交。

5

Enter both registries

sessions.enter(session),再 agents.enter(agent, ownerCtx.agent);这里是同 ID 竞争的权威仲裁点。

6

Announce in order

session/createdagent/created → 非 veto 的 agent/session-start,source 分别是 startup 或 resume。

7

Return AgentHandle

creation-only signal 已脱离未来控制;之后由 handle、Agent cancel 或结构性 owner 管理生命周期。

生产控制流

新建和恢复最终都调用 setupAndPublish();resume 的 persistence load 同样受 caller signal、owner unload 与 factory teardown 约束。AGENT-CREATE-RESUMEAGENT-PREPARE-PUBLISH

行为证明

测试在 async setup 的闸门前确认两个 registry 都不可见,并验证完整事件顺序;两个同 ID create 可以同时进入 setup,但最终只有一个成功发布,失败者不留下孤儿 Session;创建 signal 在 handle 返回后再 abort 不会终止 live Agent。AGENT-ATOMIC-TEST

6. Publication 被拆成 enter 与 announce

Registry 控制流

enter() 先强制 agent.id === agent.session.id、检查 ID 冲突并插入一个带稳定 carrier 的精确 entry,但不发事件。announce() 只允许对当前 exact entry 调用一次,并在 dispatch 前标记 announced。AGENT-REGISTRY-LIFECYCLE

拆分不是形式主义,而是为原子发布和重入 teardown 服务:

  • Session 与 Agent 可以先同时进入 store,随后 listener 看到的是一致世界。
  • agent/created listener 同步请求 detach 时,删除延迟到整个 dispatch 返回,所以后续 listener 仍看到同一个 live entry。
  • detach closure 捕获 entry object,不只捕获 ID;旧 closure 无法删除后来复用同一 ID 的 replacement。
  • 从未 announce 的 entry 回滚时不发 agent/disposed,避免凭空制造生命周期边。
重入测试

Registry 单测验证 split publication、幂等 detach、stale capability 隔离,以及 created listener 中请求 detach 后所有 listener 仍观察到稳定 entry。AGENT-REGISTRY-TEST

7. Teardown 有多个 owner,但只有一个 quiescence

一个 programmatic Agent 同时有三类 lifecycle authority:

Authority为什么拥有它可做什么
Consumer 的 AgentHandle显式创建/恢复 Agent调用 memoized dispose()
调用者 fiber结构化并发:子资源不能超出调用者插件生命周期卸载时触发同一 dispose
AgentLoop factory fiberlive Agent 依赖该 provider 的实现与服务面provider HMR/卸载时停止全部旧实例
实际逆序

所有 owner 竞争同一个 memoized Promise:先以 {kind:'disposed'} cancel driver,等待 whenIdle(),再 dispose Agent scope,随后精确 detach Agent、detach Session,最后清理 factory/owner bookkeeping。AGENT-PREPARE-PUBLISH

重入发布测试

即使 session/createdagent/created listener 立刻卸载 owner,dispatch 中其他 observer 仍能看到完整双 registry;回滚的可观察顺序是 scope disposal,然后(如果已 announce)agent/disposed,最后 session/disposedAGENT-TEARDOWN-TEST

8. Runtime owner、durable lineage 与 initiator 是三件事

关系表示什么存在哪里能否授权
Session parentSessionfork/派生的耐久谱系Session header不能自动授权当前进程操作
Registry owner哪个 live Agent 的 scoped context 创建了该 live entry进程内 AgentEntry只支持精确 runtime ownership 查询
Current initiator哪一个 driver 的 async chain 因果触发当前工作AsyncLocalStorage明确不是 liveness proof 或 authorization

因此,一个带 parentSession 的持久化 fork 在独立 resume 后仍可能是 runtime root;反过来,一个当前 owner 创建的 child 也不能仅靠 ambient initiator 穿越 worker、HTTP、进程或 durable queue。

Initiator API

withInitiator() / withoutInitiator() 在并发 async chain 中隔离并恢复身份,保留操作返回值或 Promise 的精确 identity。服务 teardown 先拒绝新 boundary,等待已返回 Promise 的 boundary drain,再 disable AsyncLocalStorage;触发自身 unload 的嵌套链从 drain 中释放以避免等待自己。AGENT-INITIATOR-APIAGENT-INITIATOR-DRAIN

9. Inbox 是 durable event 的增量投影

重建

新 Agent 构造 Inbox 时,会重放 seedLength 之后的全部 agent/inbox/spliced;无效坐标或重复身份让恢复失败,而不是静默修复。Inbox 有 next-turnnext-step 两个列表,但 MessageId 在二者合并范围内必须唯一。AGENT-INBOX-REPLAYAGENT-INBOX-COMMIT

操作耐久事实Live notification
append / prepend带 inserted 的 agent/inbox/spliced逐消息 agent/inbox/inserted
replace同一 splice 删除旧值并插入新值先 discarded 旧消息,再 inserted 新消息
remove / clear删除 splice 带 outcome:'canceled'逐消息 discarded
claim纯删除 splice,不标 canceled逐消息 claimed,并绑定 owning turn

提交顺序是先 session.append(),再修改内存数组;同步 session observer 因而看到 pre-splice projection,并可用规范化坐标重建被删消息。一步 claim 先取所有 pending next-step,再在 turn 边界需要时取一个 next-turn。

行为证明

单测覆盖非法持久化 splice、跨两个列表 replace、重复 MessageId 拒绝,以及 clear 先 next-step 后 next-turn 生成两条 canceled 事件。AGENT-INBOX-TEST

10. Follow-up、Steer、Inject 与 Cancel 的差异

API目标列表是否唤醒最早消费边界
followup(message)next-turn一个新的普通 turn;每次只消费一条 next-turn
steer(message)next-step运行中 Agent 的下一 step;idle 时开启 turn
inject(message)next-step已有 driver 的下一 pre-step;idle 时一直停放
cancel(cause)默认清空两者abort 当前 activity;无 activity 时不为未来工作“预装取消”
cancel(cause,{keepInbox:true})保留两者只 abort 当前 activity,pending work 可由以后 wake 消费
实现映射

send() 先持久化 Inbox insertion,再按 wake flag 驱动。若当前 activity 已 abort,新的 waking input 被重新分类为 next-turn,防止混入正在收敛的失败 turn;disposal 原因不会 latch 新 wake。AGENT-RUNTIME-SCOPE

11. 设计收益、代价与限制

选择收益代价 / 限制
Agent 接口与 loop 分包消费者和 orchestration 不绑定具体 driver替换实现必须完整复制复杂生命周期与事件合同
Unpublished setup transaction首个 observer 永远看到完整 scoped worldsetup 是 trusted same-process code;“只组合不驱动”依靠合同
Enter / announce 两阶段支持一致双 registry 与重入 teardown生命周期实现更复杂,部分通知失败仍需配对 disposal
Handle 作为 teardown capability只读 registry observer 不能关闭 Agent所有权必须在每种 transport/provider 中显式传递
Durable Inbox projectionidle 注入、取消和 resume 有统一事实每次 queue mutation 增加事件量;单 MessageId 只能带一个 source
Process-local initiator同进程 async 因果归因便捷且并发隔离不能跨 worker/process/wire,也绝不能代替显式授权
本章评价

DeepSeek Harness 没有把 Agent 设计成一个携带所有状态的“智能对象”,而是把耐久事实、live driver、作用域贡献、排队工作、所有权 capability 和因果归因拆开。这个分解为 resume、per-Agent plugin、HMR 与多种 transport 提供了坚实基础;真正的成本是 lifecycle protocol 本身已经接近一套小型事务系统,任何新 provider 若绕开 prepare/publish/dispose 次序都会破坏一致性。

本章复核清单

  • 已区分 Agent interface package 与具体 AgentLoop factory。
  • 已核对 id/session、options、scope、status 与公共输入/取消方法。
  • 已追踪 create 与 resume 从 preparation、setup、commit 到三次发布事件。
  • 已验证同 ID 并发仲裁、creation-only signal 和失败回滚。
  • 已追踪 enter/announce、reentrant detach 与 stale capability 防护。
  • 已核对 consumer、caller fiber、factory fiber 的共同 teardown 所有权。
  • 已区分 durable lineage、runtime owner 与 process-local initiator。
  • 已核对 Inbox replay、提交顺序、claim、取消和 MessageId 唯一性。

我的学习体会

内容仅自动保存到当前浏览器,不上传、不进入仓库。你可以导出 Markdown 自行归档。