Agent setup may await while a mutable contribution registry changes. The previous subagent path validated and committed its provisioning batch inside the setup callback. A revocation queued after that callback returned therefore treated the installation as resident and released it, even though AgentLoop had not published the child yet. AgentLoop could then admit and announce a child whose required capability had already disappeared. Introduce AgentSetupCommit as the optional synchronous result of create and resume setup. AgentLoop now awaits setup, invokes that commit with no intervening asynchronous boundary, and only then enters the Session and Agent registries. A commit failure follows the existing private-transaction rollback, so neither identity is published and the caller can reuse the id. Keep continuable-subagent installations provisional until this publication commit. Contribution removal still releases every installation immediately, but now marks an unpublished batch invalid so its commit rejects with ACTIVATION_SETUP_REVOKED. Once the commit succeeds, later removal remains ordinary live revocation. Cover create and resume ordering, resume commit rejection and identity reuse, and an assembled microtask revocation that leaves only the parent Agent and Session. Update the public JSDoc, architecture flow, package contracts, current Agent Notes, Chinese counterparts, pairing records, and generated Cordis API to describe the new boundary. Validated with the four focused Agent/subagent test files (91 tests), the isolated assembled regression, targeted TypeScript project builds, generated Cordis API freshness, export JSDoc verification, scoped translation pairing, Markdown wrapping, and Mermaid parsing.
19 KiB
dsh-agent
English | 中文
Agent 接口、注册表、进程本地发起方作用域,以及 agent/* 事件词汇。每个插件(UI、钩子、编排器)都面向此处定义的 Agent handle 编程;它不依赖循环,因此循环可以替换。
可选配套包(package)@deepseek-ai/dsh-agent/invariant 会向 ctx.invariants 注册此包的 agent(智能体)状态转换检查。根 agent 服务不会隐式加载诊断。
服务:AgentRegistry(ctx 键:agents)
跟踪实时 agent,并在异步驱动器工作中携带发起调用的 Agent,而无需导入具体循环包。
公开 API
带作用域的注册接口:Agent.ctx 是 agent 的作用域上下文(dsh-scope,键 = 该 agent)。通过它注册工具/段/变量/监听器,只对该 agent 生效,并在 dispose(资源释放)时全部撤销。agentEvents(ctx, agent) 是普通 agent 主体操作的融合分发器(一次完成载体 + 注入主体);其通知 mode 会调用每个监听器,并同时收容同步抛出和返回 Promise 的拒绝。注册表生命周期对复用一个稳定路由载体。assembleContextFor(agent) 构建按 agent 的组装上下文(同时包含 agent + scope)。installAgentLlmTarget(agentCtx, target) 在提示词组装期间快照可变的提供方/模型/推理(reasoning)强度选择,将路由应用到提示词变量,并将完整目标应用到一个步骤的请求路由;如果没有选定推理强度,则会清除继承的推理强度,使该目标使用适配器/提供方默认值。CreateAgentOptions.setup(agentCtx) 和 ResumeAgentOptions.setup(agentCtx) 在新建或恢复的 agent 尚未发布时,组合其带作用域的世界。Setup 可以返回一个 AgentSetupCommit;所有 setup 的 await 均结算后,工厂会在进入注册表前立即调用其同步 commit(),若其抛出异常,则回滚私有事务且不发布任何一个 id。Setup 仍是受信任、仅用于组合的同进程代码:只有创建完成后才能驱动 agent。
AgentOptions 提供初始的提供方/模型路由,以及可选的正数 maxTokens 输出上限。实体循环会解析确切模型的适配器默认值,把生效上限记录到请求 header,并应用到每次对话模型请求;显式 Agent 选项优先,省略时由适配器或提供方路由默认值控制。
ctx.agents.register(agent: Agent): () => void:记录一个 已经构造完成 的 agent。随调用 fiber dispose。- 高级有序生命周期:
enter(agent, owner): () => void强制agent.id === agent.session.id,执行权威 ID 冲突检查,并在不通知的情况下插入;owner显式记录实时创建方 agent 关系(根 agent 为undefined),与持久会话谱系无关。announce(agent)恰好发出一次agent/created。创建监听器同步请求的 detach 会延后到该次分发结束;每次 detach 都会检查捕获的条目对象,因此陈旧能力无法删除后续使用同一 ID 的替代项。异步工厂使用这一拆分;普通插件使用register()。 ctx.agents.get(id: SessionId): Agent | undefinedctx.agents.isOwnedBy(id: SessionId, owner: Agent): boolean:该确切实时条目是否通过父 agent 的作用域上下文创建;运行时所有权与持久会话谱系无关。ctx.agents.list(): Agent[]ctx.agents.roots(): Agent[]:在没有所属 agent 上下文的情况下创建的实时 agent;带谱系的恢复会话仍可能是运行时根。
发起方 Agent 作用域
AgentLoop 在发起方边界内运行每个具体驱动器的完整生命周期。并发驱动器彼此隔离:子驱动器的 continuation 携带子 agent,而 withInitiator() 返回后,父 continuation 立即重新取得父 agent;drain 跟踪持续到子驱动器的 Promise 结算。创建、持久化加载和未发布 setup 位于子边界之外,因此由父 agent 发起的 setup 会继承父 agent,而 agentCtx.agent 显式标识子 agent。
ctx.agents.currentInitiator(): Agent | undefined:读取继承的发起方,不要求其存在。ctx.agents.requireInitiator(): Agent:读取发起方,缺席时抛出no initiating agent is active。ctx.agents.withInitiator(agent, operation):使用一个确切 Agent 运行,并保留操作的确切同步值或 Promise。ctx.agents.withoutInitiator(operation):对无关的进程本地工作隐藏继承的发起方。
该作用域携带 Agent 本身,并且只在进程内有效。环境中的身份既不是存活证明,也不是授权;在服务、worker、进程、持久化和 wire 边界,显式 Agent 字段仍是权威来源。Teardown 会拒绝新边界,允许注入的依赖方和返回 Promise 的边界 drain,然后禁用底层 AsyncLocalStorage;未返回的工作仍归将其分离的子系统所有。如果某个边界继承的异步链开始卸载一个拥有它的 Cordis fiber,该嵌套边界链会从 drain 中释放,使卸载不会等待自身;其 continuation 会在 teardown 后观察到已 dispose 的服务。详细边界与 teardown 契约由发起方作用域决策拥有。
工厂 seam(创建)
Agent 创建 由实现 AgentFactory 的插件(dsh-agent-loop)提供,并通过 setFactory 注册。这样,创建功能留在 dsh-agent 接口上,消费方(UI、ACP 桥接层)可以面向 ctx.agents 编程,而不依赖具体循环包。注册表会把已经 traced 的 Service 规范化为具体目标,并通过调用方上下文重新 trace 每次调用;这既避免嵌套 Cordis shadow,也会把显式、绑定调用方的 ownerCtx 传给普通工厂。
ctx.agents.setFactory(factory: AgentFactory): () => void:注册创建工厂(循环在构造时调用)。第二个工厂会导致抛出;dispose 时清空槽位。ctx.agents.create(options: CreateAgentOptions): Promise<AgentHandle>:创建会话和 agent,在不发布的情况下等待可选 setup,调用其可选的同步提交,然后通过最终的SessionStore.enter()与AgentRegistry.enter()检查发布。不支持并发创建同一 ID:多个操作可以进行准备,但只有一个能进入;每个失败方都会回滚其私有作用域/会话/驱动器。可选且只用于创建的signal会取消未发布的 setup,并在返回 handle 前分离;之后的取消使用handle.dispose()或agent.cancel()。发布包含在回滚范围内,回滚期间每条已交付创建边都会成对处理。未注册工厂时拒绝。ctx.agents.resume(options: ResumeAgentOptions): Promise<AgentHandle>:加载持久化会话(会话持久化),创建新的未发布 agent 作用域,等待可选 setup,调用其可选的同步提交,并使用相同的最终进入发布序列。其可选signal同样只用于创建。未注册工厂或未配置会话持久化时拒绝。
AgentHandle = { agent: Agent; dispose(): Promise<void> }。Disposer 是一项 消费方能力;仅持有裸注册表条目的观察方不能 teardown agent。调用方 fiber 和已注册工厂提供方是结构化共同拥有者:调用方卸载会强制结构化所有权,而工厂卸载必须停止旧实例,因为它们的作用域依赖范围属于该提供方。任意拥有者调用 dispose() 都会到达同一个记忆化完全停稳边界:它停止循环,等待循环退出,注销 agent,从存储中移除其会话,最后撤销其作用域世界。ctx.agents.get(id) 仍返回裸 Agent;ACP 桥接层与进程内 subagent 后端持有消费方 handle,而配置创建的 agent 已由循环 fiber 拥有。
实时事件
dsh-agent 声明实时 agent/* 协调词汇,使插件不必依赖具体循环。确切签名、分发 mode、作用域筛选规则与 payload 契约位于生成的 Cordis 事件目录;架构轮次流 展示它们与持久会话事件的相对顺序。
生命周期边有两个重要的本地注意事项。agent/created 在作用域 setup 之后、会话与 agent 注册表条目都存在之后运行。Setup 是受信任、仅用于组合的代码;紧随其后且不可 veto 的 agent/session-start 通知是第一个受支持的启动注入点。agent/disposed 始终表示确切 agent 已离开注册表。AgentLoop 在其驱动器完全停稳后发出该事件,而有序 teardown 此时可能仍在分离会话并撤销作用域;直接注册的自定义 agent 自行拥有任何更强的驱动器顺序契约。
大多数拦截点都是协作式 waterfall(瀑布式事件)。轮次作用域的异步 seam 接收一个显式 AbortSignal,其中 signal 紧邻 waterfall 最终的 next;监听器可以配合,但不得将它保留为控制另一轮次的权限。agent/step 是派生请求前的串行检查点,而 agent/request-error 是失败模型请求的恢复 waterfall:失败步骤关闭后,它接收确切错误、规范化失败事实和信号。拥有恢复权的监听器返回 { kind: 'retry' } 且不调用 next();循环会关闭失败轮次,并打开一个编号重试轮次。agent/turn-stopping 在本可完成的轮次关闭前运行。普通排队提示词保持原样。有效的广义取消会先发出只观测的 agent/cancel-requested 及其解析后的类型化原因,再清空队列并中止;通知失败会被收容,不能 veto 停止。信号生命周期由显式取消决策拥有;作用域分发与终止结算由 agent 作用域 runtime 设计 Agent Note(agent 决策记录)拥有。
PromptDecision.additionalContexts 是由带标识且冻结的 UserMessage 值组成的数组,因此每个上下文都保留自己的标识和来源。获准的提示词与每个附加上下文都会在轮次运行前成为各自独立、面向模型的 user/message 事件。包装下游允许决策的监听器会保留其 content 与 additionalContexts,除非有意替换任一字段;替换获准内容时仍会保留提示词的标识。
轮次和步骤边界以及模型 token 流是持久 session/event 事实,而不是镜像的 agent/* 通知。消费方从会话事件流读取 turn/*、step/* 和 assistant/chunk;工具策略与结果观测属于 dsh-tools 记录的完整流水线。
Agent 接口(types.ts)
每个插件面向的 handle:
agent.send(message, options):覆盖(target×wakeup)矩阵的唯一投递原语。message是已有标识且已冻结的UserMessage;调用方通常会在开始路由前使用createUserMessage()创建它。SendOptions只持有target与wakeup策略。每次获准进入 FIFO 的项都会获得独立的InboxItemId,即使调用方复用了同一个MessageId;agent/inbox/enqueue/update及终态dequeue或discard都会携带这一完整InboxItem。target: 'next-turn'排队一条独立 FIFO 项,获准后成为其轮次中唯一的普通提示词。target: 'next-step'且wakeup: true提交 steering(中途引导),而target: 'next-step'且wakeup: false注入持久上下文,不运行模型。轮次原理由 one-send-one-turn Agent Note拥有。agent.reserveTurnAdmission():在任何已排队唤醒提示词认领其轮次之前,同步预留空闲边界。已获接纳的提示词拥有优先权,包括同一 tick 内仍在等待唤醒的项,此时预留返回undefined。预留期间,之后发送的项保留其普通 ID、FIFO 位置与唤醒信息;acceptsNextStep保持 false,inject()不受阻塞,whenIdle()将该预留计为活动,返回的释放函数可幂等调用。这项范围有限的协调能力使手动压缩(compaction)等独立持久操作能够在排队提示词从会话派生内容前完成并 flush。agent.updateInbox(itemId, action):同步编辑或移除一个仍处于待处理状态的 queued 入队项。编辑会替换已冻结的内容,同时保留其MessageId、InboxItemId、来源与 FIFO 位置;移除会发出该项的终态 discard。steering 项和已被认领的项会返回not-found。agent.followup(input):send()的next-turn/wakeup 预设:排队一个普通后续轮次并唤醒驱动器。agent.steer(input):next-step/wakeup 预设:提交一条已有标识的消息,并取得其SteeringReceipt。提示词接纳期间或轮次打开时,消息会为下一个安全请求边界暂存,且不分发agent/prompt-submit;该接收窗口之外则成为会唤醒驱动器的排队提示词。只有循环记录消息、将其捕获到不可变请求历史并提交step/start后,receipt.outcome才会解析为admitted,并附带轮次与步骤。结束轮次的工具结果、广义取消、dispose(资源释放)或准入前故障会使其解析为rejected;cancel(..., { keepInbox: true })和非终止型路由会保留待处理投递。需要可靠投递的调用方应等待回执;尽力执行的 UI steering 可以忽略它。agent.inject(input):next-step/不唤醒预设:追加面向模型的上下文而不运行模型;下一次请求会看到一条逐字的 user role 消息,其来源由必填的input.source携带。提示词接纳期间或轮次打开时,注入会在 outbox 中等待下一个安全边界。该接收窗口之外,它会立即追加而不开启轮次;如果接纳结束却未开启轮次,仅含上下文的接纳批次会采用这一回退,而与 steering 一同暂存的上下文则会随其继续待处理。持久化独立地响应session/event。注入不发出agent/inbox/*事件。agent.acceptsNextStep:当前发送next-step时,是否会加入提示词接纳或已打开的轮次。当调用方必须在 steering 与新接纳的提示词之间选择时,应使用这一更窄的路由判定;status === 'running'还涵盖接纳收尾与轮次结算阶段。agent.cancel(cause, options?):取消活动轮次,并在未设置options.keepInbox时取消全部待处理工作。调用方必须显式选择user | parent原因;活动持有者会在中止前把其判别字段复制为已分离、冻结的信号原因。有效调用会在清除排队与 steering 工作前,随原因发出agent/cancel-requested;丢弃项在agent/inbox/discard上报告,观察方可以同步状态,但不能 veto 取消。keepInbox: true会中止轮次,但保留排队与 steering 项(不丢弃,且不删除尚未开始的工作)。同进程类型化 seam 不会为无类型调用方添加运行时校验或兼容回退。重复取消活动轮次时,首个信号生效;空闲取消是安全空操作,不发通知。ACP 映射到user,进程内父传播映射到parent。原因只存在于运行时;持久turn/end保持粗粒度的aborted。agent.whenIdle():agent 从running结算后达到完全停稳时解析(idle ⇒ 立即;disposed ⇒ 等待循环退出)。这是非拥有者的完全停稳观测钩子:观察工作结算,但不 teardown agent。Teardown 独立存在;生命周期拥有者通过AgentHandle.dispose()停止并注销,并直接等待循环退出。agent.session、agent.status、agent.options、agent.id
running 描述驱动器范围的 drain 区间,而不是轮次仍打开的证明;它可以覆盖轮次关闭、持久性检查点和连续的排队轮次。
扩展点
- Agent 创建:
AgentLoop.create()是具体配置路径实现(位于dsh-agent-loop),程序化消费方则通过ctx.agents.create()/ctx.agents.resume()创建或恢复有所有权的 agent。替换循环时,应实现Agent并通过ctx.agents.register()注册。 - 事件监听器:全部
agent/*事件都在此处声明,不需要依赖循环包。 - Subagent 委派不是
Agent方法;提供方通过工厂 seam 创建或驱动普通 handle,因此委派传输留在核心 agent 接口之外。
模型体验
用户、steering 与注入消息
模型看到的内容
send、steer 与 inject 会向所属会话提供输入。agent/prompt-submit、agent/step 和其他已声明事件让插件能够阻止提示词或添加持久请求材料;此接口本身不贡献固定文案。
Token 影响
已接受内容成为保留历史,或成为每次请求都会重复的会话前缀;被阻止内容不贡献请求 token。大小取决于调用方与插件。
KV Cache 影响
已接受历史与 steering 只追加;被阻止的提交不发送请求。会话前缀在循环实例内保持稳定,而新建或恢复的实例可能建立不同前缀。
Agent 作用域的请求组合
模型看到的内容
通过 agent.ctx 进行的注册可以遮蔽提示词段或工具,也可以在未发布 setup 期间安装仅适用于该 agent 的拦截器。
Token 影响
此包自身不增加 token;带作用域贡献只影响该 agent,并在 dispose 时消失。
KV Cache 影响
只要 agent 的作用域注册不变,前缀就保持稳定。改变提示词段、工具定义或请求监听器的 setup 或 reload,可能从第一个受影响的请求 token 起使复用失效。
已知限制与暂缓事项
- 发起方作用域只存在于进程内:worker、子进程、HTTP、持久队列和重启必须显式传递所需身份。
- 环境身份可能比存活状态更久:消费方在生命周期敏感工作前,仍要检查
agent.status、取消状态和所属能力契约。 - 委派以外的 agent 间通道:共享状态、流式子输出和后台/轮询语义仍在当前同步
ctx.subagentsseam 之外。 agent/session-start不能为启动设置门禁:它仍是同步且不可 veto 的通知;必须在发布前完成的异步组合属于工厂的setup(agentCtx)事务。cancel()默认清空 inbox:它会中止正在处理的轮次以及排队和 steering 工作;cancel(cause, { keepInbox: true })只中止轮次并保留待处理项。仍不存在只中止步骤、同时让正在处理的轮次继续运行的操作(停止表层 Agent Note)。- 每条附加
UserMessage恰好携带一个MessageSource:多个插件合并到一次工具调用上的贡献会归入一个来源;无法表示混合来源。 SessionStartSource预留'clear'/'compact',但还没有发出方:在驱动子系统落地前,只会出现'startup'/'resume'(TODO(compaction))。