ntro.workflow. For the LLM-facing layer of a runbook, see skill definitions. To delegate a step to an external agent, see Register agents.
Anatomy of a runbook bundle
A runbook lives inrunbook-templates/runbooks/<slug>/:
/workflows/<slug>/<version>/ for bundles at boot and registers each runbook’s workflow + activities with Temporal. Deploys land under that path via ntro workflow create --path ….
The shape of a runbook
Every runbook subclassesNtroWorkflow and decorates each phase with @runbook.step. The decorator both attaches metadata for the Tenant UI breadcrumb and wraps the method so step lifecycle (_current_step_id, _steps_completed) tracks automatically.
@runbook.defnand@runbook.stepboth come from therunbookfacade inntro.workflow(the N-102 runtime-agnostic surface) — not fromtemporaliodirectly.defnbinds the class to whatever runtime is active (Temporal in the worker,LocalRuntimein tests); going through the facade is exactly what lets the same code run in both. (@runbook.stepis the facade alias of@ui_step— either spelling works.)@runbook.stepthreads UI metadata into the breadcrumb and wires the step lifecycle, so the Tenant UI can render the right component for this step.
__dict__ insertion order). Icons are Lucide names rendered by the Tenant UI.
What NtroWorkflow gives you
Beyond the bare Temporal workflow surface, NtroWorkflow bakes in the patterns runbooks need:
HITL approvals — wait_for_action
HITL approvals — wait_for_action
Block on a human approve / reject / correct signal:The
display_hint tells the Tenant UI which review component to render (extraction review, journal review, NAV signoff, etc.). The signal is dispatched from the UI when the human clicks Approve/Reject.External signals — await_signal_with_action
External signals — await_signal_with_action
Block until an external signal satisfies a predicate, advertising what’s needed via the Useful when the workflow waits for an upload, an email arrival, or any human-driven event that doesn’t have a fixed schedule.
current_pending_action query:Child workflows — run_child_workflow
Child workflows — run_child_workflow
Dispatch another runbook by slug:Children appear as their own progress trees in the Tenant UI under the parent’s step. Cardinality
many lets you fan out and join.External agents — ntro.workflow.agents.invoke
External agents — ntro.workflow.agents.invoke
Delegate a step to a registered external agent (Claude Managed, Copilot, …):The adapter starts a session on the agent’s host platform, polls until terminal, pulls any output files, and persists them to
ingest.submitted_documents with source='agent_output:<agent_id>'. See Register agents for the agent-side lifecycle.UI queries and signals — wired automatically
UI queries and signals — wired automatically
The base class registers
current_pending_action, current_steps, and current_ui_state queries plus the user_action signal handler at construction. Your subclass doesn’t need to do anything — the Tenant UI starts polling them as soon as the workflow exists.Observable results — ntro.events
For values that update over time (extraction results being corrected, journals being adjusted), wrap them in ObservableResult so the Tenant UI can render live updates without re-fetching:
ObservableResult lives in ntro.events and is used by every runbook template’s models.py. It’s the bridge between activity outputs and the UI’s live-update channel.
Determinism rules
Workflow code (the@runbook.defn class — not activities) must be
deterministic: Temporal replays it from history to recover state, so the same
inputs must always produce the same sequence of commands. Anything that varies
per call — the wall clock, randomness, network I/O — breaks replay.
This bites silently under LocalRuntime:
non-deterministic code runs fine in tests but corrupts Temporal history in
production. The N-102 determinism lint flags the common offenders in CI.
The rules, and the deterministic alternative for each:
| Don’t (in workflow code) | Do instead |
|---|---|
datetime.now() / time.time() | runbook.now() — the workflow’s replay-safe clock |
random.* / uuid.uuid4() | Generate it in an activity and pass the value back in |
| Direct DB / HTTP / file I/O | Wrap the side-effect in an @activity.defn; call it via runbook.execute_activity |
os.environ / reading config inline | Thread config through the workflow’s Context input |
Related
Skill definitions
The runbook.md frontmatter + skill body that the platform’s runbook-creation assistant reads when guiding a user through configuring a workflow.
ntro.workflow API reference
Signatures and behaviour of
NtroWorkflow, @runbook.step, wait_for_action, await_signal_with_action, run_child_workflow.UI and Temporal signals
How Tenant UI actions reach workflows (
approve, row_action) end-to-end.Ingest contracts
ntro.ingest contracts and set_user_feedback for upload steps.Register agents
Bring an external agent (Claude Managed, Copilot) into a runbook step.
Testing
Run your
NtroWorkflow subclass locally with WorkflowHarness.