DSH / PLUGIN / ORCHESTRATION

dsh-subagent

v0.1.0-rc.5deepseek-ai / deepseek-harness47f943859b

Included in DSHPluginsWorkflow & orchestrationBuilt-in source
Runtime anatomy
HOSTCLIENTUITOOLDATAFLOW

Overview

dsh-subagent

The subagent seam lets one agent delegate work to a child through a named provider. Callers use one service API (ctx.subagents); providers decide whether the child runs in this process, in another process, or through a future transport.
BUILT-IN / ATOMIC
Already shipped with DSH — no separate install

This is an atomic module already shipped with Harness, not a standalone profile layer.

Capabilities

What it contributes

HostCordis loadableZero-config
Client / UIHost only0 contributions
Model tools0None declared
Profile stateenabledbase, headless, web

README / EN

Package documentation

@deepseek-ai/dsh-subagent

English | 中文

The subagent seam lets one agent delegate work to a child through a named provider. Callers use one service API (ctx.subagents); providers decide whether the child runs in this process, in another process, or through a future transport.

The subagent family overview maps implementations and model-facing consumers. This package owns the provider registry, shared request and result contracts, durable descriptors, and continuable-child orchestration. Multiple named providers may coexist behind that contract.

Service API

SubagentRuntime has these operations:

Member Meaning
registerProvider(provider) Register one trusted same-process implementation by name. Registration is effect-scoped; removing it prevents new starts but does not revoke runs already returned to callers. Duplicate names fail loud.
getProvider(name) Return the provider, or undefined when absent.
list() Return provider names in insertion order.
start(name, request) Validate an ordinary caller request, resolve its detached one-shot descriptor, then await the provider until a real one-shot child is published. Fulfillment returns a holder-owned SubagentRun; rejection means the provider has already cleaned every unpublished startup resource, while post-publication turn or infrastructure faults settle through the run. Continuable children never enter through this operation.
startContinuable(spec) Establish one durable continuable child and deliver its initial prompt. Resolves with { childId, messageId } when the child's inbox accepts that prompt, without waiting for the turn to start or for the message to reach the Session log; any earlier failure rejects with no ids and rolls the child back entirely. Requires ctx.agents, session persistence, and a provider with the prepareContinuable capability.
followup(parent, childId, content, { source, signal }) Deliver one later message from the exact live direct parent as the child's next FIFO turn, matching Agent.followup() terminology, and return the accepted MessageId. A resident child's inbox accepts it directly (waking a waiting Activation); an absent one cold-resumes from its persisted Session. Requires ctx.agents; cold resume also requires session persistence.
interrupt(targetSessionId, authority) Interrupt one live continuable child's current turn under a human durable parent address ({ kind: 'user', parentSessionId }) or an exact live ancestor Agent ({ kind: 'ancestor', agent }). Admission is synchronous and the effect asynchronous: it issues Agent.cancel(cause, { keepInbox: true }) and returns without waiting for the target to observe the signal. Unclaimed pending inbox work, the Activation, and published descendants are preserved; work already claimed into the interrupted turn is not requeued. An absent target is an accepted no-op; a wrong parent address or a stale, self-targeting, or non-ancestor caller rejects with UNAUTHORIZED.
reportFrom(child, content, { delivery, signal }) Deliver one selected message from the exact live continuable child to its exact live direct parent and return the accepted stable MessageId. Quiet delivery injects context; waking delivery submits one later parent turn.
registerContinuableSetup(contribution) Compose an optional deployment capability into each continuable child's unpublished scope, with immediate revocation from resident children.
drainContinuableDescendants(parents) Close admission below exact live host-owned parent Agents, stop only their visible continuable descendants, await materializations admitted below those roots through publication or rollback, then release the selected forests child-first. The cutoff lasts until each exact parent leaves the registry; unrelated parent forests and manager-wide admission remain live.
listChildren(parentSessionId, signal?) List direct session-backed subagents with their one-shot/continuable mode, running/inactive activity, origin-classified one-level hasChildren hint, and per-child diagnostics, ordered by createdAt then id, without loading or resuming them. Reads the live session store and optional session persistence directly (live-only enumeration when persistence is absent) and requires the mounted sessionProjections registry; it does not require ctx.agents, the continuation manager, or any query service.
listDescendants(rootSessionId, signal?) Flatten the root's complete session tree in stable pre-order from the same live-preferred corpus, adding each subagent entry's durable parentId and root-relative depth. Ordinary sessions and one-shot children remain traversal nodes so continuable descendants below them are discovered. Identity, diagnostics, dependencies, and cancellation follow listChildren().

SubagentStartRequest.label is an optional short durable display label for a session-backed one-shot child. Model-facing delegation supplies its existing description; lower-level callers need not invent presentation metadata. Continuable starts always carry their own required label. signal is required and is the canonical cancellation channel for a one-shot start. An abort before publication makes start() reject after rollback; an abort after publication cancels the returned run's remaining turn work without hiding its id. The request may also select a model, require structured output, cap delegation depth, restrict child tools, or set a child persona. For a continuable start or follow-up, the caller signal owns lookup, materialization, and admission only until inbox acceptance; afterward the manager owns the Activation independently, so later caller cancellation neither cancels the accepted turn nor disposes the child.

Follow-up authority comes from the exact live direct parent recorded in the child's durable header. Cold resume checks that authority before reconstruction and again in the final no-await inbox-admission span, so a parent unregistered or replaced during materialization cannot authorize delivery. The source on a follow-up records who supplied the delivered message and grants no authority.

Same-process requests, descriptors, results, and event payloads are trusted typed values borrowed as immutable. The service does not clone or freeze them; serialization and hostile-input validation belong at actual process, worker, persistence, and model boundaries.

Capabilities

Start-time features are advertised in provider.capabilities because the service must reject an unsupported one-shot request before child creation:

  • outputSchema — enforce a structured final result.
  • depthLimit — enforce maxDepth.
  • toolFilter — apply the requested child tool restriction.
  • persona — apply a per-child persona.

Every in-process child is composed by one call, applyChildComposition(childCtx, parent, composition), which joins the parent's agent-preset composition before applying the child's own persona and tool filter. The join is what gives the child its capabilities: with every model-facing row on the agent plane, a child that joined nothing would reach the model with an empty tool registry (dsh-agent-presets). Taking the parent as a parameter is deliberate — it makes composing a child WITHOUT that join unrepresentable at the call sites, which is the defect the one call exists to prevent. A deployment composing no preset roster joins nothing and needs nothing: its model-facing rows sit in the host composition, where the child already resolves them through the tool registry's global layer.

childSessionMeta() records the joined preset id on the child's durable header for the same reason a top-level session records its own: the preset decides the tool schemas and prompt sections the model saw, so a cold read of the child's history has to rebuild that composition rather than the deployment default. It is read from the parent's live scope chain, not from the parent header, because a parent that switched preset while blank runs on the newer composition while its header still names the older one.

Continuable creation is the optional SubagentProvider.prepareContinuable?() method: its presence is the capability check, so the service rejects a configured continuable start on a provider without it, while a provider that has it may still serve ordinary one-shot delegations. The method returns only a detached ContinuableCreateSpec ({ seed? }) — data, never a capability: it carries no Agent, AgentHandle, prompt delivery, result, disposal, or resume operation, because the continuation manager owns identity reservation, composition, Agent creation, prompt delivery, cold resume, ownership, and disposal after preparation. A one-shot SubagentRun represents one disposable foreground delegation with one result and no cold-resume operation. The service may invoke one provider concurrently for distinct siblings: each start or preparation owns its mutable state and cancellation path, and one operation's failure, result, or cleanup must not settle or release another. A provider may queue its own capacity internally without changing that independence contract.

The durable descriptor

The Service Definition owns the versioned subagent/descriptor session event vocabulary (src/descriptor.ts): snapshotSubagentDescriptor() validates and detaches the record before provider work, and foldSubagentDescriptor() validates the complete current-version payload before recovering it from a loaded child log. Every local session-backed start appends one descriptor with the provider name and lifecycle mode. A one-shot descriptor optionally carries the caller-owned durable display label; a continuable descriptor requires its durable creation label and additionally records resolved child agentOptions.provider/model and optional persona/toolFilter for cold resume. These are explicit fields, never the merge-extensible AgentOptions object, so an unrelated extension value cannot break continuation. The descriptor omits subagentDepth (the persisted header's delegationDepth is the monotone floor) and outputSchema (an Activation's result contract). The event is log-only: no surfaceOp, absent from model history, and retained by the append-only log across compaction. Malformed current-version payloads are corrupt; unsupported versions cannot be classified by this runtime.

Delegation depth

The seam owns the depth vocabulary shared by Service Providers and Consumers: the AgentOptions.subagentDepth declaration, assertSubagentMaxDepth, and delegationDepthOf(agent). The persisted SessionHeader.delegationDepth is authoritative and monotone — runtime options may deepen the count but never lower it, so a resumed child cannot be re-counted as top-level.

inheritsParentContext is descriptive rather than enforceable. It says only whether the child sees completed parent conversation history (fork does; spawn and the out-of-process one-shot providers do not), not whether it inherits tools, services, or authority.

Delegated policy

Both in-process delegation paths fix the child's permission scope at the delegation boundary through the shared child-agent helpers. captureDelegatedPolicyOverrides(parent) snapshots the parent session's explicit sandbox override (sandboxPolicy.overrideOf()) and pins the child's approval policy to 'never' whenever the approval capability is composed — regardless of the parent's own policy — so a delegated child acts only within its inherited sandbox scope and every ask (for example a sandbox_permissions escalation) is rejected deterministically instead of waiting on a prompt no one is watching (both services are optional ctx.get consumers). appendDelegatedPolicyOverrides() writes each value onto the child's own log as a source: 'delegation' sandbox/mode or approval/policy event during unpublished setup, after any fork seed — so fresh policy wins stale seed state and the child's effective policy stays reconstructable from its log alone. The sandbox deployment default is never copied: an unswitched parent stamps no sandbox/mode and its child follows the deployment default dynamically. A continuable start captures before its first await and seeds only fresh materialization; a cold resume replays the persisted delegation events instead of re-capturing the parent, so a parent switch after creation never retroactively changes a durable child. Every in-process child also receives a scoped runtime-context statement (subagent:delegation) telling it the scope is fixed and that a task needing wider access ends with a reported limitation, not retries. See the one-shot and continuable delegation-policy Agent Notes.

One-shot ownership and lifecycle

provider.start(request): Promise<SubagentRun> is the ownership-transfer boundary; the delegation tool also uses it inside its one-shot Task-backed background path. Before fulfillment, the provider owns setup and must cancel, roll back, and quiesce unpublished resources on every failure. After fulfillment, the caller owns the run and must call dispose() on every path; remaining prompt and turn work belongs to SubagentRun.result.

SubagentRun.result resolves to { output, structured?, stopReason }. Child-level failures resolve with a non-completed reason; only an infrastructure fault that the seam cannot represent may reject. dispose() is idempotent, cancels remaining work, and waits for both result settlement and child-resource quiescence. A result rejection remains on result; dispose() rejects only for an independent resource-release failure. output and the subagent/end event's lastAssistantMessage use the exported AssistantOutputFold/finalAssistantOutput helpers to select the child's last non-empty assistant message, or its accumulated assistant text when no such message exists. output is [] and the event field is absent when the child produced neither (SubagentResult.output owns the result contract).

A local run publishes an ordinary child agent/session before start() fulfills, returns that shared session id as SubagentRun.id, exposes the exact child as SubagentRun.localAgent, records request.parent.session.id in the child's parentSession header, and appends the resolved descriptor inside its initial turn. Remote providers instead mint a parent-scoped lifecycle id and return localAgent: undefined; without a local child session, their one-shot runs are not part of trace-backed enumeration.

Continuable children and Activations

A continuable child has one durable Session and at most one process-local Activation — one residency epoch for a reconstructed child Agent, not a request, result, cancellation, or Task boundary. The Agent inbox is the only turn queue, so the continuation manager owns residency while the Agent loop owns all turn ordering and execution. No continuable path creates a Task or an intermediate result-bearing wrapper.

The manager derives three internal residency conditions from Agent quiescence and the owned-child set rather than maintaining a second state machine: running (an active admission, open turn, or waking inbox work), waiting (quiescent but still owning at least one undisposed child), and settled (quiescent with every owned child disposed, so the manager disposes the AgentHandle and removes the Activation). Every continuation message uses Agent.followup() and becomes one FIFO turn with no steering of the current turn. Routing depends only on residency: running enqueues, waiting wakes the same Agent, and an absent Activation cold-resumes a new one.

The manager reserves the child identity, resolves the durable descriptor, calls ctx.agents.create() (or ctx.agents.resume() for cold resume) through a private activation-owner scope, installs the returned AgentHandle in the Activation, establishes any continuable-parent ownership, and then submits the prompt. Cold resume never dispatches through a provider because the persisted Session already holds the initial prefix and the folded descriptor is the whole reconstruction input.

Settlement delivery

When a resident Activation settles, the manager tells the child's durable direct parent, in the parent's own turn stream, that the child produced everything it is going to. Delivery is unconditional for every child whose id a caller actually received: it does not consider whether the child called report, because the endings that most need an account — a token ceiling, a model failure, cancellation, teardown — are exactly the ones where the child never got to choose. A materialization rolled back before its first accepted message stays silent, since that caller was told the child was not established. The message carries the epoch's stop reason, its final assistant content when it produced any, and durable provenance { kind: 'subagent-settled', form: 'notice', senderSessionId: <child-id> } — a different source kind from a child-authored subagent-report, so a transcript never credits the child with words the runtime wrote.

Two ordering rules make the delivery reliable rather than lucky, and both are why this belongs to the manager instead of an external subagent/end listener. First, the send happens before the child's ownership release, while the parent still counts the child and is therefore structurally unable to be judged settled. Second, a parent that is itself a resident Activation receives the message through the same waking-admission accounting as a report, so the window between the synchronous send and the microtask that admits it is not mistaken for quiescence — Agent.status folds context maintenance into idle, and a waking send behind maintenance only arms a deferred wake. Without either rule the parent can be disposed with the notice still in an inbox that cancel() clears, which loses it silently.

An idle parent receives the notice as one ordinary later turn. A busy parent is steered into its nearest step boundary instead, so several children settling together cost one step rather than one turn each; steering rather than injecting also means a driver that retires between the status read and the send still claims the message. A parent whose own lineage is already draining receives the notice by injection, with no wake at all: Agent.followup() on a quiescent parent starts a turn and cancel() does not arm against a later one, so waking during teardown would spend a model request on an Agent its host is about to dispose — once per tree layer, since each layer's notice then wakes the layer above it. The injected message reaches a parent that is still reading its inbox, and the log records the account either way, but it does not outlive that parent's own disposal: AgentHandle.dispose() is a keepInbox: false cancel, which durably cancels an unclaimed notice. A resumed parent therefore has no pending notice to read: list_agents tells it which children exist and whether each is live or stored, while the outcome itself stays in the child's own Session, which a send_message reaches by resuming that child. A parent that has left the registry is not an error: the notice is dropped and the child's own Session remains the durable record. Delivery never blocks or fails teardown — a rejected send is logged, because retaining a child to retry a notice would pin its whole ancestry in waiting forever.

A continuation-managed parent Activation records each child Session id in an ownedChildren set before the child can run and disposes only after every owned child Activation completes AgentHandle disposal (child-first). Teardown propagates Agent cancellation top-down before awaiting slow descendants, while handle release remains child-first. Top-level and other non-continuation Agents have no Activation and stay outside this waiting graph. Final settlement awaits a best-effort ctx.sessions.flush(child.session) before handle disposal. A listener rejection is logged without failing the Activation because listener participation does not identify a persistence backend; the persisted state may therefore be missing or stale on resume.

Lifecycle events

The service emits a subagent/start/subagent/end pair for each one-shot run and each resident continuable Activation epoch, so continuable children are observable with the same vocabulary as one-shot runs without exposing whether the manager materialized, woke, or cold-resumed them. For a one-shot start it attaches the result observer before the synchronous subagent/start, so even an already-settled child still produces subagent/start before subagent/end; a continuable epoch that fails before residency emits neither edge. The pair shares a service-minted runId; the local flag is snapshotted from the provider's exact localAgent (always true for a continuable child), so observers never infer run identity or locality from reusable provider/session names. The provider field contains the provider name recorded when the child was first created rather than claiming current registration: an accepted one-shot run may settle after provider removal, and a cold-resumed epoch reads the initial provider name from its descriptor without calling or registering that provider.

Run events are scoped to the delegating parent. Every listener is independently contained: a synchronous throw or rejected returned promise is logged without starving peer listeners or changing the run.

Provider additions and removals also emit subagent/provider-added and subagent/provider-removed. Consumers such as the model-facing tool use those events because Cordis may load sibling plugins concurrently; configuration order does not prove registration order.

Continuable children do not create SubagentRun or Jobs. The continuation manager directly owns one process-local Activation and retained AgentHandle per resident child Session, uses the Agent inbox as the only FIFO, and cold-resumes from the durable descriptor. Exact live direct-parent identity authorizes parent-to-child delivery. Exact live child identity authorizes reports; the manager derives the recipient from durable parentSession, and MessageSource records the sender without granting authority. Interrupt authority is deliberately wider than delivery authority: a human presents the durable direct-parent address so a live child stays stoppable while its parent Agent is offline, and any exact live ancestor recorded in the Activation's materialization lineage may stop its descendant, because stopping a turn is idempotent and delivers no content.

When ctx.sessionProjections is available, the service registers two projection units. subagentTiming resets at each descriptor so a fork seed's ancestor work cannot enter the child's total, then accumulates turn/startturn/end active time and retains same-cut active.since and active.through bounds for an open turn; while that turn remains open, active.through follows the latest folded event, giving an inactive consumer a conservative crash bound without mixing in newer session metadata. subagent folds the durable identity — mode plus creation label — from subagent/descriptor events with the same last-wins reset discipline, so a fork seed's ancestor descriptor stands only until the child's own overrides it; a malformed or unrecognized-version payload folds to the serializable null sentinel — indistinguishable from a log with no descriptor, and surviving every JSON push frame so a consumer replaces a stale identity instead of keeping it — and never throws.

registerContinuableSetup() lets optional packages add child-scoped capabilities without teaching the continuation manager their names. Contributions install synchronously before Activation publication, roll back with failed setup, and are released with the child scope. New grants wait for the next Activation, while contribution removal revokes every resident installation immediately.

Collection model

The model-facing tool collects synchronously by default: it awaits the child result and disposes the run before returning. One-shot background delegation registers a plain Task in the tool, whose generic status, collection, and cancellation tools own later interaction, and persists its model-supplied description as the optional display label. Continuable background delegation calls ctx.subagents.startContinuable() and returns only the durable child id; the child owns its own turns from inbox acceptance, so there is no Task and no result promise — a caller sends later work with the send_message follow-up tool, and interrupt() stops only the current turn without disposing the child, and the durable child Session remains the source of the child's detailed output. The continuation manager exists only while ctx.agents is available, and session persistence is resolved per continuation operation. Independently, listChildren() enumerates the live-preferred merge of the live session store and optional session persistence — live-only when persistence is absent, since a cold child cannot be resumed then either — and serves each child's durable mode/label from the registered subagent projection unit: the registry's watermark snapshot for a live child; for a cold one, a durable projection-cache row when it serves an own-suffix identity — its seq gate proves the value postdates the fork seed, where a child's own descriptor is immutable once appended — else one bounded-concurrency persistence inspection folded through the registry, whose result must still name the enumerated lifecycle (a re-published id degrades to a corrupt diagnostic). A throwing cache read renders no verdict — the cache is derived data — and silently falls through to that authoritative re-fold. The projection fold is the single classification authority; listing parses no descriptor itself. A served identity produces a child row; a settled candidate whose fold served no identity is a corrupt diagnostic, a failed inspection is a transient unavailable retried on the next listing, and a running candidate without an identity yet is omitted (the creation window before its descriptor is appended). It never consults the continuation manager, Agent registrations, Activations, or providers. Each child row derives its read-time hasChildren hint from merged headers carrying durable origin: 'subagent'; it does not read descendant event logs, and the descriptor-backed child catalog remains authoritative when expanded. Service consumers such as a UI can retain both modes and choose a fallback for an unlabeled one-shot child; the model-facing list_agents tool projects only continuable entries and refines status through the live Agent registry and maps storage-only to its resumable-not-terminal ready (running/idle/ready) and walks listDescendants() for its descendants scope. The listing forwards the caller's signal to every persistence read, checks cancellation around each of those awaits, and reports every observed abort as SubagentError code CANCELLED; an unmounted projection registry fails loud with SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE, and a missing session store with SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE. See the background subagent tasks Agent Note, the continuable background subagents Agent Note, the durable catalog Agent Note, the merged-service Agent Note, the capability-seam Agent Note, and src/types.ts for the complete contracts.

Continuable Activations await a best-effort final session flush without treating listener participation as durability confirmation. One-shot runs retain best-effort session checkpointing, so a completed one-shot child is discoverable after disposal only when its session actually reached persistence; the service does not invent a catalog entry from Task history when that checkpoint is absent.

Model Experience

Settlement notice

What the model sees

One user-role parent message opening with the outcome — Background subagent <child-id> finished and will do no further work unless you send it more., or the matching line for a child that was stopped, ran out of room, declined, or failed — followed by Its closing message: and the child's final assistant content, or It left no closing message. when it produced none. This is the service's only direct parent-side contribution; delegation schemas, parent continuation and discovery, and the child-scoped report belong to dsh-tool-subagent, dsh-tool-subagent-control, and dsh-tool-subagent-report.

Token effect

One notice per settled Activation in the parent's request, sized by the child's final message. A child that both reports and settles costs the parent both.

KV Cache effect

Append-only in the parent: the notice follows its reusable request prefix. Reaching an idle parent starts one independent model request; reaching a busy one does not.

Child delegation-scope statement

What the model sees

Every in-process child's runtime-context snapshot carries the subagent:delegation statement below, after the sandbox-policy and approval-policy sentences.

The delegation-scope statement
You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the job needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it.

Token effect

One fixed statement in each child's runtime-context snapshot; none in the parent's requests.

KV Cache effect

Prefix-stable within a child: the statement never changes during the child's lifetime, so it is written once into the first runtime-context snapshot. Parent-side, no direct invalidation; the named tool consumers own any request-prefix changes.

Known Limitations and Deferred Work

  • ACP children remain one-shot and are not trace-enumerable — an ACP run has no local child session in the parent's session corpus. An ACP prepareContinuable requires persisting the remote session id in provider-specific descriptor data and a per-child continuation advertisement, since ACP loadSession support is negotiated per child rather than established by the method's presence. Remote providers also require a separate Activation ownership contract with equivalent authenticated control and child-first quiescence before they support continuable children.
  • No host-user continuationfollowup() requires the exact live direct parent. Only interrupt() accepts a durable parent-address user authority, because stopping a turn is idempotent and delivers no content; a future host adapter needs a concrete authenticated interaction before the seam gains a user delivery capability.
  • No current-turn steering — continuable messages and waking reports enqueue later turns; neither redirects an open turn.
  • Wake gap during cancellation convergence — a waking follow-up accepted after the interrupt signal is issued but before the active driver becomes idle remains queued until another waking send. Issue #1838 owns the agent-loop wake latch, which also affects ordinary session cancellation.
  • Process-local residency — the Activation inbox and ownership graph do not coordinate two harness processes; concurrent access to one persistence store still requires a durable mailbox and cross-process lease protocol.
  • No replay of accepted-but-unlogged messages — only messages written to the child Session log are reconstructable with the source that supplied them. A crash may lose an accepted initial prompt or follow-up that never reached the log; a later authorized message can cold-resume the child, but the lost message is not replayed automatically.
  • No durable report mailbox — reports require a live direct parent and provide acceptance identity rather than exactly-once delivery or a read receipt.
  • Lifecycle events are observe-only — a run-affecting subagent/end continuation or decision API waits for a concrete consumer.

LIMITATIONS

Known limitations

- **ACP children remain one-shot and are not trace-enumerable** — an ACP run has no local child session in the parent's session corpus. An ACP `prepareContinuable` requires persisting the remote session id in provider-specific descriptor data and a per-child continuation advertisement, since ACP `loadSession` support is negotiated per child rather than established by the method's presence. Remote providers also require a separate Activation ownership contract with equivalent authenticated control and child-first quiescence before they support continuable children. - **No host-user continuation** — `followup()` requires the exact live direct parent. Only `interrupt()` accepts a durable parent-address user authority, because stopping a turn is idempotent and delivers no content; a future host adapter needs a concrete authenticated interaction before the seam gains a user delivery capability. - **No current-turn steering** — continuable messages and waking reports enqueue later turns; neither redirects an open turn. - **Wake gap during cancellation convergence** — a waking follow-up accepted after the interrupt signal is issued but before the active driver becomes idle remains queued until another waking send. Issue #1838 owns the agent-loop wake latch, which also affects ordinary session cancellation. - **Process-local residency** — the Activation inbox and ownership graph do not coordinate two harness processes; concurrent access to one persistence store still requires a durable mailbox and cross-process lease protocol. - **No replay of accepted-but-unlogged messages** — only messages written to the child Session log are reconstructable with the source that supplied them. A crash may lose an accepted initial prompt or follow-up that never reached the log; a later authorized message can cold-resume the child, but the lost message is not replayed automatically. - **No durable report mailbox** — reports require a live direct parent and provide acceptance identity rather than exactly-once delivery or a read receipt. - **Lifecycle events are observe-only** — a run-affecting `subagent/end` continuation or decision API waits for a concrete consumer.