Skip to main content

Pipelines

A pipeline is a directed graph (a sequence of steps where output flows forward) of agent nodes that processes an artifact through a series of steps. Each step can read, transform, route, or gate the artifact.

Pipeline structure

Pipeline config
{
  "name": "pr-review-pipeline",
  "nodes": [
    { "id": "fetch-diff", "type": "connector", "connector": "github", "action": "get_diff" },
    { "id": "analyze", "type": "llm", "model_backend": "anthropic", "prompt": "Review this diff..." },
    { "id": "human-review", "type": "gate", "human_only": true },
    { "id": "post-comment", "type": "connector", "connector": "github", "action": "create_comment" }
  ],
  "edges": [
    { "from": "fetch-diff", "to": "analyze" },
    { "from": "analyze", "to": "human-review" },
    { "from": "human-review", "to": "post-comment" }
  ]
}

Node types

Node types
Type Purpose
llm Calls an LLM via the configured model backend
connector Reads/writes through a connector (GitHub, Linear, etc.)
gate Evaluates a condition and passes or blocks
manual Placeholder for a step done outside Modulo; run pauses for human output
trigger Starts a pipeline execution (manual or webhook)
transform Transforms the artifact without an LLM call

Edge routing

By default, a node runs after all its upstream nodes complete. You can configure conditional routing:

Pipeline config
{
  "from": "check-quality",
  "to": "human-review",
  "condition": "{{ artifact.score < 0.8 }}"
}

Snapshot management

Every pipeline run produces a snapshot – a complete record of the input, all intermediate outputs, and the final artifact. Snapshots can be:

  • Replayed – re-run a pipeline with the same inputs to debug agent behavior
  • Compared – diff two snapshots to see how changes in prompts or models affect output
  • Exported – download as JSON for offline analysis or compliance

Pipeline execution (§8.4)

When a pipeline run is triggered, the Modulo engine compiles the pipeline config into a LangGraph state graph and executes each node in topological order.

Run lifecycle

  1. Pending – run is queued, waiting for a capacity slot
  2. Running – nodes execute sequentially; checkpoints are persisted after each node
  3. Awaiting human – run paused at a HITL gate; a human must claim and decide (approve, reject, or modify output)
  4. Completed – all nodes exhausted without error; eval suite thresholds checked
  5. Failed – an unhandled exception, eval block, output rejection, or lock timeout occurred
  6. Cancelled – cancellation was requested via the run detail

Capacity & concurrency

Each pipeline has a max_concurrent_runs setting. The executor serialises capacity checks via SELECT FOR UPDATE on the pipeline row to prevent race conditions. If the capacity slot cannot be acquired within lock_wait_timeout_seconds, the run fails with error_code="lock_timeout". For guidance on sizing this setting to your infrastructure, see Concurrency & capacity sizing.

Checkpointing

After every node execution, the LangGraph state is checkpointed to PostgreSQL using the ModuloPostgresSaver (tenant-isolated via organisation_id column and encrypted at rest via Fernet). Runs can be resumed from the last checkpoint after a server restart.

HITL (Human-in-the-Loop)

HITL gates pause execution at an edge boundary. The gate checks:

  1. Conditional gating – a JMESPath condition on the gate config; if falsy the gate is skipped
  2. Eval-before-interrupt – node-scoped eval definitions run against state; blocking failures raise EvalBlockedError
  3. Autonomy levelmanual_approval (interrupt), notify_on_complete (auto-approve), or fully_autonomous (skip)
  4. human_only flag – overrides autonomy; always interrupts

Claims are managed via HITLManager with short-lived JWT tokens and configurable TTL.

Error recovery

Failed or awaiting-human runs can recover individual manual-input nodes via POST /recover – either re-running with new input or skipping the node. Runs interrupted for HITL resume from the interrupted node via POST /runs/{id}/resume.

Runaway protection

Three independent guards prevent runaway runs:

  • Max duration – wall-clock timeout
  • Max steps – node completion count limit
  • Token budget – cumulative token usage limit

Any guard violation terminates the run with error_code="runaway".

Events & observability

The executor emits events (node_started, node_completed, node_failed, run_completed, hitl_awaiting, run_cancelled) through a per-run RunEventBroker. WebSocket subscribers receive live events; a 100-event ring buffer supports reconnection replay. OpenTelemetry spans are emitted via the LangGraphOtelBridge callback handler.

Manual (placeholder) nodes

A manual node represents a step performed by a human outside Modulo. When a run reaches a manual node, it pauses (status: awaiting_human) and waits for a human to submit the output through the HITL review UI. The output is validated against the node’s schema before the run continues.

Manual nodes are the primary tool for SDLC onboarding: teams map their existing process – including hand-operated steps – into Modulo and get a governed, observable pipeline immediately, even before any AI agents are added.

Step replacement

Manual nodes can be promoted to AI agent nodes without rebuilding the pipeline:

  • Convert to Agent (POST /{pipeline_id}/nodes/{node_id}/convert-to-agent) – replaces a manual node with an agent node. Requires an agent_id, model_backend_id, and connector_binding. The node type changes to agent and the output schema reference is removed (the agent produces its own output).
  • Revert to Manual (POST /{pipeline_id}/nodes/{node_id}/revert-to-manual) – restores an agent node back to manual using a snapshot that captures the original manual configuration. Requires a snapshot_id query parameter pointing to a snapshot where the target node had type manual with an output_schema_id.

Both operations are available from the pipeline editor UI via the node property panel.

Visual pipeline builder

The Stage Board UI provides a drag-and-drop canvas for building pipelines. Nodes snap to a grid, edges are drawn by clicking between ports. The canvas serializes to the JSON format above.