Home

Pipelines

Pipelines let you define multi-stage AI workflows as directed graphs using Graphviz DOT syntax. Each node in the graph is a task — an LLM call, a human review gate, a conditional branch, or a parallel fan-out — and edges define the flow between them.

Pipelines are declarative: you describe what the workflow looks like, and the execution engine decides how and when to run each stage.

Note

Node and graph attribute names can be written in kebab-case, snake_case, or camelCase. We recommend kebab-case for readability, and this page uses that casing.

Quick start

A pipeline is a digraph block in a workflow file. Here is a minimal pipeline that searches for literature, summarizes findings, and drafts a report:

Key elements:

  • digraph declares a directed graph. Every pipeline must be a single digraph.

  • graph [goal="..."] sets a pipeline-level goal. The $goal variable is expanded in node prompts.

  • Start and End are the entry and exit points, recognized automatically by name.

  • Nodes are tasks. By default they run as LLM stages.

  • Edges (->) define the flow between stages.

Nodes

Every node represents a task in the pipeline. The node's shape determines what kind of task it is. In practice, you rarely need to set shape explicitly — the shorthand conventions below (node ID prefixes and property shortcuts) cover most use cases and are more readable.

Shapes

The recommended approach is to use node ID prefixes (e.g. Review, CheckQuality, FanOut) or property shortcuts (e.g. ask="...", branch="...", shell="...") instead of explicit shape= attributes. These shorthands are self-documenting and reduce boilerplate.

The table below shows the underlying shape mechanism for reference:

ShapePurposePreferred form
box (default)LLM taskAnalyze [prompt="Analyze the data"]
hexagonHuman review gateReview [ask="Review the draft"]
diamondConditional routingCheckQuality [branch="Meets criteria?"]
componentParallel fan-outFanOut [label="Search in parallel"]
tripleoctagonParallel fan-inFanIn [label="Collect results"]
invtriangleExplicit failureFail

Shorthand conventions

To reduce boilerplate, the pipeline engine recognizes certain node IDs and attribute names as implying a handler type. You do not need to set shape explicitly when using these conventions.

Node IDs

Node IDImplied shapeHandler type
Start, startMdiamondEntry point
End, end, Exit, exitMsquareExit point
Fail, failinvtriangleFailure
FanOut…componentParallel fan-out
FanIn…tripleoctagonParallel fan-in
Review…, Approve…hexagonHuman review
Check…, Branch…diamondConditional
Shell…, Run…parallelogramShell command

The first three are exact ID matches; the rest are prefix matches (e.g. FanOutSearch, FanInResults, ReviewDraft, CheckQuality). A node with a fan-out attribute also implies shape=component (parallel fan-out), regardless of its ID.

An explicit shape attribute always takes precedence over ID-based inference. If a node has a prompt or agent attribute, it is treated as an LLM task (box), overriding prefix-based ID inference — so ReviewData [prompt="Summarize the reviews"] stays a codergen node, not a human gate. Reserved structural IDs (Start/End/Exit/Fail) are exempt: they always receive their structural shape.

Property shortcuts

ShorthandExpands to
ask="Do you approve?"shape=hexagon, label="Do you approve?"
workflow="child"type="workflow"
shell="make build"shape=parallelogram, shell_command="make build"
shell="cargo test"shape=parallelogram, shell_command="cargo test"
branch="Quality OK?"shape=diamond, label="Quality OK?"
persist="full"fidelity="full", thread-id="persist:{node_id}"

Additionally, a node with an interview attribute (typically set via interview-ref) is inferred as shape=hexagon (a human gate).

Property shortcuts never override an explicitly set shape, label, type, or shell_command. All sugar keys (ask, workflow, shell, branch) are always normalized on the node, even when a higher-precedence shortcut wins.

Resolution order

When a node has no explicit shape, the engine applies the first matching rule:

  1. Property shortcutsask > shell > branch (all consumed regardless of which wins)

    • workflow is also treated as a property shortcut and normalized to a workflow handler node

  2. interview — node has an interview attribute (typically from interview-ref), inferred as human gate

  3. prompt or agent — node is an LLM task, prefix-based ID inference is skipped (structural IDs exempt)

  4. Node ID — exact or prefix match from the table above

Example — the combined example from below can be written more concisely:

Here CheckQuality is automatically a conditional node and Review is automatically a human gate — no shape= needed.

Common attributes

AttributeTypeDescription
labelStringDisplay name for the node. Used as the prompt fallback if prompt is empty.
promptStringInstruction for the LLM. Supports variable expansion (see below).
agentStringStencila agent to execute this node (e.g., "code-engineer").
agent.modelStringOverride the agent's model (e.g., "gpt-4o", "o3").
agent.providerStringOverride the agent's provider (e.g., "openai", "anthropic").
agent.reasoning-effortStringOverride reasoning effort ("low", "medium", "high").
agent.trust-levelStringOverride the agent's trust level ("low", "medium", "high").
agent.max-turnsIntegerOverride maximum conversation turns (0 = unlimited).
workflowStringRun another workflow as a composed child workflow (e.g., "code-implementation").
goalStringFor workflow nodes, override the child workflow's goal. If omitted, the child goal defaults to the node's resolved input (prompt, then label).
max-retriesIntegerAdditional retry attempts beyond the initial execution.
goal-gateBooleanIf true, this node must succeed before the pipeline can exit.
timeoutDurationMaximum execution time (e.g., 900s, 15m).
classStringComma-separated class names for overrides targeting.
question-typeStringHuman node question type: "yes-no", "confirm". "single-select', "freeform", Default: single select from edges.
storeStringContext key to store a node's output in. On human nodes, stores the answer; on shell nodes, stores trimmed stdout.
store-asStringShell node output format: "json" (force JSON parse), "string" (no parse), or absent (try JSON, fall back to string).
persistStringShorthand for setting fidelity and thread-id together. Values: "true"/"full", "summary", "gist", "details", "false"/"off".
fidelityStringContext fidelity mode for session carryover. "full" enables session reuse via thread-id. See Session persistence.
thread-idStringThread key for session reuse. Nodes sharing the same thread-id with fidelity="full" share a conversation session.
fan-outStringDynamic fan-out list key. Resolves a JSON array from context and spawns one branch per item. true derives key from node ID.
interview-refStringReference to a YAML code block defining a multi-question interview (e.g., "#review-interview").

Agent property overrides

When a node references an agent via agent="name", you can override specific agent properties inline using agent.* dotted-key attributes:

This lets you use a shared agent definition while customizing its behavior per-node — for example, running a particular stage with a more capable model or a different provider.

The override precedence order (highest to lowest):

  1. agent.* node attributes — explicit overrides on the node

  2. Overrides rules — from overrides selectors

  3. Agent definition — from the named agent's AGENT.md

  4. System defaults

Workflow composition

Use the workflow node attribute to run another workflow as a child workflow.

In this example, Implement does not execute a prompt directly. Instead, it resolves and runs the code-implementation workflow and maps the child workflow's result back into the parent pipeline.

When to use composition

Composition is most useful when:

  • a stage is complex enough to deserve its own internal graph

  • the same subprocess should be reused by multiple parent workflows

  • the parent workflow should stay readable by delegating detail to a child workflow

Prefer a normal node with prompt, agent, or shell when the stage is simple and unlikely to be reused.

Input, parent context, and output mapping

When a child workflow runs:

  • the parent run id and parent node id are tracked as parent metadata

  • the child receives context values identifying its parent workflow and parent node

  • any node input is passed to the child under input

  • the child workflow's goal is set from the node's explicit goal attribute when present; otherwise it defaults to the node's resolved input (prompt, then label)

  • the child workflow's final output is mapped back to the parent as that node's output

So, after a composed node completes, downstream parent nodes can continue using normal variables such as $last_output.

This means child workflows can usually keep using $goal whether they run standalone or as a composed subprocess.

If goal is omitted, the child goal falls back to the composed node's resolved input.

The parent also receives workflow-scoped context updates including:

  • workflow.output.<node-id> — the child workflow's final output for that composed node

  • workflow.outcome.<node-id> — the child workflow's full outcome object

Composition should be acyclic

Workflow composition should remain acyclic. A workflow should not compose itself directly or indirectly through another workflow.

Current validation rejects direct self-reference within the same workflow definition. Avoid indirect composition cycles as well, even if they are not yet detected statically.

Prompt variables

Use $-prefixed variables in prompt attributes to inject dynamic values:

VariableDescriptionWhen expanded
$goalThe pipeline-level goal from graph [goal="..."]Before the pipeline runs
$last_outputFull text of the previous stage's agent responseAt each stage
$last_outcomeOutcome status of the previous stage (success, fail)At each stage
$last_stageNode ID of the previous completed stageAt each stage
$KEYAny value from the pipeline context (see below)At each stage

$goal is expanded once before the pipeline starts. The other variables are expanded at execution time, so each stage sees the outputs of the stage that ran before it.

Tool-based alternative

Variable interpolation embeds prior output directly into the prompt text, which can bloat messages in iterative workflows where outputs are long (e.g. full drafts or detailed reviews). For these cases, prefer the workflow_get_output and workflow_get_context tools instead — the engine automatically tells agents about these tools, and agents fetch the content via background tool calls rather than receiving it inline. See Workflow context tools below.

Variable interpolation remains the best choice for short references (e.g. $goal, $last_stage), shell commands, and edge conditions.

Built-in variables

Example using runtime variables:

Context variables ($KEY)

Use $KEY to reference any value stored in the pipeline context. The key can contain letters, digits, underscores, and dots. Missing keys resolve to an empty string.

For example, a short inline reference to a stored context value:

However, for iterative workflows where the interpolated content may be long (full drafts, detailed reviews), prefer using the workflow_get_output and workflow_get_context tools instead — see Workflow context tools below.

Context values can come from several sources:

  • Human answers stored via the store attribute on human nodes

  • LLM tool calls that write to the context via workflow_set_context

  • Handler outputs like human.gate.selected and human.gate.label

Referencing multiline content from Markdown

Instead of escaping long prompts, shell scripts, or human questions inside DOT strings, you can define them in fenced code blocks or code chunks with ids and reference them from node attributes.

Recommended style:

  • put the executable DOT block first for easier scanning

  • define the referenced code blocks after the DOT block

  • use kebab-case attribute names in examples and authored workflows

  • use refs mainly for long or multiline content; keep short single-line values inline when that is clearer

Supported reference attributes:

Reference attributeResolves to
prompt-ref="#id"prompt
shell-ref="#id"shell_command
ask-ref="#id"human question label
interview-ref="#id"interview (multi-question interview spec)

Example:

```dot
digraph Example {
  Start -> Create -> Check -> Ask -> End

  Create [agent="writer", prompt-ref="#creator-prompt"]
  Check  [shell-ref="#run-checks"]
  Ask    [ask-ref="#human-question", question-type="freeform"]
}
```

```text #creator-prompt
Create or update the draft for this goal: $goal

Before starting, check for reviewer feedback from a previous iteration.
If feedback is present, use it to revise the existing draft instead of starting over.
```

```sh #run-checks
cargo fmt -p workflows
cargo test -p workflows
```

```text #human-question
What should change before the next revision?
```

References resolve only against code blocks and code chunks in the same WORKFLOW.md. It is an error if a referenced id does not exist, if ids are duplicated, or if a node sets both a literal attribute and its corresponding *_ref attribute.

Edges

Edges define transitions between nodes. They can carry labels, conditions, and weights to control routing.

For authored workflows, the recommended style is to keep outgoing edges close to their source node: define the node, then place its outgoing edge or edges immediately after it. A small exception is the initial Start -> … entry edge, which is often kept near the top of the graph.

Attributes

AttributeTypeDescription
labelStringDisplay caption. Also used for preferred-label matching.
conditionStringBoolean guard expression (e.g., "outcome=success").
weightIntegerPriority for edge selection. Higher weight wins among equally eligible edges.

Edge selection

After a node completes, the engine selects the next edge using this priority order:

  1. Condition match — edges whose condition evaluates to true

  2. Preferred label — edge whose label matches the handler's preferred label

  3. Highest weight — among unconditional edges, the highest weight wins

  4. Lexical tiebreak — alphabetical by target node ID

Chained edges

You can chain edges as shorthand:

This creates individual edges between each consecutive pair.

Conditions

Edge conditions use a simple expression language to gate transitions:

Supported syntax:

OperatorMeaningExample
=Equalsoutcome=success
!=Not equalsoutcome!=success
&&Logical ANDoutcome=success && context.citations_valid=true

Available variables:

  • outcome — the current node's result status (success, fail, retry, partial_success)

  • preferred_label — the handler's preferred edge label

  • context.* — values from the shared pipeline context

Keys starting with internal. are reserved and cannot be written by agents.

Agent routing with workflow_set_route

When an agent node has outgoing edges with labels, the engine automatically provides routing instructions so the agent can make a structured routing decision instead of relying on text output matching.

For sessions with tool support, the agent receives a workflow_set_route tool and calls it with the chosen label. For sessions without tool support, the agent is instructed to end its response with a <preferred-label> XML tag, which the engine parses.

For example, this review node gives the agent two labeled branches:

The agent sees both labels and signals its choice — via a tool call or XML tag — with either Accept or Revise. The engine matches the chosen label against outgoing edge labels (case-insensitive) and follows the corresponding edge.

This is more reliable than prompting the agent to reply with a specific word and matching against context.last_output, because routing is decoupled from the agent's text response. The agent can provide detailed feedback in its text output while separately signaling the routing decision.

Note that edge conditions (Step 1 in the edge selection algorithm) take priority over preferred labels (Step 2).

Workflow patterns

Linear pipeline

The simplest pattern: stages execute one after another.

Conditional branching

Use conditions on edges to route based on outcomes.

The Check prefix and branch attribute both imply a conditional (diamond) routing point. The engine evaluates edge conditions against the current outcome and context to decide which path to take.

Retry loops

Nodes can retry automatically on failure using max-retries:

max-retries=2 means up to 3 total executions (1 initial + 2 retries).

For more control, use edge-based retry loops that route back to an earlier stage on failure:

Shell nodes

Shell nodes run a command and capture its standard output. Use the Shell… or Run… node ID prefix, or the shell property shortcut:

The command's trimmed stdout is stored as shell.output and last_output in the pipeline context.

Shell output storage

The store attribute on a shell node writes the command's trimmed stdout into the pipeline context under a named key, in addition to the standard shell.output and last_output keys:

By default, the handler tries to parse the output as JSON. If the output is valid JSON it is stored as a typed value (array, object, number, etc.); otherwise it falls back to storing the raw string. This means a command that emits a JSON array produces a real list in the context, while a command that emits plain text is stored as-is.

The store-as attribute overrides this automatic behavior:

store-as valueBehavior
"json"Force JSON parse. Fails the node if the output is not valid JSON.
"string"Always store as a plain string, no JSON parsing.
(absent)Try JSON parse, fall back to string (the default).

This is primarily useful for producing structured data — like JSON arrays — that downstream nodes can consume as typed values. For example, a shell node can emit a JSON list that a dynamic fan-out node iterates over:

Note

Use kebab-case (store-as) in DOT attributes. The canonical form store_as is also accepted but kebab-case is recommended for consistency.

Goal gates

Mark critical stages with goal-gate=true to prevent the pipeline from exiting until they succeed:

If the pipeline reaches the exit node and any goal gate node has not succeeded, the engine looks for a retry-target to jump back to instead of exiting.

Human-in-the-loop

Use a Review (or Approve) node ID prefix, or the ask property shortcut, to create a gate that pauses for human input. The choices are derived from the node's outgoing edge labels:

The human is presented with the choices derived from the outgoing edges:

  • [A] Approve — continues to publication

  • [R] Revise — loops back to re-analyze

Accelerator keys

Each outgoing edge from a human gate becomes a selectable option with an accelerator key — a short string the user can type or press to quickly select that option. The engine extracts the key from the edge label using these formats:

FormatExampleParsed key
[K] Label[Y] Yes, deployY
K) LabelA) Option AA
K - LabelX - Choice XX
Plain label (fallback)DeployD

The space after the delimiter (], )) is optional — [Y]Yes works the same as [Y] Yes. Brackets support multi-character keys like [OK] Continue or [AB] Option AB. The parenthesis and dash formats are limited to a single character.

Explicit keys are optional. When a label has no recognized prefix, the engine automatically derives the key from the first character of the label (uppercased). For example, these two blocks are functionally equivalent:

When to use explicit keys:

  • Disambiguating collisions — If two labels start with the same letter (e.g., Staging and Send), auto-derived keys would both be S. Use [S] Staging and [X] Send to assign distinct keys.

  • Multi-character keys — The bracket format supports keys like [OK] that can't be expressed with the single-character fallback.

  • Choosing a more intuitive key — e.g., [N] No, abort deployment assigns N instead of the auto-derived N from "No" — same result here, but explicit keys make intent clear when the first letter isn't the most natural accelerator.

Here is an example with a three-way choice using auto-derived keys:

The engine derives keys S, P, D from the first letter of each label. No brackets are needed because the first letters are already unique.

Question types

By default, human nodes derive a multiple-choice question from their outgoing edge labels. You can override this by setting the question-type attribute:

question-typeDescriptionRouting
(default)Single-select (multiple choice) from edge labelsRoutes to the selected edge
"freeform"Free-form text inputFollows first outgoing edge
"yes-no"Yes/no binary choiceFollows first outgoing edge
"confirm"Confirmation promptFollows first outgoing edge

For non-choice types, the node always follows its first outgoing edge — there is no choice-matching step. The node still needs at least one outgoing edge for routing.

Storing answers (store)

The store attribute writes the human's answer into the pipeline context under a named key. Later nodes can reference this value using $KEY in their prompts:

When the human provides an answer, it is stored as a string:

  • Freeform text — the entered text

  • Single-select — the selected accelerator key

  • Yes/no"yes" or "no"

  • Timeout or skip — key is not set (resolves to "" when referenced)

Collecting human feedback

Combining question-type, store, and workflow context tools enables iterative workflows where a human can provide specific feedback that guides subsequent stages.

Here is a complete example of a create–review–revise workflow:

This pipeline:

  1. Creates the initial skill draft (or revises it based on feedback)

  2. Reviews the draft automatically with a reviewer agent

  3. Routes to human review on success, or loops back to revise on failure

  4. Asks the human whether to accept or revise

  5. If revising, collects freeform feedback that describes what to change

  6. The feedback is stored as human.feedback in the pipeline context. On the next iteration, the Create agent retrieves it using the workflow_get_context tool and retrieves the reviewer's output using workflow_get_output — both happen as background tool calls, keeping the prompt compact

Multi-question interviews

When a single human pause needs to collect multiple pieces of information — such as a routing decision and detailed feedback — use interview-ref to reference a YAML code block that defines a structured interview.

The YAML block specifies a preamble (optional context shown before the questions) and a questions array. Each question can have a type (defaults to freeform), options (for single-select and multi-select types), a default, and a store key for saving the answer to the pipeline context.

preamble: |
  Please review the implementation.

questions:
  - question: "Is the implementation ready to merge?"
    header: Decision
    type: single-select
    options:
      - label: Approve
      - label: Revise
    store: review.decision

  - question: "What specific changes should be made?"
    header: Feedback
    store: review.feedback

Routing is driven by the first single-select question's answer, matched against outgoing edge labels — the same mechanism as single-question human nodes. When an interview has no single-select question, the node follows its first outgoing edge. An interview node with no outgoing edges succeeds as a terminal node after collecting answers.

Storing answers — each question with a store key writes its answer to the pipeline context. Downstream nodes reference these values using $KEY expansion (e.g., $review.feedback in a prompt). Freeform questions without a store key will trigger a validation warning, since the answer would be collected but never stored.

Conditional questions — use show-if to display a question only when a previous answer matches a condition (e.g., show-if: "decision == Revise"), and finish-if to end the interview early when an answer matches a value (e.g., finish-if: "no" on a yes-no gate question). These can be combined to build branching interviews with early-exit gates.

Use interview-ref when a review step naturally combines routing with structured feedback. Use separate ask / ask-ref nodes when the questions are independent or belong to different pipeline stages.

See Creating Workflows — Multi-question interviews for the full spec format, conditional question examples, and guidance.

Parallel execution

Fan out to multiple branches using a FanOut… node ID prefix and collect results with a fan-in node. Branches can be defined statically in the graph (one edge per branch) or dynamically at runtime using the fan-out attribute. You can make the fan-in point explicit using a FanIn… node ID prefix. These prefixes imply shape=component and shape=tripleoctagon respectively, so you don't need to set the shape explicitly:

Branches execute concurrently. The fan-in node waits for all branches to complete before proceeding.

Dynamic fan-out

Static fan-out (above) fixes the number of branches at graph-definition time. The fan-out attribute adds dynamic fan-out, where the branch count is determined at runtime from a variable-length list in the pipeline context.

A node with fan-out must have exactly one outgoing edge pointing to the template entry node. The engine spawns one concurrent branch per list item, each executing the same downstream subgraph with per-item context.

The list is typically produced by an upstream shell node with store (see Shell output storage), but any source that writes a JSON array into the context works — for example, an agent node with context-writable=true can call workflow_set_context to store a JSON array (e.g., key items), and the fan-out node references it with fan-out="items".

Attribute forms:

FormBehavior
fan-out="key"Resolves the context key as a JSON array and fans out over it.
fan-out=trueDerives the key from the node ID in snake_case (e.g., FanOutReposfan_out_repos).

Per-branch context:

Each branch receives the following variables, accessible via $-expansion or workflow context tools:

VariableDescription
$fan_out.itemThe current list item (scalar or JSON object).
$fan_out.indexZero-based index of this branch.
$fan_out.totalTotal number of items in the list.
$fan_out.keyThe context key that was fanned out over.
$fan_out.item.<prop>For object items, individual properties are accessible directly.

After all branches complete, results are aggregated into parallel.results (list of per-branch outcomes) and parallel.outputs (list of per-branch outputs). The fan-in successor receives these as context values.

Empty lists: When the resolved list is empty, the handler skips directly to the fan-in successor — no branches are spawned.

Validation rules:

  • The fan-out node must have exactly one outgoing edge.

  • A warning is emitted if no tripleoctagon fan-in node is reachable from the template subgraph.

  • Nested dynamic fan-out (a dynamic fan-out inside another dynamic fan-out's template subgraph) is rejected.

Example — discover repositories with a shell node, then analyze each one in parallel:

This pipeline:

  1. Discovers repositories by running gh repo list and storing the JSON output as a typed array under repos

  2. Fans out dynamically — one branch per repository in the list

  3. Audits each repository concurrently, with $fan_out.item.name and $fan_out.item.url expanded per branch

  4. Collects results at the fan-in node

  5. Summarizes the combined findings

Composed subprocess

Use this pattern when a single stage in the parent workflow should expand into a reusable internal process:

The Review prefix alone implies a human gate — no shape= needed. The parent graph stays focused on orchestration, while paper-drafting can own the internal research, outlining, drafting, and checking stages.

Session persistence

By default, each agent node in a pipeline starts a fresh conversation — it has no memory of what earlier nodes said. Session persistence lets multiple nodes share a conversation session so that later nodes can recall context from earlier ones, enabling multi-turn interactions across pipeline stages.

This is useful when a pipeline has logically connected steps — for example, an agent that first reads a set of research papers and then synthesizes findings across them. Without session persistence, the synthesis step would not remember what the agent read; with it, the LLM sees the full conversation history and can build on its earlier reasoning.

persist shorthand

The simplest way to enable session persistence is the persist attribute, which sets fidelity and thread-id together:

Each node with persist="full" gets fidelity="full" and an auto-generated thread-id of the form persist:{node_id}. Since the thread IDs differ (one per node), these two nodes do not share a session — each starts fresh. To share a session across nodes, use an explicit thread-id (see below) or graph-level defaults.

persist values:

ValueExpands to
"true" or "full"fidelity="full", thread-id="persist:{node_id}"
"summary"fidelity="summary:medium", thread-id="persist:{node_id}"
"gist"fidelity="summary:low", thread-id="persist:{node_id}"
"details"fidelity="summary:high", thread-id="persist:{node_id}"
"false" or "off"(disabled — no fidelity or thread-id set)

If fidelity is already set explicitly on the node, persist does not override it or generate a thread-id. If thread-id is already set, it is preserved. The persist attribute is always removed after processing — it never appears in the final graph.

fidelity and thread-id

For full control, set fidelity and thread-id directly. The key concept: nodes that share the same thread-id with fidelity="full" reuse the same conversation session, so the LLM sees the full message history from all prior nodes on that thread.

Warning

A shared thread-id means shared conversation history. In practice, nodes that share a thread-id should use the same agent, because different agents may have different system prompts, tools, and behavior. Reusing one thread across different agents is invalid and fails workflow validation. Likewise, do not reuse the same thread-id across parallel branches.

In this example:

  1. Explore starts a session under thread "analysis" and examines the dataset

  2. Interpret joins the same "analysis" thread — the LLM sees the full exploratory analysis and can reference the specific proteins and outliers it identified, without needing them restated in the prompt

  3. WriteUp has no fidelity or thread-id, so it starts a fresh session — appropriate here because drafting the results section is an independent writing task that receives the prior output via the standard $last_output variable

Fidelity modes

The fidelity attribute controls how much conversation context is carried over:

ModeBehavior
"full"Full session reuse — the LLM sees the complete message history.
"summary:low"Low-detail summary of prior context (available via persist="gist").
"summary:medium"Medium-detail summary (available via persist="summary").
"summary:high"High-detail summary (available via persist="details").
"compact"Compact context carryover. Set directly — not available via persist.
"truncate"Truncated context. Set directly — not available via persist.
Warning

Currently, only full fidelity alters runtime behavior (session reuse by thread-id). The summary, compact, and truncate modes are resolved and validated but do not yet synthesize different context-carryover prompt text.

Graph-level defaults

Use the default-fidelity and default-thread-id graph attributes to set pipeline-wide defaults. This is the most concise way to make an entire pipeline share a single conversation:

All three nodes inherit fidelity="full" and thread-id="main", sharing a single conversation session throughout the pipeline. The Assess step remembers which studies Search found, and Synthesize remembers both the studies and the quality assessment — so the final review can reference specific papers and their limitations without restating them.

When to use session persistence

  • Multi-step analysis — an agent explores a dataset, identifies patterns, then interprets their significance. Each step should build on what came before.

  • Literature reviews — an agent searches for papers, screens them for relevance, then synthesizes findings. The synthesis is richer when the agent remembers what it read.

  • Iterative drafting — a manuscript section is written, then revised based on reviewer feedback, with the revision step aware of the original reasoning.

Session persistence is not needed when nodes are independent or when the relevant context is already passed via $-variable expansion or workflow context tools. Use it when the full conversation history matters — not just a single output value.

Overrides

Centralize agent property overrides with CSS-like rules instead of setting agent.model on every node:

Selectors and specificity:

SelectorMatchesSpecificity
*All nodesLowest
.class_nameNodes with that classMedium
#node_idSpecific node by IDHighest

Explicit agent.* attributes on a node always override values from the overrides rules.

Combined example

Here is a more complete pipeline combining several patterns:

This pipeline:

  1. Searches for papers using the default model (Sonnet)

  2. Screens for relevance with up to 2 retries on failure

  3. Analyzes using Opus (via .deep_analysis class) with a goal gate ensuring it must succeed

  4. Branches based on quality check — passing goes to review, failing loops back to re-analyze

  5. Pauses for human review with approve/revise options

  6. Formats the approved review for publication

Graph attributes

AttributeTypeDefaultDescription
goalString""Pipeline-level goal. Expanded as $goal in prompts.
labelString""Display name for the pipeline.
overridesString""CSS-like per-node agent override rules.
default-max-retryInteger3Global retry ceiling for nodes that omit max-retries.
default-fidelityString""Default context fidelity mode.
default-thread-idString""Default thread key for full fidelity session reuse.
retry-targetString""Node to jump to when goal gates are unsatisfied at exit.

Context and state

Nodes communicate through a shared key-value context. After each node executes, its outcome and any context_updates are merged into the context. Subsequent nodes can reference these values in edge conditions (e.g., context.citations_valid=true), in prompt variables (e.g., $last_stage), or by calling workflow context tools.

Context values come from several sources:

  • Human node store — the store attribute on human nodes writes the answer into a named key (e.g., store="human.feedback")

  • Shell node store — the store attribute on shell nodes writes trimmed stdout into a named key, with optional JSON parsing controlled by store-as

  • LLM tool calls — when an LLM writes context via the workflow_set_context tool

  • Handler outputs — built-in keys like human.gate.selected, human.gate.label, last_output, last_stage

  • Graph attributesgraph.* keys are mirrored into context at pipeline start

The engine also saves a checkpoint after each node completes. If the pipeline crashes, it can resume from the last checkpoint.

Workflow context tools

Agent nodes in a pipeline automatically receive workflow context tools that let them query pipeline state on demand via tool calls. This is the recommended way for agents to access prior outputs and stored values in iterative workflows, because the content is fetched in the background rather than being interpolated into the prompt text.

Available tools:

ToolPurpose
workflow_get_outputGet the output of a pipeline node by ID, or the most recent output if no node_id is given
workflow_get_contextRead a specific context key (e.g., human.feedback) or all context
workflow_set_contextWrite a value to the workflow context (requires context-writable=true on the node)
workflow_get_runGet metadata about the current run (name, goal, status, start time)
workflow_list_nodesList all workflow nodes with status and duration
workflow_store_artifactStore a file artifact associated with this run
workflow_get_artifactRetrieve a previously stored artifact by ID

The engine appends usage instructions to each agent's prompt when these tools are available. Agents are told to call workflow_get_output for prior output (e.g., reviewer feedback or a previous draft) and workflow_get_context for stored values (e.g., human.feedback).

When to use tools vs variables: Use $-variable interpolation for short values like $goal, $last_stage, and $last_outcome, and in shell commands and edge conditions where tools are not available. Prefer tools like workflow_get_output and workflow_get_context when the content may be long (full drafts, detailed reviews) to keep prompts compact.

Syntax reference

DOT basics

  • One digraph per file

  • Node IDs must match [A-Za-z_][A-Za-z0-9_]*

  • Edges use -> (directed only)

  • Attributes go in [key=value, key=value] blocks

  • String values use double quotes: "hello world"

  • Comments: // line and /* block */

  • Semicolons are optional

Duration values

Duration attributes like timeout use an integer with a unit suffix:

UnitExampleMeaning
ms250msMilliseconds
s900sSeconds
m15mMinutes
h2hHours
d1dDays

Subgraphs

Subgraphs scope default attributes for a group of nodes:

Nodes inside the subgraph inherit its defaults unless they explicitly override them.

© 2026 Stencila