@deepseek-ai/dsh-subagent-inprocess
The shared in-process subagent run driver. A pure library (no provider, no registration) that the in-process backends — spawn (a fresh child) and fork (a child seeded with a prefix of the parent's log) — both build on. The backends are thin shells that differ ONLY in the session seed they pass; everything downstream lives here, so neither backend depends on the other.
What it exports
startInProcessRun(ctx, request, options): SubagentRun
Runs a child as a child Agent on the same cordis context (ctx.agents):
- computes child depth =
depthOf(parent) + 1; ifrequest.maxDepthis set and exceeded, throwsSubagentDepthError(thedepthLimitcapability); arequest.outputSchemais asserted against the supported subset (assertSupportedOutputSchemafrom dsh-tools) and then snapshotted withstructuredClonebefore any child exists — assertion first so a hostile value fails asOutputSchemaError(never a raw clone error), the snapshot so a post-start()caller mutation cannot drift the enforced schema; - creates a child via
ctx.agents.createwith a freshAgentId/SessionId, the parent'scwd+parentSessionlineage, the optionaloptions.seed(fork's completed-turn prefix; omitted for a fresh child), andagentOptions(the child inherits the parent's model by default — a child with no model can't run — overridable viarequest.agentOptions.model; the deployment persona needs no inheritance — it is a context-wide prompt section); - drives the one-shot:
child.send(prompt)thenawait child.whenIdle()(ordering matters —sendenqueues synchronously, sowhenIdleobserves the queued work and resolves on the child'srunning → idletransition, never before the turn starts); there is deliberately NO re-prompt for a structured child that finished cleanly without callingstructured_output— the shortfall maps to anerrorresult for the parent; - reads the result, scoped to the child's OWN events (everything at or after
seedLength, so a seeded child that produced no message of its own never returns the seeded parent's last message): the lastassistant/messagecontent (deep-cloned — the log is frozen) and the lastturn/end.reasonmapped to aSubagentStopReason. A structured run surfaces the captured value asresult.structured; a structured child that finished cleanly WITHOUT ever capturing settleserror(a clean finish without the demanded result is a failure, not a success with a missing field).
dispose() delegates to AgentHandle.dispose() (stop loop → await quiescence → remove session); cancel() cancels the child's in-flight turn. A cancel landing before any turn/end (the pre-turn window) still settles aborted, honoring the cancel contract rather than the generic no-turn error.
InProcessRunOptions
{ providerName: string; seed?: SessionEvent[] } — the per-backend inputs: the provider name (for error context) and the optional child-session seed.
Structured output (package-internal runtime)
The mechanism behind outputSchema for in-process children — acquired per structured RUN inside startInProcessRun (nothing is registered on a context that never runs a structured child; only the model-facing constants STRUCTURED_OUTPUT_TOOL/STRUCTURED_OUTPUT_INSTRUCTION are exported). One globally registered structured_output capture tool (its registered parameters are a placeholder) plus four listeners:
- a
system-prompt/assemblewaterfall listener registeredprepend: truethat post-processesawait next()— final-assembly enforcement: the assembly the loop renders never carriesstructured_outputfor an agent without a structured run, and for one that has it always carries the run's OWN schema (as the tool'sparameters) plus the calling instruction as a trailing prompt section (the demand travels with the tool —AgentOptionshas no per-agent prompt field to carry it). The loop logs the rendered assembly as the step'srequest/header, so the injection is reconstructable log state, never a wire-only mutation. Per-agent shaping lives here because the tool registry and prompt assembly are context-global while schemas differ per concurrent child (FIXME in the module doc: per-agent/per-session scoping would dissolve this); cooperative mutate-then-next()would not survive a downstream listener returning a replacement assembly. - a
tools/post-executelistener (prepend: true= outermost, soawait next()yields the composed final decision) that COMMITS the capture: the tool body only stages the validated value, and it becomes the run's result only when the final decision accepts the call — a downstream block (a PostToolUse hook) turns the logged result intoisError, and the run must not reportstructuredsuccess for a call the model and session log saw fail. - a
tools/pre-executedeny for any call arriving after the agent's capture — terminal means terminal WITHIN the step: a response listingstructured_outputbefore further tool calls cannot run side effects after the final answer was accepted. - an
agent/turn-continuationlistener (alsoprepend: true— an earlier-registered force-continue listener returning withoutnext()must not decide the turn before the veto runs) that stops a child's turn once its output is captured, so a successful capture doesn't buy a wasted extra model step.
The capture tool validates each call against the run's schema (validateStructuredValue) — violations become an INVALID_ARGS isError result the model retries in-turn; a valid call stages the value for the post-execute commit.
Lifetime is refcounted by live structured runs: each acquires at start and releases at settle, so the registrations exist exactly while at least one structured child is live, a backend hot-reload mid-run cannot unregister the capture tool under a live child, and the last settle disposes everything. release() is idempotent per acquisition.
depthOf(agent): number
Delegation depth rides on a merge-extensible AgentOptions.subagentDepth field (0 for a top-level agent, parent + 1 for a child), so a nested spawn reads its parent's depth from parent.options.subagentDepth. depthOf reads it (absent ⇒ 0).
SubagentDepthError
Thrown by startInProcessRun when a spawn would exceed the request's maxDepth cap; carries attemptedDepth and maxDepth.