Skip to main content
Reliant provides two levels of workflow testing: static validation catches structural problems in your workflow definition, and scenario testing verifies runtime behavior with mocked events. Together, they ensure your workflows are both well-formed and behave correctly.

Static Validation

Reliant validates every workflow when it’s loaded. This catches structural problems before your workflow ever runs.

What Gets Validated

Static validation checks for:
  • Entry point validity: Every node listed in entry must exist in the workflow
  • Node references: Edges can’t reference non-existent node IDs
  • Input completeness: Every input must have either a default value or required: true
  • Loop structure: Loop nodes must have either a workflow reference or an inline definition
  • Edge consistency: Edge targets must be valid node IDs within the workflow
  • Reserved names: Input groups can’t use reserved names like default
  • CEL expression syntax: Template expressions and conditions are checked for valid syntax

Common Validation Errors

Validation runs whenever a workflow is loaded or saved. A project workflow file with errors is listed in Workflows → Library under Failed to load, with the error, and is not offered in the workflow picker; a workflow in the builder with errors cannot be published until they are fixed. The workflow tools an agent uses (create_workflow, edit_workflow, write_workflow) validate every write and return the problems, and get_workflow reports whether a workflow is valid. To check a file yourself, run reliant workflow validate path/to/workflow.yaml. It follows every ref: transitively, exits non-zero on any error, and with --trigger-payload dry-runs the workflow’s triggers against a sample event (see Automations).

When Static Validation Isn’t Enough

Static validation confirms your workflow is structurally sound, but it can’t tell you whether the workflow actually does what you intend. A workflow can pass all validation checks and still route to the wrong node, loop forever, or produce unexpected outputs. That’s where scenario testing comes in.

Scenario Testing

Scenario testing lets you verify workflow runtime behavior without running real LLM calls or tool executions. You define simulated events—mocked LLM responses and tool results—then assert expected outcomes like which nodes were reached, what outputs were produced, and whether the workflow completed successfully.

How It Works

Scenarios run on the real workflow runtime — the same engine that executes your workflow in a chat — inside an in-memory environment. Only the activity layer (LLM calls, tool execution, worktree and daemon operations) is replaced: each activity’s result comes from your mocked events. Edge routing, conditions, loops, joins, sub-workflows, spawns, CEL evaluation and save_message resolution are all production code, so a passing scenario proves the workflow actually runs. Any activity a scenario cannot mock fails the run by name; nothing ever executes for real.

Scenario Structure

A scenario YAML file defines:

Event Types

An event without a type must look like the real node’s output: it is checked against that node type’s output schema, so a mock cannot claim a shape the real node never produces.

Event Fields

Targeting Nodes

By default, events are consumed sequentially — the first triggered node gets the first event, and so on. Use the node field to target a specific node:
Multiple events for the same node are consumed in order each time that node is triggered.

Inner Nodes (Loops and Sub-Workflows)

For workflows with inline loops or inline sub-workflows using type: workflow with inline:, you can target individual nodes inside them using dot-separated qualified IDs:
The same dot-notation works for inline workflow nodes:
For nested structures, chain the IDs:

How inner nodes execute

Inline loops and inline workflow nodes always execute their bodies. Each inner node’s mock is looked up by its qualified ID (loop_id.inner_node_id, workflow_id.inner_node_id), inner conditions skip nodes exactly as they do at runtime, and a loop’s while and declared outputs: are evaluated after each iteration. Referenced loops or workflows (ref:) are black-boxed by default: an event on the node itself (node: review) supplies the node’s whole output, and the referenced workflow’s body does not run. When the scenario targets qualified inner nodes (node: review.agent_loop.call_llm), the referenced workflow is loaded and executes for real instead. To mock a loop as a unit — its body does not run — set black_box: true on its event; the event’s output is the loop’s aggregate output (_results, _iterations, declared outputs) exactly as downstream nodes read it. Events are always keyed by node path. Keying an event by a workflow ref (node: builtin://agent) is a validation error: one ref can back many nodes, so name the node that references it.

Expectations

The expect section defines assertions:
A node’s messages come from its save_message: block, or from the node itself for a save_message node, resolved by the real runtime against each mocked result. saved: false asserts it saved nothing; count asserts an exact number (a node in a loop saves once per iteration); content_not_contains rules text out.

Partial Testing

Use start_at and state to test from a specific point in the workflow:

Loop Testing Patterns

Single Iteration (Exit Immediately)

Multi-Iteration Loop

Provide events for each iteration. Events for the same node are consumed in order, one per execution:

Testing Loop Output Routing

When a loop’s inline workflow declares outputs:, those evaluated outputs are available for downstream edge conditions:

Using Scenario Tools

An agent manages scenarios with these workflow tools (in any chat, load_tool(name="tag:workflow") loads them): Scenarios are stored alongside the workflow and run instantly without network calls or LLM API usage.

Scenarios in your project

A project workflow’s scenarios live in .reliant/workflows/<name>/scenarios/, one scenario per .yaml file, where <name> is the workflow’s name: as a slug — not its file name. A workflow in blog.yaml that declares name: blog-content-pipeline keeps its scenarios in blog-content-pipeline/scenarios/:
.reliant
workflows
blog.yaml
blog-content-pipeline
scenarios
drafts_from_brief.yaml
rejects_empty_brief.yaml
The app lists these scenarios, and the CLI runs the same ones:
The built-in workflows keep their scenarios in this same layout, and the CLI carries them, so --include-builtins lists or runs them alongside yours from any directory. They are worked examples of every scenario feature on this page. A scenario file’s name: defaults to its file name. A file holding several scenarios is an error — split it into one file per scenario. Scenarios in the retired layouts (scenarios/<file>/ or <file>_scenarios.yaml) are also an error, and the message names the directory each file belongs in. A sub-workflow reference resolves the same way everywhere: ref: project://blog-content-pipeline names the workflow by its name:, in a chat, in a scenario, and in reliant workflow validate. validate follows every reference transitively, so a broken one is reported with the chain that reaches it — content-weekly.yaml → project://content-next → project://blog: no project workflow is named "blog"; blog.yaml is named "blog-content-pipeline" — and a reference cycle is an error.