Workflow execution lifecycle
Construction inventory before convergence
This inventory comes from repository-wide searches for new WorkflowRunner,
WorkflowRunner(, connectPythonBridgeForGraph, hydrateGraphNodeFlags, and
withExplicitNodeFlags. Paths identify call sites without depending on line
numbers. Tests under tests/ and __tests__/ construct the kernel directly.
| Construction site | Classification | Responsibilities before migration |
|---|---|---|
packages/execution/src/session.ts |
Top-level server execution | Normalize, hydrate, preflight, resolve executors, construct context and runner, connect and close Python, timeout, cancel, live input and properties, message capture, cost ledger, worker boundaries, optional output naming and persistence, scratch cleanup |
packages/execution/src/service/workflow-run.ts |
Top-level server execution | Normalize, metadata hydration, preflight, registry or host resolver, start shared Python worker, workspace context, runner, interactive cancel, progress and terminal persistence, output naming, result presentation. No run timeout or scratch cleanup |
packages/dsl/src/core.ts |
Top-level server execution | Build graph with explicit flags, construct context, caller/global/builtin/Python resolution, connect and close Python, runner, throw on failed result or node error, select last output. No preflight, timeout, persistence, live inputs or property updates |
scripts/run-workflow.mjs |
Top-level server execution | Load file, prepare registry and context, select providers, start local Python worker, resolve executors, runner, print result, close Python on success. No cancellation, timeout or workspace cleanup |
packages/workflow-runner/src/run.ts |
Browser/portable execution | Registry hydration and validation, context and runner, AbortSignal cancellation, queue/wake message streaming, completion and result propagation, listener cleanup. No Python, persistence or output rewriting |
packages/workflow-runner/e2e/src/browser-entry.ts |
Specialized browser harness | Prepare context, registry and sandbox catalog, hydrate graph, runner, record messages and traces, browser test controls. No server lifecycle |
packages/core-nodes/src/nodes/run-inner-graph.ts |
Nested execution | Normalize child graph, inherited metadata and executor resolution, inherited context, runner, failure interpretation and parent output mapping. No independent Python, persistence, timeout or workspace cleanup |
examples/workflow_runner/js/main.js |
Browser execution, different class | Constructs the example’s HTTP/WebSocket client named WorkflowRunner, not the kernel runner. Owns transport and UI |
Electron’s createWorkflowRunner() constructs a client store, not the kernel
runner. README examples are documentation rather than execution entry points.
Boundary
Top-level Node/server execution uses ExecutionSession. Public APIs keep their
own graph preparation and result presentation. Nested execution can construct a
child kernel runner with its inherited environment. Browser execution uses the
portable runWorkflow API. Kernel tests and browser harnesses can construct the
runner directly. npm run check:execution-boundary guards production sources.
Session responsibility audit
| Behavior | Ownership |
|---|---|
| Normalize, hydrate, construct runner, cancel, timeout, terminal result | Canonical lifecycle |
| Python connection, worker job boundaries and owned bridge close | Canonical lifecycle. A shared host bridge remains host-owned |
| Scratch cleanup, including setup failure | Canonical lifecycle |
buildWorkspaceExecutionContext |
Server default. An injected context overrides secrets, storage, workspace and generation durability |
| Headless permission gate | Server default, explicitly disabled by hosts preserving another permission policy |
| Cost ledger | Optional integration, enabled by default for existing server callers |
| Durable generation tracking | Context preparation, enabled by the default server context and saved-workflow service. Injected contexts choose their own hooks |
| Output name rewriting | Optional presentation policy, enabled with requireTerminalResult |
| Persistence | Optional host hooks. Job rows, progress, interactive sessions and receipt formatting remain in adapters |
| Buffered message iteration | Portable coordination shared with the browser runner |
The DSL keeps its explicit graph flags and output keys, secret resolver and registry precedence. Its preflight and cost recording remain disabled. Its result adapter continues throwing on failed runs and node error messages.
Shared-server bridge bootstrap and metadata preparation remain host work. WebSocket graph preparation still precedes session normalization and hydration. Nested execution retains its parent context and output mapping.
Adapter changes
DSL run() and runGraph() retain their signatures and return records of the
last value for each nonempty output. Registry resolution stays caller registry,
global registry, builtin packs, then Python. The DSL no longer connects or closes
Python, constructs a runner, or invokes the kernel run method. Graph conversion,
context inputs, registry policy and result-to-exception conversion stay in the
adapter.
The saved-workflow service and scripts/run-workflow.mjs also use the session.
The file runner keeps its existing preflight, permission and cost-recording
policies through explicit options rather than inheriting server defaults.
The service retains ownership checks, preflight before lazy bootstrap, metadata
preparation, job progress, background receipts and interactive verdicts. Its
shared Python bridge now receives session job boundaries without being closed.
Actor failures now produce a canonical failed run. The legacy DSL check for node error messages remains separate from the session and is tested against a message-only result. A cancellation can also retain node errors while reporting a cancelled terminal status.
The session adds these options for known host requirements:
| Option | Requirement |
|---|---|
executorResolverFactory |
DSL registry precedence needs the bridge the session owns |
hasTsExecutor |
Python detection must consult every DSL TS registry |
bridgeOptions |
Preserve DSL remote worker configuration without wrapping bridge lifecycle |
preflight |
Preserve DSL behavior and avoid repeating the service’s pre-bootstrap preflight |
installHeadlessPermissionGate |
Preserve an injected DSL context’s permission policy |
context and recordCosts already existed. The DSL uses them to preserve its
secret resolver and avoid adding persistence.
Behavior verification
The DSL differential tests retain the previous kernel path as a test oracle. They compare outputs, status, errors and retained messages for independent outputs, actor failure and an empty terminal. Job IDs and durations are excluded from comparison. Registry tests cover caller precedence, global precedence over builtin packs, builtin fallback and Python fallback with required secrets. Python tests stub the transport rather than starting a worker.
Lifecycle tests cover bridge closure on completion, failure and partial setup, workspace cleanup on setup failure and execution errors, explicit cancellation and timeout. The cleanup test fails against the previous session when hydration throws after connecting the bridge. Existing runtime tests cover closure after local or remote bridge connection failures.
Portable tests cover ordered delivery, terminal delivery after abort, queue draining, listener cleanup and abort before start. The cancellation terminal test fails against the previous portable runner. Message-buffer tests drain a large backlog and exercise overflow. Architecture tests exercise renamed and namespace imports while ignoring comments and types.
No output, status, retained-message or exception differences were found in the representative DSL comparisons. Observable lifecycle changes are scratch cleanup after setup failure, shared-worker job boundaries in service runs, and portable terminal delivery after cancellation. Early portable iterator closure now cancels and waits for the run instead of leaving it executing without a consumer.
Remaining duplication
The saved-workflow service and WebSocket host still prepare metadata before the session hydrates the graph. Moving that preparation requires preserving unknown nodes, list property types and each surface’s output keys. Server preflight is also performed before lazy service bootstrap, using the shared preflight helper. Shared bridge bootstrap remains a host responsibility because its lifetime spans runs. Nested graph normalization and output mapping remain local to the child execution contract.