Skip to main content
A workflow is a binding of a runbook (the deterministic Python templates that drive the process) to an entity, optionally on a schedule. A runbook may invoke external agents (Claude Managed, Copilot, …) from inside its steps when it needs LLM-driven artefact generation that doesn’t fit the deterministic capabilities surface. This page is the concept-level walkthrough for authoring a runbook. For API reference, see 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 in runbook-templates/runbooks/<slug>/:
The worker scans /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 subclasses NtroWorkflow 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.
Two things to notice:
  • @runbook.defn and @runbook.step both come from the runbook facade in ntro.workflow (the N-102 runtime-agnostic surface) — not from temporalio directly. defn binds the class to whatever runtime is active (Temporal in the worker, LocalRuntime in tests); going through the facade is exactly what lets the same code run in both. (@runbook.step is the facade alias of @ui_step — either spelling works.)
  • @runbook.step threads UI metadata into the breadcrumb and wires the step lifecycle, so the Tenant UI can render the right component for this step.
Class-definition order is the breadcrumb order in the UI sidebar (Python preserves __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:
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.
Block until an external signal satisfies a predicate, advertising what’s needed via the current_pending_action query:
Useful when the workflow waits for an upload, an email arrival, or any human-driven event that doesn’t have a fixed schedule.
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.
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.
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: The throughline: side-effects and entropy live in activities; the workflow only orchestrates. Activities run once and their results are recorded in history, so anything non-deterministic is safe there.

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.