Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.ntropii.com/llms.txt

Use this file to discover all available pages before exploring further.

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 under 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:
Contexttemporalio installedTEMPORAL_HOST setRuntime
ntro-worker pod (production)yesyesTemporalRuntime
CI smoke jobyesnoLocalRuntime
pytest / python -m runbooks.XmaybenoLocalRuntime
Claude MA sandboxnonoLocalRuntime
A single INFO log line on the first call confirms which is active:
ntro.workflow: LocalRuntime active (no Temporal detected; running in-process)

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.
# runbooks/nav-monthly/main.py — sketch
import asyncio
from ntro.workflow import run_local

from .templates.workflow import NavMonthlyWorkflow
from .templates.models import NavMonthlyContext


async def main():
    ctx = NavMonthlyContext(
        period="2026-03",
        tenant_slug="acme-fund",
        entity_slug="acme-spv1",
        # …whatever your context needs
    )
    result = await run_local(NavMonthlyWorkflow, ctx, workflow_id="local-test")
    print(result)


asyncio.run(main())
Then:
python -m runbooks.nav-monthly.main
# → ntro.workflow: LocalRuntime active (no Temporal detected; running in-process)
# → PeriodSummary(…)
Iteration cycle: sub-second per run (modulo your activities’ own work — DB writes, LLM calls, etc.).

What you get under LocalRuntime

  • Direct activity dispatch. runbook.execute_activity(fn, arg) directly awaits fn(arg) with an ActivityInfo pushed 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 via ntro.workflow.registry (populated when the bundle module is imported) and awaits the child’s @runbook.run.
  • Signal-driven gates. @runbook.signal handlers are plain methods; fire them from any other coroutine on the same event loop and wait_condition predicates pick up the state change at 50ms polling granularity.
  • Workflow + activity context. runbook.info() and activity.info() return small dataclasses with workflow_id / activity_id synthesised 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_timeout are accepted and discarded. Wrap the activity call in asyncio.wait_for if you want a local timeout.
  • No replay. Workflow code that uses datetime.now() / time.time() / random.X / uuid.uuid4() directly will silently work under LocalRuntime but 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 wants LocalRuntime regardless of env), call set_runtime before importing any runbook module:
import os
os.environ.pop("TEMPORAL_HOST", None)

from ntro.workflow import set_runtime
from ntro.workflow.runtime import LocalRuntime
set_runtime(LocalRuntime())

# Now import your runbook — its decorators see LocalRuntime.
from runbooks.nav_monthly.templates.workflow import NavMonthlyWorkflow
Once the cache is primed (first get_runtime() call), swapping is unsafe — existing decorators have already been applied to user code.

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.