Run Modes & Backends
Pipelex separates what the inference leaves do (the run mode) from where the work executes (the backend). The two axes are independent: any run mode works on any backend, because the run mode travels with every inference job and is honored at the leaf — the last stop before a provider would actually be called.
Axis 1: Run Mode
| Run mode | CLI trigger | Python trigger | AI providers | Usage & cost report |
|---|---|---|---|---|
| Live (default) | — | — | Real calls | Real usage, report rendered |
| Dry run | --dry-run |
pipe_run_mode=PipeRunMode.DRY |
Never called — leaves build mocks | Zero tokens, report suppressed |
Live runs call the configured AI providers for real: real cost, real latency, and storage IO for generated images and extracted pages.
Dry run exercises the full pipeline — controllers, working memory, data flow, execution-graph tracing — with no AI cost. Each inference leaf branches to a format-compliant mock instead of calling the provider (see Dry Run Mock Generation). A dry run needs no API keys, performs no storage IO (mocked images and pages never touch the storage provider), reports zero-token usage, and suppresses the end-of-run cost report. Add --mock-inputs to also synthesize any missing required inputs (it requires --dry-run).
Axis 2: Backend
| Backend | Trigger | What runs where |
|---|---|---|
| Direct (default) | — | Everything in your process |
| Temporal | --temporal, or config [temporal] is_enabled = true |
Controllers as child workflows, inference leaves as activities on workers |
The run mode rides across the wire with every inference job, so the backend never changes what a leaf does — only where it does it. In particular, when you run a pipeline in dry mode on Temporal, the real inference activities are still dispatched and mock inside them on the worker. That's by design: it lets you test the entire distribution machinery — dispatch, scheduling, serialization, task-queue routing, cross-worker graph tracing — with zero AI spend and no credentials.
One job type deliberately opts out of this fan-out: a validation sweep submitted through the runner API runs as a single in-process activity instead of dispatching nested activities — see Validation Sweeps below for how that fits the model.
The Matrix
All cells below describe pipeline runs — pipelex run ... or PipelexRunner.execute_pipeline(). Validation sweeps have their own distribution shape, covered in the next section.
| Run mode \ Backend | Direct (in-process) | Temporal |
|---|---|---|
| Live | Standard local run. | Durable distributed run; real inference executes on workers. |
| Dry run | Leaves mock inline. Validates pipeline logic and data flow at zero cost. | Activities are genuinely dispatched and mock inside the worker — proves out the distribution machinery with no API keys, no spend, no storage IO. |
# Live + direct (the default)
pipelex run bundle my_bundle.mthds
# Dry run + direct
pipelex run bundle my_bundle.mthds --dry-run --mock-inputs
# Live + Temporal
pipelex run bundle my_bundle.mthds --temporal
# Dry run + Temporal — dispatches real activities, mocks inside them
pipelex run bundle my_bundle.mthds --temporal --dry-run --mock-inputs
From Python, the same knobs live on the runner — the backend comes from config:
from pipelex.pipe_run.pipe_run_mode import PipeRunMode
from pipelex.pipeline.runner import PipelexRunner
runner = PipelexRunner(
bundle_uri="path/to/my_bundle.mthds",
pipe_run_mode=PipeRunMode.DRY,
)
response = await runner.execute_pipeline()
Validation Sweeps
pipelex validate (see the validate commands) combines static validation with a dry run of every pipe. It relates to the matrix in two specific ways:
- The CLI sweep always runs in-process, even when Temporal is enabled. The
--temporalflag onpipelex validateexists precisely to verify that: it flips the boot config so you can confirm a validation sweep does not dispatch anything under a Temporal-enabled setup. - The runner API's
/validateroute ships the sweep to a worker — as one activity: with Temporal enabled, it offloads the whole sweep as a single activity (act_dry_validate, wrapped in theWfDryValidateworkflow). Inside that activity, the pipe router and the content generator are scoped to their in-process variants, so nothing nested is dispatched: every dry run in the sweep behaves exactly like the dry × direct cell of the matrix, just hosted on a worker instead of your machine. The per-pipe statuses plus the execution graph come back in one round-trip. See Temporal integration for the mechanics.
This is not a contradiction of "the run mode never changes the backend" — it's the same leaf-mock foundation with a different backend choice for the nested work. A --temporal --dry-run pipeline run picks the Temporal backend all the way down, fanning out for real to test the distribution machinery. A Temporal validation sweep picks the Temporal backend for exactly one hop (get the sweep onto a worker) and the in-process backend for everything inside, because its goal is a cheap interactive answer, not distribution testing. Two deliberate opposites built from the same parts.
Forced Dry Mode
When a process boots without inference configured — Pipelex.make(needs_inference=False), e.g. no AI provider set up — every run that process initiates is forced to dry-run mode. This is a property of the run, not of the backend: under a Temporal-enabled boot the forced-dry run still dispatches activities and mocks inside them, like any other dry run. A warning logs whenever a live-requested run is forced dry.
See Also
- Validation & Dry Run — feature overview
- Dry Run Configuration — mock list sizes, allowed-to-fail pipes
- Cost Tracking & Reporting — what the usage reports contain
- Distributed Execution — running on Temporal
- Dry Run Mock Generation — how format-compliant mocks are built