Pipelines
A pipeline is a directed graph of agent nodes. Output flows forward through the graph as each step reads, transforms, routes, or gates the artifact on the way to its destination.
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
| Type | Purpose |
|---|---|
agent |
Delegates the step to an agent entity that carries the prompt, model backend, and connector binding |
llm |
Calls a large language model (LLM) via the configured model backend |
connector |
Reads/writes through a connector (GitHub, Notion, 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 |
The llm, connector, gate, manual, trigger, and transform node types mirror the corresponding agent type, so a node can be written inline in the pipeline definition. The agent node type instead points at a reusable agent stored in the workspace; converting a manual node to an agent sets this type (see Step replacement).
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
When a pipeline run is triggered, the Modulo engine compiles the pipeline config into a LangGraph state graph and executes nodes as their upstream dependencies complete.
Run lifecycle
- Pending - run is queued, waiting for a capacity slot
- Running - ready nodes execute; checkpoints are persisted after each node
- Awaiting human - run paused at a human-in-the-loop (HITL) gate; a human must claim and decide (approve, reject, or modify output)
- Completed - all nodes exhausted without error; eval suite thresholds checked
- Failed - an unhandled exception, eval block, or output rejection occurred
- Rejected - the run ended without being a failure. Usually a reviewer rejected it at a human review gate that had no reject route. The same status also covers two supersession cases: a newer version replaced the waiting review (
hitl.superseded), or the run waited in the capacity queue past the pipeline’squeue_timeout_seconds(queue.expired). - Cancelled - cancellation was requested via the run detail
Capacity and concurrency
Each pipeline has a max_concurrent_runs setting (default 5) that caps how many of its runs execute at once. Modulo enforces it with count-based checks: a run over the cap is queued, meaning it stays pending, holds no slot, and is re-checked for capacity every minute until it starts. Runs waiting for a human decision hold no pipeline slot either. A capacity-queued run waits indefinitely by default.
Each pipeline can also set queue_timeout_seconds, which bounds that wait. It is nullable, and empty means never (the default). The window is anchored on when the run was queued and applies only to capacity-queued runs. On expiry the run is superseded - status rejected, error code queue.expired - so it is visible in the runs list as expired while queued, but it is not a failure and pages nobody. Set it in the pipeline editor toolbar (Queue timeout (s)), or through PATCH /api/v1/pipelines/{pipeline_id}. A cron pipeline should set it to its schedule’s period (an hourly cron sets 3600) so a stale fire drops instead of running hours late; webhook, manual and Model Context Protocol (MCP) triggered pipelines should leave it empty.
This cap works together with the organization-level limits and the per-pipeline trigger backpressure guard, so the full model, the defaults, and the sizing guidance live on Concurrency and capacity sizing.
Checkpointing
After every node execution, the LangGraph state is checkpointed to PostgreSQL using the ModuloPostgresSaver (tenant-isolated via organization_id column and encrypted at rest via Fernet). Runs can be resumed from the last checkpoint after a server restart.
Human-in-the-Loop (HITL) gates
HITL gates pause execution at an edge boundary. The gate checks:
- Conditional gating - a JMESPath condition (JSON query language) on the gate config; if falsy the gate is skipped
- Eval-before-interrupt - node-scoped eval definitions run against state; blocking failures raise
EvalBlockedError - Autonomy level -
manual_approval(interrupt),notify_on_complete(auto-approve), orfully_autonomous(skip) - human_only flag - overrides autonomy; always interrupts
Claims are managed via HITLManager with short-lived JSON Web Token (JWT) credentials and configurable time-to-live (TTL). See Human-in-the-Loop for the full claim workflow and rejection handling.
Error recovery
Failed or awaiting-human runs can recover individual manual nodes via POST /api/v1/runs/{run_id}/nodes/{node_id}/recover, either replaying the node with new input or skipping it. Runs paused at a HITL gate resume when a reviewer claims and approves (or rejects) the gate; see Human-in-the-Loop.
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 Software Development Lifecycle (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 /api/v1/pipelines/{pipeline_id}/nodes/{node_id}/convert-to-agent) - replaces a manual node with an agent node. Requires anagent_id,model_backend_id, andconnector_binding. The node type changes toagentand the output schema reference is removed (the agent produces its own output). - Revert to Manual (
POST /api/v1/pipelines/{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 asnapshot_idquery parameter pointing to a snapshot where the target node had typemanualwith anoutput_schema_id.
Both operations are available from the pipeline editor UI via the node property panel.
Visual pipeline builder
The Pipeline Editor (/pipelines/:id/editor) 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.
Related
- Agents: the atomic units that make up a pipeline
- Triggers: the manual, webhook, cron, and other ways a run starts
- Connectors: tool integrations for pipeline nodes
- Lifecycle Maps: the SDLC-wide view that places a pipeline as one stage
- Human-in-the-Loop: review gates and governance
- Variant Groups: weighted alternatives that extend a pipeline without changing it
- Concurrency and capacity sizing: the run and pipeline quotas that bound dispatch