Agent 对象、注册表与生命周期
创建、恢复、收件箱、状态和关闭
结论: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 对象的精确构成
| 成员 | 语义 | 是否耐久 |
|---|---|---|
id | 与 session.id 完全相同的 SessionId;不是另一套 Agent UUID | 身份来自 Session |
options | 本实例的 provider、model 与可选正整数 maxTokens | 不是 Session log 的固有历史;恢复时可重新选择 |
session | append-only event log 与派生历史的事实源 | 是 |
inbox | 由 durable splice 事件重建的 pending-work 投影 | 事件耐久,数组是 live projection |
ctx | 该 Agent 独占的 Cordis scope;工具、prompt section、listener 随 scope 一起卸载 | 否,resume 时重新构建 |
status | 只暴露 idle / running;disposal 是离开 registry,不是第三种 status | live-only |
接口还提供 send、followup、steer、inject、cancel、whenIdle 与 runMaintenance。running 表示整个 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/status。AGENT-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
create/resume 的 setup(agentCtx) 在 Session 和 Agent 均未进入 registry 时运行,可以异步挂载 scoped plugins,并可返回一个同步 commit() 在发布边界再次验证可变资源。Setup 只能组合,不能驱动 Agent;抛错、commit 抛错或 owner 卸载都在未发布状态回滚。AGENT-SETUP-CONTRACT
这让“第一个模型请求之前,Agent 的能力世界必须已经完整”成为结构性保证,而不是依赖 agent/created listener 竞速补装。
5. Create 与 Resume 汇合成同一条原子发布管线
Acquire SessionPreparation
Create 验证并快照新 session 的 seed/meta;Resume 从 persistence prepare 已有 Session。两者此时都未进入 live store。
Prepare ReactLoopAgent
创建 scope、Inbox 与 driver;在任何资源之前安装 caller signal、owner fiber 和 factory teardown 的融合取消。
Await setup
调用 setup(agent.ctx) 并允许它异步完成;对象仍不可被 registry 查询。
Synchronous setup commit
在所有 await 之后、任何发布之前执行最后一次不可让出的验证/提交。
Enter both registries
先 sessions.enter(session),再 agents.enter(agent, ownerCtx.agent);这里是同 ID 竞争的权威仲裁点。
Announce in order
session/created → agent/created → 非 veto 的 agent/session-start,source 分别是 startup 或 resume。
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
enter() 先强制 agent.id === agent.session.id、检查 ID 冲突并插入一个带稳定 carrier 的精确 entry,但不发事件。announce() 只允许对当前 exact entry 调用一次,并在 dispatch 前标记 announced。AGENT-REGISTRY-LIFECYCLE
拆分不是形式主义,而是为原子发布和重入 teardown 服务:
- Session 与 Agent 可以先同时进入 store,随后 listener 看到的是一致世界。
agent/createdlistener 同步请求 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 fiber | live 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/created 或 agent/created listener 立刻卸载 owner,dispatch 中其他 observer 仍能看到完整双 registry;回滚的可观察顺序是 scope disposal,然后(如果已 announce)agent/disposed,最后 session/disposed。AGENT-TEARDOWN-TEST
8. Runtime owner、durable lineage 与 initiator 是三件事
| 关系 | 表示什么 | 存在哪里 | 能否授权 |
|---|---|---|---|
Session parentSession | fork/派生的耐久谱系 | 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。
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-turn 和 next-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 world | setup 是 trusted same-process code;“只组合不驱动”依靠合同 |
| Enter / announce 两阶段 | 支持一致双 registry 与重入 teardown | 生命周期实现更复杂,部分通知失败仍需配对 disposal |
| Handle 作为 teardown capability | 只读 registry observer 不能关闭 Agent | 所有权必须在每种 transport/provider 中显式传递 |
| Durable Inbox projection | idle 注入、取消和 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 自行归档。