DSHarness Systems Dissection Pinned baseline 47f943859b · 36 verified / 0 in progress / 36 chapters
中文
Orchestration·Chapter 25

Workflow and Ralph

How model-authored JavaScript orchestration bridges to Agent calls

VerifiedUpstream 47f943859bScope: Analyze the worker-thread engine, agent() bridge, fan-out, aggregation, events, errors, and the fixed Ralph loop.

Conclusion: Workflow Is a Foreground Orchestration VM; Ralph Is a Fixed Policy on Top

DeepSeek Harness separates orchestration into three layers. ctx.workflowEngine defines a holder-owned run contract; the worker-thread engine executes one model-authored JavaScript body and bridges agent() calls to the subagent seam; model-facing consumers decide what script and policy the model may control. The generic workflow tool exposes the script. ralph does not: it supplies one build-time-fixed loop and accepts only an objective and a bounded round count.

This makes Workflow powerful without pretending to be durable infrastructure. It is a foreground fan-out and aggregation mechanism with explicit concurrency, cancellation, JSON, and lifecycle boundaries. It is not a scheduler, resumable job system, security sandbox, or independent verifier.

1. The Three-Layer Architecture Keeps Policy out of the Engine

LayerOwnsDoes not own
Workflow seamStart request, live run, result vocabulary, lifecycle events, fatal error taxonomyJavaScript implementation, model-facing schema, persistence policy
Worker-thread providerVM hooks, worker/host protocol, child admission, caps, cancellation, cleanupWhen the model should use orchestration or which consumer exposes it
Tool consumerVisible schema, prompt guidance, result projection, optional Session recordingExecution mechanics behind ctx.workflowEngine

The request carries a plain-JavaScript body, JSON metadata and arguments, an optional provider and child ceiling, and a required live parent Agent. A returned run exposes validated metadata, a never-rejecting result promise, cancellation, and idempotent disposal. WORKFLOW-SEAM

Design reading

A specialized policy can reuse the same engine without adding another branch to the Agent loop. Ralph proves the point: it composes the existing workflow and subagent services rather than creating a “Ralph mode” in the core runtime.

2. A Live Run Is a Holder-Owned Resource, Not a Fire-and-Forget Promise

start(request)
  → validate before publication
  → return WorkflowRun
       result: Promise<WorkflowResult>  // never rejects
       cancel(reason?)
       dispose()                        // idempotent, bounded cleanup

The seam makes ownership explicit. The caller that receives a run must dispose it; cancellation and disposal are bounded; listener failures are contained; and workflow/end fires once when the result settles. Ordinary failures become data with completed, cancelled, or error rather than rejecting the public result promise. WORKFLOW-LIFECYCLE

This avoids an ambiguous state where a rejected promise might mean either “the script failed” or “the runtime itself lost ownership.” It also forces every consumer to confront cleanup instead of treating children as detached work.

3. Validation Happens Before Publication; Runtime Failure Happens After It

The worker provider validates and normalizes metadata, parse-checks the same async wrapper the worker will compile, resolves the child provider, and bounds any per-run child override before minting an ID or emitting workflow/start. Unknown metadata fields and malformed phases fail together; an old export const meta header receives a pointed parse error. WORKFLOW-START-VALIDATIONWORKFLOW-META

Commit point

Before start() returns, an invalid request has no published run. After it returns, script errors, worker death, cancellation, and boundary failures resolve through the run result. This is the same transactional shape used elsewhere in the repository: reject candidates before they become observable.

4. One Fresh Worker Thread per Run Provides Containment, Not Security

Each run receives a new Node worker. Its ambient environment is scrubbed, loader flags are cleared, and initialization data crosses through structured clone. Model-authored code then runs in a vm.Context inside that worker. This protects the host event loop from a synchronous script and gives the host a thread it can forcibly terminate. WORKFLOW-WORKER-SPAWN

5. The Script World Is Intentionally Tiny

globals = {
  agent(prompt, options?),
  parallel(thunks),
  pipeline(items, ...stages),
  phase(title),
  log(message),
  args
}

No filesystem, network, timers, Cordis context, Node APIs, or direct Session handle is injected. The initial synchronous slice is limited by syncTimeoutMs; after the first await, cooperative hook checks plus the host's cancellation grace provide the stop mechanism. Once cancellation or disposal begins, a script that parks forever without touching another hook is terminated by the host after grace; absent such a trigger, this engine defines no general wall-clock deadline. WORKFLOW-VM-GLOBALSWORKFLOW-CANCEL-BOUNDARY

Boundary meaning

The script coordinates capabilities but does not itself possess them. Actual work belongs to child Agents, which enter their ordinary tool, permission, and execution worlds.

6. agent() Is an RPC Bridge to the Existing Subagent Seam

A call validates a non-empty prompt and a closed option set: label, phase, schema, provider, and model. Explicitly deferred options such as effort, isolation, and agentType fail loudly. The host then starts the configured subagent provider with the workflow's live parent, the shared run-cancellation signal, optional output schema, and optional provider/model override. WORKFLOW-AGENT-OPTIONSWORKFLOW-CHILD-BRIDGE

Without a schema, agent() concatenates only final text blocks. With a supported object-rooted schema, it returns the structured value. A clean child missing promised structured output becomes null, and every child handle is disposed in finally. WORKFLOW-AGENT-RESULT

7. There Are Two Independent Scale Backstops

LimitDefaultWhat it controlsWhen checked
maxConcurrentAgentsmin(16, max(1, cores - 2))Simultaneously active agent() callsFIFO slot acquisition
maxTotalAgents1000Accepted calls over one runBefore waiting for a slot
maxItemsPerCall4096Items in one parallel() or pipeline()At combinator entry

The total counter includes calls queued for a concurrency slot, closing the common loophole where a runaway loop enqueues unbounded work behind a small pool. Slot waiters are FIFO and are rejected on cancellation. A consumer may lower the total cap for one run but cannot exceed the engine ceiling. WORKFLOW-CAPSWORKFLOW-SLOTS

8. parallel() Is a Barrier; pipeline() Is Per-Item Flow

CombinatorExecution shapeOrdinary throwFatal workflow error
parallel([f, g])Invoke all thunks and await Promise.allThat position becomes nullWhole script fails
pipeline(items, s1, s2)Each item runs s1 → s2 independently; no cross-stage barrierThat item becomes null and skips later stagesWhole script fails

Both preserve input position because they return through Promise.all. Pipeline therefore does not mean “finish stage one for all items, then begin stage two.” A fast item can enter its next stage while a slow sibling is still in the previous one. WORKFLOW-COMBINATORS

9. The Error Taxonomy Separates Work Failure from Contract Failure

An ordinary child stop reason becomes null, which lets a script filter or aggregate partial work. An ordinary user-script throw inside a combinator also nulls only that item. But bad arguments, unsupported options or schemas, cap violations, provider-start failure, rejected child-result transport, unserializable output, and cancellation are typed fatal WorkflowErrors and cannot dissolve into a null. WORKFLOW-ERROR-TAXONOMYWORKFLOW-FATALITY

Why this matters

“One reviewer found nothing” and “the orchestration contract is broken” must not look identical. The fatality check is a host-realm instanceof, so a model script cannot forge a plain object that masquerades as an ignorable or fatal engine error.

10. Every Cross-Realm Result Must Be Lossless JSON

The materializer accepts finite numbers, booleans, strings, null, dense arrays, and plain objects. It rejects bigints, functions, symbols, nested undefined, non-finite numbers, cycles, sparse arrays, exotic prototypes, symbol keys, and extra array properties. It copies __proto__ through defineProperty so a data key cannot mutate the host copy's prototype. WORKFLOW-JSON-BOUNDARY

Child results are independently snapshotted before crossing from host to worker. A child value that cannot make that trip becomes an infrastructure failure rather than a silently lossy answer. WORKFLOW-CHILD-SNAPSHOT

11. Cancellation Is a Race with One Winner, Followed by Bounded Quiescence

cancel(reason)
  → close new work admission
  → notify worker
  → abort one signal shared by pending and published children
  → reject queued slots and future hook calls
  → grace timer
      └─ if still unsettled: synthesize missing agent-end, settle cancelled,
         terminate worker

The first worker result, worker death, or cancellation-grace expiry owns terminal settlement. A result already won cannot be rewritten by cleanup callbacks; a cancellation accepted before a competing non-cancel result reports cancellation. The host suppresses late phase/log narration but preserves or synthesizes exactly one end event for every published child start. WORKFLOW-CANCEL-RACEWORKFLOW-EVENT-PAIRING

12. dispose() Starts Child Cleanup Immediately and Shares the Same Grace

Disposal is memoized. It detaches the input signal, cancels if necessary, begins disposing every registered child immediately, waits for result plus child quiescence or the configured grace—whichever comes first—then terminates the worker unconditionally. Per-child disposal is also memoized, so worker RPC, host disposal, and death cleanup converge on one operation. Slow cleanup beyond grace is deliberately no longer awaited rather than letting a run live forever; a rejection, if one arrives, is logged and contained. WORKFLOW-DISPOSEWORKFLOW-CHILD-QUIESCENCE

HMR lifetime

The engine captures a holder-bound subagent service before returning the run. Unloading the engine removes the ability to start a new workflow but does not invalidate an already accepted run's ability to start and clean up children. WORKFLOW-HMR-HOLDER

13. Workflow Events Are Observe-Only and Deliberately Omit the Return Value

The seam emits start, phase, log, agent-start, agent-end, and end. Phases are progress labels only; they do not schedule work. Listeners receive borrowed identity and outcome snapshots, never control handles, and listener failures are caught and logged. The final event omits the script value: only the run holder may await and consume it. WORKFLOW-EVENTS

Ownership preserved

An observer can build progress UI or metrics but cannot cancel the run, mutate the result, or accidentally retain a live child-control capability.

14. The Generic Tool Adds a Model Contract and a Narrow Durable Projection

The workflow tool exposes script, meta, and optional object-shaped args. Its description is the authoring specification, while a separate system-prompt section says to use it only when the human explicitly asks for a workflow or large multi-agent orchestration. The tool awaits the foreground result, maps non-completed outcomes to errors, caps only rendered JSON, and always disposes the run. WORKFLOW-TOOL-CONTRACTWORKFLOW-TOOL-EXECUTE

For root transport calls only, it projects run start/end and paired member start/end into the parent Session. Nested dispatches run normally but write no such records. A failed append disables later recording for that run without affecting execution, leaving either no record or a valid continuous prefix. WORKFLOW-DURABLE-RECORD

15. Durable Recording Is an Audit Trail, Not Workflow Recovery

The four Session events record the run name, member sequence/label/phase/child ID, member outcome, and terminal reason. Package invariants reject duplicate starts, unpaired ends, terminal events with open members, and post-terminal updates, while allowing an incomplete terminal suffix after a crash. WORKFLOW-RECORD-TYPESWORKFLOW-RECORD-INVARIANT

16. Ralph Moves the Script, Provider, and Report Schema Back under Deployment Control

The model-facing Ralph schema has only a required objective and optional maxRounds. The plugin owns a fixed JavaScript body, fixed metadata, structured round schema, provider route, handoff limit, and terminal validation. The configured provider must exist, support structured output, and declare that it does not inherit the parent context. RALPH-CONTRACTRALPH-FRESH-PROVIDER

Security and product difference

Generic Workflow asks the parent model to author control flow. Ralph asks the model only for data and runs deployment-reviewed control flow. Both use the same escapable VM, but the attack and reliability surface presented to the calling model is much smaller in Ralph.

17. The Ralph State Machine Is Small and Explicit

previous = none
for round in 1..maxRounds:
  report = await fresh agent(objective, round, previous, workspace rules)
  null      → round-failed
  complete  → complete
  blocked   → blocked
  continue  → previous = report
loop exhausted → budget-limited(previous)

A report contains status, summary, evidence, next steps, and blocker text. Continue requires next steps and no blocker; complete requires evidence, no next steps, and no blocker; blocked requires a concrete blocker. Every string must be normalized and each serialized handoff must fit maxHandoffChars. RALPH-SCRIPT

18. Fresh-Context Routing Is Gated by a Provider Declaration; Continuity Lives in the Workspace

Each round's prompt says it receives neither the parent conversation nor a prior child Session. It carries the immutable objective, current round and cap, the previous bounded report, and an instruction to inspect and verify the shared workspace as the long-term source of truth. The live parent still supplies cwd and lineage to the subagent seam, but the chosen provider must promise inheritsParentContext:false. RALPH-ROUND-PROMPT

This yields a deliberate memory hierarchy: files and working tree are durable truth; one small report is coordination state; uncommitted conversational reasoning dies with each round. That can reduce anchoring on a long chat, but it also makes filesystem hygiene and honest handoffs load-bearing.

19. Ralph Validates the Same Claim Twice across the Trust Boundary

The fixed script validates each child report before carrying it forward. After the workflow returns, TypeScript validates the exact terminal object, allowed keys, round count, status-specific report semantics, and size again. A model-selected round cap must be a positive safe integer no larger than the deployment ceiling; the same value becomes the engine's total-child ceiling. RALPH-DOUBLE-VALIDATIONRALPH-EXECUTE

Two distinct output caps

maxHandoffChars protects every cross-round report and canonical terminal report. maxResultChars only truncates the parent-facing rendered text; it does not rewrite the canonical value. RALPH-RENDER

20. Completion and Blockage Are Worker Reports, Not Independent Judgments

The renderer says “worker reported completion” or “worker reported a blocker.” A child failure ends the run immediately with the failed round and last successful handoff; it is not retried. Cancellation and workflow infrastructure failures are errors, never partial success. Reaching the round cap yields budget-limited only when the last valid report still says continue. RALPH-OUTCOMES

21. Shipped Defaults Make Both Tools Real—but Still Explicit-Ask

The base composition mounts the worker engine over the spawn provider, the generic workflow tool, and Ralph with a 64-round deployment ceiling. The standard CLI preset isolates the workflow service inside the delegation group and mounts the same trio. The host-plane web bundle disables these base rows so selected per-session presets own them instead. WORKFLOW-SHIPPED-BASEWORKFLOW-SHIPPED-PRESETWORKFLOW-WEB-ISOLATION

Availability therefore depends on the selected Agent preset, while usage policy remains conservative: ordinary one- or two-child delegation belongs to subagent tools; Ralph requires a direct human request; generic Workflow is for explicit large orchestration.

22. Focused Codex Comparison: Similar Delegation Goal, Different Orchestration Product

QuestionDeepSeek Workflow / RalphCodex public contract
Who writes orchestration?Generic Workflow: parent model writes JavaScript; Ralph: deployment ships fixed JavaScriptCodex delegates through documented subagent workflows and role configuration; its public guide does not describe a model-facing JavaScript workflow VM
Primary fan-out unitagent() calls inside one foreground runSpecialized subagent threads, typically for parallel, separable work
Recurring/background workNot provided by this subsystemScheduled tasks are a separate documented product surface
Cross-worker continuityExplicit JSON handoff and shared workspace in RalphParent/subagent summaries and thread management under the documented multi-agent workflow

Codex's public guidance recommends subagents for independent, mostly read-heavy exploration, tests, or triage, and warns that overlapping write-heavy work can conflict. DeepSeek's generic Workflow adds programmable parallel/pipeline aggregation, while Ralph requires a provider that declares fresh-context rounds. Conversely, Codex documents scheduled tasks as a separate recurring-work surface; DeepSeek Workflow explicitly remains foreground-only. See the official Codex subagents guide. Retrieved 2026-08-13.

23. What to Reuse—and What Not to Generalize

1

Reuse the seam

Keep live-run ownership, never-reject results, bounded disposal, and observe-only events independent of one engine.

2

Separate policy

Expose free-form control flow only where needed; ship fixed workflows for repeatable operational policies.

3

Name failure classes

Partial work failure may be data; contract and infrastructure failures must stay fatal.

4

Do not overclaim

A worker thread is not a sandbox, an event trace is not a checkpoint, and a worker's completion report is not verification.

The subsystem's strongest idea is not JavaScript itself. It is the discipline of placing orchestration behind an owned lifecycle, then letting different consumers choose how much control to expose.

Verification Notes

This chapter was traced against the pinned baseline across the workflow seam, worker-thread host/runtime/realm, generic workflow consumer, durable event invariant, Ralph consumer, and shipped composition. Focused Vitest execution covered 12 files and all 190 tests passed under an isolated temporary root. Claims distinguish source facts from design assessments, and the Codex comparison uses only official public documentation.

My Learning Notes

Autosaved only in this browser. Nothing is uploaded or committed. Export Markdown whenever you want to keep a copy.