A workflow is a binding of a runbook (the deterministic Python templates that drive the process) to an entity, optionally on a schedule. Outside of production (which uses Temporal for durability + replay), runbook code runs in-process underDocumentation Index
Fetch the complete documentation index at: https://docs.ntropii.com/llms.txt
Use this file to discover all available pages before exploring further.
LocalRuntime — no broker, no Docker, no temporalio cluster required.
This page covers when and how to use it.
When LocalRuntime is active
The runtime backend is auto-selected at first attribute access on the runbook / activity facades. LocalRuntime wins when:
| Context | temporalio installed | TEMPORAL_HOST set | Runtime |
|---|---|---|---|
| ntro-worker pod (production) | yes | yes | TemporalRuntime |
| CI smoke job | yes | no | LocalRuntime |
pytest / python -m runbooks.X | maybe | no | LocalRuntime |
| Claude MA sandbox | no | no | LocalRuntime |
A minimal local run
ntro.workflow.run_local is the entry helper for a one-shot in-process run. It instantiates the runbook, sets up the WorkflowInfo contextvar, and awaits the @runbook.run method.
What you get under LocalRuntime
- Direct activity dispatch.
runbook.execute_activity(fn, arg)directlyawaitsfn(arg)with anActivityInfopushed onto a contextvar. Same call shape as Temporal — same signature, same semantics, just no broker. - Slug-based child workflows.
NtroWorkflow.run_child_workflow(slug=…)resolves the slug viantro.workflow.registry(populated when the bundle module is imported) and awaits the child’s@runbook.run. - Signal-driven gates.
@runbook.signalhandlers are plain methods; fire them from any other coroutine on the same event loop andwait_conditionpredicates pick up the state change at 50ms polling granularity. - Workflow + activity context.
runbook.info()andactivity.info()return small dataclasses withworkflow_id/activity_idsynthesised at entry, so introspection code that’s used to Temporal works unchanged.
What you don’t get
LocalRuntime is deliberately thin:
- No durability. A KeyboardInterrupt or process crash mid-run loses everything. Use Temporal in production; use the local runtime for the inner loop.
- No retries. Activity options like
start_to_close_timeout,retry_policy,heartbeat_timeoutare accepted and discarded. Wrap the activity call inasyncio.wait_forif you want a local timeout. - No replay. Workflow code that uses
datetime.now()/time.time()/random.X/uuid.uuid4()directly will silently work underLocalRuntimebut produces non-deterministic Temporal history in production. The N-102 determinism lint flags these in CI — see Build runbooks for the standard alternatives (runbook.now(), value-via-activity-input). - No sandboxing. Workflow code runs in the same process as your tests / dev script. Don’t rely on isolation behaviour Temporal provides.
Forcing a specific runtime from code
If your test fixture needs to pin a runtime (e.g. an integration test that always wantsLocalRuntime regardless of env), call set_runtime before importing any runbook module:
get_runtime() call), swapping is unsafe — existing decorators have already been applied to user code.
Related
Build runbooks
Concept walkthrough for authoring a runbook.
ntro.workflow reference
Full API surface — facade methods, runtime backends,
WorkflowError.Testing locally (harness)
ntro workflow test — scenario-driven test runner that pairs with a real in-memory Temporal for replay-faithful coverage.Deploy to production
ntro workflow create --path — push the same runbook code that ran locally to a Temporal-backed worker.