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.
4.3 KiB
@deepseek-ai/dsh-subagent-control
The continuable-subagent control service (ctx.subagentControl): the one orchestration path that binds a durable child session to a series of disposable Task-backed activations. Model tools and human-facing adapters call the same contract; the low-level ctx.subagents seam stays collection-, Task-, and persistence-agnostic.
Activation lifecycle
A continuable background subagent is a durable child session with a series of Task-backed activations. startContinuable() allocates the stable child session id before Task creation, snapshots the descriptor inputs (a non-JSON input throws with no Task), and registers the initial activation's Task; the provider publishes exactly that child id and appends the versioned subagent/descriptor event inside the child's first turn. Every activation — initial or resumed — creates a fresh Task whose settlement awaits the child result, disposes the run, and only then records the TaskOutcome: a terminal Task leaves the durable child session but no live child Agent.
sendMessage(parent, childId, message) owns steer-or-resume routing. A running activation receives live delivery through the run's strict steer capability and returns the existing Task id (steered); an absent activation starts a fresh Task that loads the persisted child, authorizes the recorded parentSession as the direct parent, folds the descriptor, and dispatches SubagentService.resume() (started). Failure throws and means the message was not delivered: losing a strict-steering race with Task settlement never falls through to cold resume within the same call, and a live registry Agent outside the activation association is an ownership conflict rather than an adoption target.
Cancellation targets the whole activation. task_kill or owner disposal aborts the Task-owned signal; before publication the provider rejects only after its creation transaction rolled back to quiescence, afterwards the signal cancels the published run, and settlement records killed only once the activation is quiescent. Human input shares this path: an adapter submits child input through sendMessage() under the loaded parent, so parent and human messages that joined one turn share its result and cancellation outcome, and TaskService.start()'s control-surface requirement applies (load @deepseek-ai/dsh-tool-tasks or attach a surface).
The activation association is process-local routing state, installed before any persistence or provider await and removed after run disposal and Task terminal publication. It is not a durable catalog: restart recovers the child session, not in-flight Tasks or their notifications.
Model Experience
Task completion and output
What the model sees
None directly, as this package registers no tool and no prompt text; the model observes continuable children through @deepseek-ai/dsh-tool-subagent's background acknowledgement, @deepseek-ai/dsh-tool-subagent-control's send_message results, and the generic task surface, whose outputs this service produces.
Token effect
None beyond the consuming tools' own results.
KV Cache effect
None; this service appends nothing to any model-visible sequence.
Known Limitations and Deferred Work
- Concurrent stopped-child admission is not atomic across awaits — the synchronous association install admits one activation per child in this process, but a caller bypassing the control service can still race it; the Agent registry's same-id collision is the final backstop, and the losing Task fails with its message not delivered.
- The association coordinates only one runtime — concurrent resume from multiple processes needs a persistence-level lease or compare-and-set, which no backend offers yet.
- Task records are process-local — restart recovers the durable child session, not an interrupted Task, its result, or its completion notice; durable Task recovery is a separate concern.
- Human interaction requires the exact live parent Agent — Task access is fenced by the owner session and owner disposal cancels its Tasks; standalone child conversations belong to the interactive-side-sessions proposal, not this Task-owned lifecycle.
- ACP children remain one-shot —
AcpProvider.resumeand per-child continuation advertisement are deferred until the remote-session descriptor contract is resolved.