DSH / Atlas
2026-07-27implementedsimplification

Intent-named subagent continuation operations

按意图命名的 subagent 继续执行操作

Merging continuable-child orchestration into `ctx.subagents` left provider dispatch and caller intent on the same public service. `resume(name, request)` accepted a descriptor, authorized parent, durable child id, and activation signal that only the internal continuation manager could resolve correctly. `sendMessage(...)` exposed transport wording rather than the `followup` intent already used by `Agent`, and its sep

English

Problem

Merging continuable-child orchestration into ctx.subagents left provider dispatch and caller intent on the same public service. resume(name, request) accepted a descriptor, authorized parent, durable child id, and activation signal that only the internal continuation manager could resolve correctly. sendMessage(...) exposed transport wording rather than the followup intent already used by Agent, and its separate source and signal parameters widened an operation every caller had to use atomically.

The durability boundary also exposed both SessionStore.flush() and flushRequired(). They performed the same scoped parallel dispatch and differed only in whether an empty listener snapshot was accepted, so the session interface encoded one consumer's policy as a second operation.

Decision

SubagentRuntime separates four execution intents: start(name, request) returns an ordinary holder-owned one-shot run; startContinuable(spec) establishes a durable child and returns its id plus the accepted initial MessageId; followup(parent, childId, content, { source, signal }) sends later parent content; and reportFrom(child, content, { delivery, signal }) sends selected child content to its direct parent. followup matches Agent.followup(), while SubagentRun.steer() remains the narrower confirmed live-run capability. The model-facing tools keep their stable send_message and report names and delegate routing to the corresponding intent methods.

Caller and provider requests are distinct. SubagentStartRequest contains caller-supplied one-shot data; ResolvedSubagentStartRequest adds the service-resolved descriptor before SubagentProvider.start(). For continuable creation, the manager passes a ContinuableCreateRequest to optional SubagentProvider.prepareContinuable() and receives detached creation data only. SubagentRuntime.resume() and provider resume dispatch are absent: the continuation manager loads the descriptor, authorizes the parent, and owns Agent materialization, prompt delivery, cold resume, and teardown.

SessionStore.flush(session) is the single durability barrier and returns Promise<boolean>. It resolves true after at least one scoped listener participates successfully, resolves false for an empty listener snapshot, and rejects with the first registered listener failure after all listeners settle. Participation cannot identify whether a selected persistence backend stored the state. Ordinary checkpoints may ignore the boolean; the continuation manager also treats its final flush as a best-effort barrier, deliberately ignores participation, logs rejection, and still disposes the child and releases ownership.

Alternatives considered

Keep public provider resume dispatch. No production caller outside the continuation manager owns descriptor lookup, direct-parent authorization, Agent materialization, Activation ownership, and child-first teardown. A public method would expose resolved implementation data without a valid independent intent; providers instead contribute detached first-creation data through prepareContinuable and never participate in cold resume.

Keep sendMessage on the service. The model tool sends a message, but the service operation represents a follow-up that may steer or cold-resume. followup aligns with the structural Agent interface and does not promise a particular route.

Keep flushRequired(). A second method hides only an empty-listener check. Returning participation from the existing barrier keeps dispatch in one implementation and lets each caller state whether absence is acceptable.

Fold ordinary and continuable starts together. A flag would make one method return either an awaited holder-owned one-shot run or immediate durable child and message identities. Separate intent methods preserve the ownership and timing distinction without a return union.

Consequences

  • The Cordis service catalog contains only caller operations; a provider can opt into continuable first creation through SubagentProvider.prepareContinuable?() without receiving Agent lifecycle authority or a public resume operation.
  • Follow-up source and cancellation travel in one options object, matching the intent-helper shape on Agent while retaining the existing live-delivery and cold-resume semantics.
  • Session durability has one barrier operation. Its participation result remains observable, but no continuable-child path treats arbitrary listener participation as proof that a persistence backend stored the state.
  • The send_message and report schemas, accepted message identities, AgentHandle ownership, durable event vocabulary, and model-visible transcript follow the activation-based realization linked above.

中文

问题

将可继续 child 的编排合并到 ctx.subagents 后,提供方分发与调用方意图共存于同一个公开服务中。resume(name, request) 接受描述符、已鉴权的 parent、持久化 child id 与激活信号,而只有内部继续执行管理器才能正确解析这些数据。sendMessage(...) 暴露的是传输层措辞,而不是 Agent 已采用的 followup 意图;它还将来源与信号拆成独立参数,扩大了操作接口,而每个调用方都必须以原子方式同时使用二者。

持久性边界还同时公开了 SessionStore.flush()flushRequired()。二者执行相同的作用域内并行分发,唯一差别是是否接受空的监听器快照,因此会话接口将一个消费方的策略编码为第二项操作。

决策

SubagentRuntime 分离四种执行意图:start(name, request) 返回普通的、由持有方负责的 one-shot run;startContinuable(spec) 建立持久化 child,并返回其 id 与已接受的初始 MessageIdfollowup(parent, childId, content, { source, signal }) 发送后续 parent 内容;reportFrom(child, content, { delivery, signal }) 将选定的 child 内容发送给其直接 parent。followupAgent.followup() 一致,而 SubagentRun.steer() 仍是范围更窄的能力,仅向已确认仍在运行的 run 提供 steering(中途引导)。面向模型的工具保留稳定的 send_messagereport 名称,并将路由委托给对应的意图方法。

调用方请求与提供方请求相互分离。SubagentStartRequest 包含调用方提供的 one-shot 数据;ResolvedSubagentStartRequest 会在调用 SubagentProvider.start() 前加入由服务解析的描述符。创建可继续 child 时,管理器将 ContinuableCreateRequest 传给可选的 SubagentProvider.prepareContinuable(),且只接收分离的创建数据。SubagentRuntime.resume() 与提供方恢复分发均不存在:继续执行管理器加载描述符、对 parent 进行鉴权,并负责 Agent 实体化、提示词投递、冷恢复与 teardown。

SessionStore.flush(session) 是唯一的持久性屏障,并返回 Promise<boolean>。至少一个作用域内监听器成功参与后,它解析为 true;监听器快照为空时解析为 false;所有监听器结算后,如有失败,则以注册顺序最靠前的监听器错误拒绝。参与结果无法表明所选的持久化后端是否已经存储状态。普通检查点可以忽略该布尔值;继续执行管理器同样将最终 flush 视为 best-effort 屏障,有意忽略参与结果,记录拒绝日志,并仍会对 child 执行 dispose(资源释放)并释放所有权。

已考虑的替代方案

保留公开的提供方恢复分发。 继续执行管理器之外,没有任何生产调用方同时负责安全调用所需的描述符查找、直接 parent 鉴权、Agent 实体化、Activation 所有权与 child-first teardown。公开方法会暴露已解析的实现数据,却没有合理的独立调用意图;提供方改为通过 prepareContinuable 贡献分离的首次创建数据,且从不参与冷恢复。

在服务上保留 sendMessage 面向模型的工具发送消息,但服务操作表达的是后续操作,既可能对运行中的激活执行 steering,也可能从持久化存储恢复。followup 与结构化 Agent 接口保持一致,也不承诺特定路由。

保留 flushRequired() 第二个方法只封装了空监听器检查。由现有屏障返回是否有监听器参与,可以让分发只保留一套实现,并让每个调用方自行判定缺少监听器是否可接受。

合并普通启动与可继续启动。 一个标志会让同一方法要么等待由持有方负责的 one-shot run 就绪后返回,要么立即返回持久化 child 与消息标识。按意图拆分的方法无需返回值联合类型即可保留所有权与时序差异。

影响

  • Cordis 服务目录只包含调用方操作;提供方可以通过 SubagentProvider.prepareContinuable?() 选择参与可继续 child 的首次创建,但不会获得 Agent 生命周期权限或公开恢复操作。
  • 后续操作的来源与取消信号通过同一个选项对象传递,与 Agent 上按意图命名的辅助方法形态一致,同时保留在线投递与从持久化存储恢复的语义。
  • 会话持久性只有一个屏障操作。参与结果仍可观测,但任何可继续 child 路径都不会将任意监听器参与视为持久化后端已存储状态的证明。
  • send_messagereport schema、已接受的消息标识、AgentHandle 所有权、持久化事件词汇与模型可见的 transcript(文本记录)遵循上文链接的基于 Activation 的实现。