A new capability family at packages/workflow/ in the bash seam shape,
modeled on Claude Code's dynamic workflows: the model writes a JavaScript
orchestration script (export const meta = {...} + plain-JS body), a runtime
executes it, and the script — not the conversation — holds the loop, the
branching, and the intermediate results.
- dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary
(WorkflowRun whose result NEVER rejects) + observe-only workflow/* events
carrying data snapshots (id + meta, never the live run), per-listener
contained like subagent/*.
- dsh-workflow-vm: in-process node:vm engine. Meta extraction via a
string/comment-aware scanner (template interpolation rejected; literal
evaluated alone in an empty timed context; statement blanked line-
preservingly so stacks keep script line numbers). Hooks: agent(prompt,
{label, phase, schema, model}) over ctx.subagents, parallel(), pipeline()
(no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline:
hook misuse (unknown/deferred options, bad arguments, unsupported
schemas, tripped caps, seam start failures, cancellation) throws fatal
WorkflowErrors the combinators RE-THROW — never dissolved into the
per-item null reserved for child failures. Realm boundary: inbound values
materialized by descriptor walks that never invoke accessors (defineProperty
copies, __proto__-safe); outbound values rebuilt in-realm via the
context's own JSON.parse. Determinism bans (Date.now/Math.random/argless
new Date) kept so future resume support cannot break scripts. Caps and
timeouts are validated Config. Every hook promise carries a no-op
rejection consumer (app-boot exits on unhandled rejections).
- dsh-tool-workflow: the model-facing workflow tool, synchronous like
dsh-tool-subagent (start → await → try/finally dispose; abort bridged;
non-completed → isError). Generic render card titled by a textual
meta.name sniff. The tool description carries the authoring contract.
Wired into examples/{coding-agent,acp-agent} with explicit-ask-only
guidance. Coverage at every tier: unit (meta scanner, materializer incl.
counting-getter and __proto__ regressions, combinator semantics,
concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon),
integration over the real spawn stack, with-key e2e (real two-phase run +
the tool through the registry pipeline), and a recorded ACP snapshot
scenario (workflow-run, 1 child session). RFC:
docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred
work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's
layout line.
@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) before any child exists; - 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 system prompt is NOT inherited; a structured run appends thestructured_outputinstruction after the caller's prompt); - 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); a structured child that finished a turn CLEANLY without callingstructured_outputis re-prompted (a nudge — a fresh turn) up tooptions.structuredNudgeRetriestimes; - 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[]; structuredNudgeRetries: number } — the per-backend inputs: the provider name (for error context), the optional child-session seed, and the structured-run nudge budget (REQUIRED, resolved from the backend's validated Config — the driver never fills it with a hidden default).
Structured output: acquireStructuredRuntime(ctx): StructuredAcquisition
The mechanism behind outputSchema for in-process children. One globally registered structured_output capture tool (its registered parameters are a placeholder) plus two listeners, registered once per root context and shared by every holder:
- an
agent/requestwaterfall listener registeredprepend: truethat post-processesawait next()— final-request enforcement: the request that hits the wire never carriesstructured_outputfor an agent without a structured run, and always carries the run's OWN schema (as the tool'sparameters) for one that has it. Per-agent shaping lives here because the tool registry and prompt assembly are context-global while schemas differ per concurrent child; cooperative mutate-then-next()would not survive a downstream listener returning a replacement request. - an
agent/turn-continuationlistener 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 records the value.
Lifetime is refcounted with two kinds of holder: each backend acquires for its plugin lifetime (apply), and each structured RUN holds its own acquisition from start to settle — so unregistration can never precede a live run's settle, and the runtime disposes only when the last backend AND the last run are gone. 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.