refactor(subagent): narrow continuation interface
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/core-data-structures/subagent.md
|
||||
subagent.md: 1321429ac9e6280878016601646dd08981ab2b40
|
||||
subagent.zh.md: 072b2d2c1635d7c2c59b5a24d2bafc6ee32f8422
|
||||
subagent.md: 2dc25dfb14b1506edf7f53f6ce0d8681fefa98c6
|
||||
subagent.zh.md: 00f2748ad92ae37b0a2fe2616d9e052f9c4b916f
|
||||
|
||||
@@ -39,8 +39,8 @@ The tool layer builds this request from the model input and its own config; the
|
||||
/**
|
||||
* What a caller asks for when starting a subagent. The tool layer builds this
|
||||
* from the model's `{ description, prompt }` plus its own config; the service
|
||||
* validates {@link SubagentCapabilities} against the named provider, then
|
||||
* passes it to {@link SubagentProvider.start}.
|
||||
* validates {@link SubagentCapabilities} against the named provider and
|
||||
* resolves a {@link SubagentProviderStartRequest} for dispatch.
|
||||
*/
|
||||
interface SubagentStartRequest {
|
||||
/** Content delivered as the child's user message. */
|
||||
@@ -89,23 +89,36 @@ interface SubagentStartRequest {
|
||||
* persona (strict `{{…}}` interpolation against the registered variables).
|
||||
*/
|
||||
readonly persona?: string
|
||||
/**
|
||||
* Continuable-child intent, resolved by `ctx.subagents` before start.
|
||||
* The provider MUST publish exactly `sessionId` as the child identity
|
||||
* instead of allocating one internally, and MUST append the snapshotted
|
||||
* `descriptor` as the child's turn-enclosed `subagent/descriptor` event
|
||||
* before its first request. Requires {@link SubagentProvider.resume} (the
|
||||
* continuation capability); the service rejects the request otherwise.
|
||||
*/
|
||||
readonly continuation?: SubagentContinuation
|
||||
}
|
||||
```
|
||||
|
||||
`signal` is the single cancellation channel before and after readiness. The [subagent composition-controls Agent Note](../../.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md) owns the persona, live global-tool filter, absolute-depth, and visibility-not-authority rationale.
|
||||
|
||||
## Continuable children: `SubagentContinuation` and `SubagentResumeRequest`
|
||||
Providers receive a separate resolved shape. Raw `SubagentService.start()` clears continuation state, while `startContinuable()` alone supplies the service-allocated identity and descriptor.
|
||||
|
||||
A **continuable background subagent** is a durable child session with a series of Task-backed activations. `SubagentService.startContinuable()` allocates the stable child id, snapshots the versioned `subagent/descriptor` payload, and passes both through the resolved start request; the provider publishes exactly that id and appends the descriptor before the initial prompt is admitted. `SubagentService.sendMessage()` loads and authorizes a stopped child before dispatching a fully resolved resume request through the raw `resume()` operation, or steers its live activation. An internal manager owns descriptor lookup and Task association only while `ctx.tasks` and `ctx.agents` exist; persistence is required per continuation operation, not to load the provider registry. `startContinuable()` returns both identities, while `sendMessage()` reports whether the message `steered` the existing Task or `started` a fresh one. Every sender supplies a `MessageSource` and cancellation signal; abort while live delivery awaits admission cancels the shared activation and rejects after quiescence. The optional model-facing tool uses `CoordinatorMessageSource` and its tool-execution signal, while a human adapter uses `{ kind: 'user' }` and its interaction signal.
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Provider-facing start request after the service resolves optional
|
||||
* continuation state. Ordinary callers use {@link SubagentStartRequest}; only
|
||||
* the Task-backed continuation path can attach a stable child identity and
|
||||
* durable descriptor.
|
||||
*/
|
||||
interface SubagentProviderStartRequest extends SubagentStartRequest {
|
||||
/**
|
||||
* Continuable-child state resolved by `ctx.subagents` before provider dispatch.
|
||||
* The provider MUST publish exactly `sessionId` as the child identity
|
||||
* instead of allocating one internally, and MUST append the snapshotted,
|
||||
* model-hidden `subagent/descriptor` before the initial prompt is admitted.
|
||||
* Requires {@link SubagentProvider.resume} (the
|
||||
* continuation capability); the service rejects the request otherwise.
|
||||
*/
|
||||
readonly continuation?: SubagentContinuation | undefined
|
||||
}
|
||||
```
|
||||
|
||||
## Continuable children and provider resume
|
||||
|
||||
A **continuable background subagent** is a durable child session with a series of Task-backed activations. `SubagentService.startContinuable()` allocates the stable child id, snapshots the versioned `subagent/descriptor` payload, and passes both through the provider-facing start request; the provider publishes exactly that id and appends the descriptor before the initial prompt is admitted. `SubagentService.followup()` mirrors the intent verb on `Agent`: it steers a live activation or privately dispatches a resolved provider resume after loading and authorizing a stopped child. An internal manager owns descriptor lookup and Task association only while `ctx.tasks` and `ctx.agents` exist; persistence is required per continuation operation, not to load the provider registry. `startContinuable()` returns both identities, while `followup()` reports whether the content `steered` the existing Task or `started` a fresh one. Every sender supplies a `MessageSource` and cancellation signal through one options object; abort while live delivery awaits admission cancels the shared activation and rejects after quiescence. The optional model-facing tool uses `CoordinatorMessageSource` and its tool-execution signal, while a human adapter uses `{ kind: 'user' }` and its interaction signal.
|
||||
|
||||
```ts type-equiv
|
||||
/** Attribution for a model coordinator's follow-up to one of its children. */
|
||||
@@ -118,8 +131,33 @@ interface CoordinatorMessageSource {
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* The resolved continuable-child identity and durable composition record a
|
||||
* continuation caller attaches to a start request.
|
||||
* Options for following up with one continuable child.
|
||||
*/
|
||||
interface SubagentFollowupOptions {
|
||||
/** Durable attribution retained on either live or resumed delivery. */
|
||||
readonly source: MessageSource
|
||||
/** Caller cancellation for a live-delivery admission wait. */
|
||||
readonly signal: AbortSignal
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* How a continuable follow-up was routed:
|
||||
* `steered` joined the running activation's existing Task without creating a
|
||||
* Task of its own; `started` created a fresh Task that cold-resumes the
|
||||
* durable child with the content. Failure is an exception, never a result —
|
||||
* undelivered content throws.
|
||||
*/
|
||||
type SubagentFollowupResult =
|
||||
| { readonly route: 'steered'; readonly taskId: TaskId }
|
||||
| { readonly route: 'started'; readonly taskId: TaskId }
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* The resolved continuable-child identity and durable composition record the
|
||||
* service attaches before provider dispatch.
|
||||
*/
|
||||
interface SubagentContinuation {
|
||||
/** Service-allocated stable child session id, published verbatim. */
|
||||
@@ -131,14 +169,13 @@ interface SubagentContinuation {
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* What a caller asks for when resuming a persisted continuable child. The
|
||||
* continuation manager loads the child log, folds and authorizes its descriptor,
|
||||
* and passes this fully resolved request to
|
||||
* {@link SubagentService.resume}, which dispatches to
|
||||
* Provider-facing request for reconstructing a persisted continuable child.
|
||||
* The continuation manager loads the child log, folds and authorizes its
|
||||
* descriptor, then privately dispatches this resolved request to
|
||||
* {@link SubagentProvider.resume}. The provider reconstructs the declared
|
||||
* composition under the live parent's scope and drives one turn with `prompt`.
|
||||
*/
|
||||
interface SubagentResumeRequest {
|
||||
interface SubagentProviderResumeRequest {
|
||||
/** The persisted child session id to resume. */
|
||||
readonly sessionId: SessionId
|
||||
/** The follow-up message that starts the resumed activation's turn. */
|
||||
@@ -295,22 +332,22 @@ interface SubagentProvider {
|
||||
* fulfillment, the provider owns and cleans all partial resources before this
|
||||
* promise rejects. Ownership transfers to the caller only on fulfillment.
|
||||
*/
|
||||
start(request: SubagentStartRequest): Promise<SubagentRun>
|
||||
start(request: SubagentProviderStartRequest): Promise<SubagentRun>
|
||||
/**
|
||||
* OPTIONAL (continuation capability): reconstruct a persisted continuable
|
||||
* child from its own transcript and declared descriptor, drive one
|
||||
* follow-up turn, and return a fresh run. Method presence is the capability
|
||||
* — the service rejects `resume` dispatch and continuable starts on
|
||||
* — the service rejects continuable starts and cold-resume dispatch on
|
||||
* providers without it. Same publication contract as {@link start}: if
|
||||
* reconstruction fails or `request.signal` aborts before fulfillment, the
|
||||
* provider rolls its creation transaction back to quiescence before
|
||||
* rejecting; after fulfillment the same signal cancels the published run.
|
||||
*/
|
||||
resume?(request: SubagentResumeRequest): Promise<SubagentRun>
|
||||
resume?(request: SubagentProviderResumeRequest): Promise<SubagentRun>
|
||||
}
|
||||
```
|
||||
|
||||
`start()` fulfills only with a ready run; `resume()` shares the same publication and lifecycle-observation contract. The service mints a unique `runId`, snapshots `local` from the provider's exact `localAgent`, observes the result, emits `subagent/start`, and returns the same run; rejection implies provider cleanup and emits no lifecycle pair. The paired `subagent/end` carries the same identity and the final output or infrastructure failure. Both events are observe-only and contain listener exceptions.
|
||||
Provider `start()` fulfills only with a ready run; provider `resume()` shares the same publication and lifecycle-observation contract but is dispatched only by the continuation manager. The service mints a unique `runId`, snapshots `local` from the provider's exact `localAgent`, observes the result, emits `subagent/start`, and returns the same run; rejection implies provider cleanup and emits no lifecycle pair. The paired `subagent/end` carries the same identity and the final output or infrastructure failure. Both events are observe-only and contain listener exceptions.
|
||||
|
||||
## In-process backends: depth and seed
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ interface SubagentCapabilities {
|
||||
/**
|
||||
* What a caller asks for when starting a subagent. The tool layer builds this
|
||||
* from the model's `{ description, prompt }` plus its own config; the service
|
||||
* validates {@link SubagentCapabilities} against the named provider, then
|
||||
* passes it to {@link SubagentProvider.start}.
|
||||
* validates {@link SubagentCapabilities} against the named provider and
|
||||
* resolves a {@link SubagentProviderStartRequest} for dispatch.
|
||||
*/
|
||||
interface SubagentStartRequest {
|
||||
/** Content delivered as the child's user message. */
|
||||
@@ -89,23 +89,36 @@ interface SubagentStartRequest {
|
||||
* persona (strict `{{…}}` interpolation against the registered variables).
|
||||
*/
|
||||
readonly persona?: string
|
||||
/**
|
||||
* Continuable-child intent, resolved by `ctx.subagents` before start.
|
||||
* The provider MUST publish exactly `sessionId` as the child identity
|
||||
* instead of allocating one internally, and MUST append the snapshotted
|
||||
* `descriptor` as the child's turn-enclosed `subagent/descriptor` event
|
||||
* before its first request. Requires {@link SubagentProvider.resume} (the
|
||||
* continuation capability); the service rejects the request otherwise.
|
||||
*/
|
||||
readonly continuation?: SubagentContinuation
|
||||
}
|
||||
```
|
||||
|
||||
`signal` 是就绪前后唯一的取消通道。[subagent 组合控制 Agent Note](../../.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md)规定 persona、live 全局工具过滤、绝对深度以及「可见性而非权限」的设计理由。
|
||||
|
||||
## 可继续子 agent:`SubagentContinuation` 与 `SubagentResumeRequest`
|
||||
提供方会接收单独的已解析请求类型。直接调用 `SubagentService.start()` 会清除继续执行状态;只有 `startContinuable()` 才会提供由服务分配的标识和描述符。
|
||||
|
||||
**可继续后台 subagent** 是一份持久化子 agent 会话,由一系列由 Task 支撑的激活组成。`SubagentService.startContinuable()` 会分配稳定的子 agent id、对版本化的 `subagent/descriptor` payload 建立快照,并通过已解析的启动请求传入二者;提供方会准确发布该 id,并在初始 prompt 获准前追加描述符。`SubagentService.sendMessage()` 会先加载并授权已停止的子 agent,再通过底层 `resume()` 操作分发完全解析的恢复请求,或引导其实时激活。只有 `ctx.tasks` 和 `ctx.agents` 存在时,内部管理器才会负责描述符查找与 Task 关联;每项继续执行操作都要求持久化,而加载提供方注册表不要求持久化。`startContinuable()` 返回两个标识,`sendMessage()` 则报告消息是对现有 Task 执行了 `steered`,还是 `started` 一个新 Task。每个发送方都会提供 `MessageSource` 和取消信号;若在在线投递等待准入期间中止该信号,则会取消共享激活,并在其完全停稳后拒绝调用。可选的面向模型工具使用 `CoordinatorMessageSource` 及其工具执行信号,人工适配器则使用 `{ kind: 'user' }` 及其交互信号。
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Provider-facing start request after the service resolves optional
|
||||
* continuation state. Ordinary callers use {@link SubagentStartRequest}; only
|
||||
* the Task-backed continuation path can attach a stable child identity and
|
||||
* durable descriptor.
|
||||
*/
|
||||
interface SubagentProviderStartRequest extends SubagentStartRequest {
|
||||
/**
|
||||
* Continuable-child state resolved by `ctx.subagents` before provider dispatch.
|
||||
* The provider MUST publish exactly `sessionId` as the child identity
|
||||
* instead of allocating one internally, and MUST append the snapshotted,
|
||||
* model-hidden `subagent/descriptor` before the initial prompt is admitted.
|
||||
* Requires {@link SubagentProvider.resume} (the
|
||||
* continuation capability); the service rejects the request otherwise.
|
||||
*/
|
||||
readonly continuation?: SubagentContinuation | undefined
|
||||
}
|
||||
```
|
||||
|
||||
## 可继续子 agent 与提供方恢复
|
||||
|
||||
**可继续后台 subagent** 是一份持久化子 agent 会话,由一系列由 Task 支撑的激活组成。`SubagentService.startContinuable()` 会分配稳定的子 agent id、对版本化的 `subagent/descriptor` payload 建立快照,并通过面向提供方的启动请求传入二者;提供方会准确发布该 id,并在初始 prompt 获准前追加描述符。`SubagentService.followup()` 沿用 `Agent` 的意图动词:它会引导实时激活,或在加载并授权已停止的子 agent 后,仅在内部向提供方分发已解析的恢复请求。只有 `ctx.tasks` 和 `ctx.agents` 存在时,内部管理器才会负责描述符查找与 Task 关联;每项继续执行操作都要求持久化,而加载提供方注册表不要求持久化。`startContinuable()` 返回两个标识,`followup()` 则报告内容是对现有 Task 执行了 `steered`,还是 `started` 一个新 Task。每个发送方都通过一个选项对象提供 `MessageSource` 和取消信号;若在在线投递等待准入期间中止该信号,则会取消共享激活,并在其完全停稳后拒绝调用。可选的面向模型工具使用 `CoordinatorMessageSource` 及其工具执行信号,人工适配器则使用 `{ kind: 'user' }` 及其交互信号。
|
||||
|
||||
```ts type-equiv
|
||||
/** Attribution for a model coordinator's follow-up to one of its children. */
|
||||
@@ -118,8 +131,33 @@ interface CoordinatorMessageSource {
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* The resolved continuable-child identity and durable composition record a
|
||||
* continuation caller attaches to a start request.
|
||||
* Options for following up with one continuable child.
|
||||
*/
|
||||
interface SubagentFollowupOptions {
|
||||
/** Durable attribution retained on either live or resumed delivery. */
|
||||
readonly source: MessageSource
|
||||
/** Caller cancellation for a live-delivery admission wait. */
|
||||
readonly signal: AbortSignal
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* How a continuable follow-up was routed:
|
||||
* `steered` joined the running activation's existing Task without creating a
|
||||
* Task of its own; `started` created a fresh Task that cold-resumes the
|
||||
* durable child with the content. Failure is an exception, never a result —
|
||||
* undelivered content throws.
|
||||
*/
|
||||
type SubagentFollowupResult =
|
||||
| { readonly route: 'steered'; readonly taskId: TaskId }
|
||||
| { readonly route: 'started'; readonly taskId: TaskId }
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* The resolved continuable-child identity and durable composition record the
|
||||
* service attaches before provider dispatch.
|
||||
*/
|
||||
interface SubagentContinuation {
|
||||
/** Service-allocated stable child session id, published verbatim. */
|
||||
@@ -131,14 +169,13 @@ interface SubagentContinuation {
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* What a caller asks for when resuming a persisted continuable child. The
|
||||
* continuation manager loads the child log, folds and authorizes its descriptor,
|
||||
* and passes this fully resolved request to
|
||||
* {@link SubagentService.resume}, which dispatches to
|
||||
* Provider-facing request for reconstructing a persisted continuable child.
|
||||
* The continuation manager loads the child log, folds and authorizes its
|
||||
* descriptor, then privately dispatches this resolved request to
|
||||
* {@link SubagentProvider.resume}. The provider reconstructs the declared
|
||||
* composition under the live parent's scope and drives one turn with `prompt`.
|
||||
*/
|
||||
interface SubagentResumeRequest {
|
||||
interface SubagentProviderResumeRequest {
|
||||
/** The persisted child session id to resume. */
|
||||
readonly sessionId: SessionId
|
||||
/** The follow-up message that starts the resumed activation's turn. */
|
||||
@@ -297,22 +334,22 @@ interface SubagentProvider {
|
||||
* fulfillment, the provider owns and cleans all partial resources before this
|
||||
* promise rejects. Ownership transfers to the caller only on fulfillment.
|
||||
*/
|
||||
start(request: SubagentStartRequest): Promise<SubagentRun>
|
||||
start(request: SubagentProviderStartRequest): Promise<SubagentRun>
|
||||
/**
|
||||
* OPTIONAL (continuation capability): reconstruct a persisted continuable
|
||||
* child from its own transcript and declared descriptor, drive one
|
||||
* follow-up turn, and return a fresh run. Method presence is the capability
|
||||
* — the service rejects `resume` dispatch and continuable starts on
|
||||
* — the service rejects continuable starts and cold-resume dispatch on
|
||||
* providers without it. Same publication contract as {@link start}: if
|
||||
* reconstruction fails or `request.signal` aborts before fulfillment, the
|
||||
* provider rolls its creation transaction back to quiescence before
|
||||
* rejecting; after fulfillment the same signal cancels the published run.
|
||||
*/
|
||||
resume?(request: SubagentResumeRequest): Promise<SubagentRun>
|
||||
resume?(request: SubagentProviderResumeRequest): Promise<SubagentRun>
|
||||
}
|
||||
```
|
||||
|
||||
`start()` 仅在 run 就绪时 fulfill;`resume()` 采用相同的发布与生命周期观察契约。服务铸造唯一 `runId`,从提供方的确切 `localAgent` 快照 `local`,观察结果,emit `subagent/start`,并返回同一个 run;rejection 意味着提供方已清理,且不会 emit 生命周期事件对。配对的 `subagent/end` 携带相同标识与最终输出或基础设施失败。两个事件都仅用于观察,每个 listener 异常都会被独立隔离。
|
||||
提供方的 `start()` 仅在 run 就绪时 fulfill;提供方的 `resume()` 采用相同的发布与生命周期观察契约,但只有继续执行管理器会分发它。服务铸造唯一 `runId`,从提供方的确切 `localAgent` 快照 `local`,观察结果,emit `subagent/start`,并返回同一个 run;rejection 意味着提供方已清理,且不会 emit 生命周期事件对。配对的 `subagent/end` 携带相同标识与最终输出或基础设施失败。两个事件都仅用于观察,每个 listener 异常都会被独立隔离。
|
||||
|
||||
## 进程内后端:深度与种子
|
||||
|
||||
|
||||
Reference in New Issue
Block a user