feat(subagent): continuable background subagents

Implement the continuable background subagents RFC: a durable child
session with a series of Task-backed activations, each disposing its
run before the Task settles.

- dsh-subagent: rename SubagentRun.sendMessage to strict steer, drop
  run-level resume, add SubagentProvider.resume dispatch via
  SubagentService.resume, the continuation start field, and the
  versioned model-hidden subagent/descriptor session event.
- dsh-subagent-inprocess/-spawn/-fork: publish the control-allocated
  child id, append the descriptor inside the initial turn, implement
  cold resume from the child's own transcript under the live parent
  scope, and strict running-only steer.
- dsh-subagent-control (new): SubagentControlService owning stable
  child ids, descriptor snapshot/fold/authorization, Task-backed
  activation with settle-then-dispose ordering, the process-local
  active-run association, and steer-or-resume sendMessage routing.
- dsh-tool-subagent: background route branches on the provider's
  resume capability (continuable via the control service; one-shot
  task for ACP), returning both child and task ids.
- dsh-tool-subagent-control (new): the globally named send_message
  tool rendering steered/started routes.

Keyless coverage spans Task ownership and disposal ordering, running
delivery, cold follow-up, descriptor rejection and rollback, known-id
reconstruction, kill during lookup, admission races, and a new
subagent-continuable ACP snapshot scenario.
This commit is contained in:
Dudu-0223
2026-07-23 17:07:38 +08:00
committed by imccyu
parent 76ff841279
commit 99a778d63f
83 changed files with 3167 additions and 627 deletions

View File

@@ -795,7 +795,7 @@ A ready child settled. Scope-filtered dispatch uses the same delegating parent c
Types: [Scoped](../core-data-structures/scope.md) · [SubagentService](../core-data-structures/subagent.md)
Source: [`packages/subagent/subagent/src/index.ts:140`](../../packages/subagent/subagent/src/index.ts)
Source: [`packages/subagent/subagent/src/index.ts:150`](../../packages/subagent/subagent/src/index.ts)
### `subagent/provider-added` — emit
@@ -812,7 +812,7 @@ A provider became resolvable in the registry.
Types: [SubagentProvider](../core-data-structures/subagent.md)
Source: [`packages/subagent/subagent/src/index.ts:114`](../../packages/subagent/subagent/src/index.ts)
Source: [`packages/subagent/subagent/src/index.ts:124`](../../packages/subagent/subagent/src/index.ts)
### `subagent/provider-removed` — emit
@@ -827,7 +827,7 @@ A provider left the registry. Accepted runs remain holder-owned.
'subagent/provider-removed'(name: string): void
```
Source: [`packages/subagent/subagent/src/index.ts:120`](../../packages/subagent/subagent/src/index.ts)
Source: [`packages/subagent/subagent/src/index.ts:130`](../../packages/subagent/subagent/src/index.ts)
### `subagent/start` — emit
@@ -849,7 +849,7 @@ A provider established a ready child. For in-process providers, `ctx.agents.get(
Types: [Scoped](../core-data-structures/scope.md) · [SubagentService](../core-data-structures/subagent.md)
Source: [`packages/subagent/subagent/src/index.ts:131`](../../packages/subagent/subagent/src/index.ts)
Source: [`packages/subagent/subagent/src/index.ts:141`](../../packages/subagent/subagent/src/index.ts)
## `system-prompt/*`

View File

@@ -1946,6 +1946,50 @@ async closeAll(): Promise<void>
Source: [`packages/storage/storage-domain/src/index.ts:69`](../../packages/storage/storage-domain/src/index.ts)
## `ctx.subagentControl` — `SubagentControlService`
The continuable-subagent orchestration service. Tool schema and UI adapters are consumers of this one contract: parent and human messages route through sendMessage and share one activation result and cancellation boundary, while foreground one-shot delegation keeps calling `ctx.subagents.start()` directly.
```ts cordis-catalog
/**
* Start a continuable background child: allocate its stable session id,
* snapshot its durable descriptor, and register the initial activation's
* Task. A synchronous validation failure (a non-JSON descriptor input,
* missing persistence, Task preflight) throws without creating a Task; the
* method otherwise returns both identities immediately, without waiting for
* child publication or descriptor durability. Asynchronous startup failure
* settles the returned Task as `failed` (or `killed` when cancelled) after
* any published run is disposed, which can leave an unmaterialized child id
* that later by-id operations report as unavailable.
* @param spec - provider, Task label, and the delegation request.
* @returns the stable child id and the initial activation's Task id.
*/
startContinuable(spec: ContinuableStartSpec): ContinuableStart
/**
* Deliver one message to a known continuable child: steer its running
* activation, or cold-resume the durable session into a fresh Task-backed
* activation. The two routes are reported distinctly so timing-dependent
* routing is observable. A throw means the message was NOT delivered — in
* particular, losing a race with Task settlement does not fall through to
* cold resume within the same call; a later retry after Task terminal may
* start the next activation. The started Task owns descriptor lookup and
* direct-parent authorization (its AbortSignal exists before that lookup),
* so an unknown, foreign, or descriptor-less child settles the started Task
* as `failed` with a detail reporting the id as unavailable.
* @param parent - the live parent agent sending the message (model tool or
* human adapter); Task access is authorized by its session id.
* @param childId - the stable child session id.
* @param message - the content to deliver.
* @returns whether the message `steered` the existing Task or `started` a new one.
*/
sendMessage(parent: Agent, childId: SessionId, message: ContentBlock[]): SendMessageResult
```
Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [ContinuableStart](../core-data-structures/subagent.md) · [ContinuableStartSpec](../core-data-structures/subagent.md) · [SendMessageResult](../core-data-structures/subagent.md) · [SessionId](../core-data-structures/core.md)
Source: [`packages/subagent/subagent-control/src/index.ts:152`](../../packages/subagent/subagent-control/src/index.ts)
## `ctx.subagents` — `SubagentService`
Named provider registry and capability-checked start surface.
@@ -1983,11 +2027,23 @@ list(): string[]
* @returns the ready holder-owned run.
*/
async start(name: string, request: SubagentStartRequest): Promise<SubagentRun>
/**
* Resume a persisted continuable child through the named provider's
* `resume` capability, with the same run lifecycle observation as
* {@link start}. The caller (the control service) has already loaded the
* child, folded its descriptor, and authorized the parent; this method owns
* only capability-checked dispatch.
* @param name - the provider recorded in the child's descriptor.
* @param request - the fully resolved resume request.
* @returns the fresh holder-owned run for the resumed activation.
*/
async resume(name: string, request: SubagentResumeRequest): Promise<SubagentRun>
```
Types: [SubagentProvider](../core-data-structures/subagent.md) · [SubagentRun](../core-data-structures/subagent.md) · [SubagentStartRequest](../core-data-structures/subagent.md)
Types: [SubagentProvider](../core-data-structures/subagent.md) · [SubagentResumeRequest](../core-data-structures/subagent.md) · [SubagentRun](../core-data-structures/subagent.md) · [SubagentStartRequest](../core-data-structures/subagent.md)
Source: [`packages/subagent/subagent/src/index.ts:181`](../../packages/subagent/subagent/src/index.ts)
Source: [`packages/subagent/subagent/src/index.ts:191`](../../packages/subagent/subagent/src/index.ts)
## `ctx.subprocess` — `SubprocessService` (abstract seam)