Skip to main content
Reliant workflows let you automate complex multi-step tasks, enforce development processes, and coordinate multiple agents. There are three ways to create workflows, from fully visual to fully manual. Workflow Builder UI showing a TDD loop workflow

When to Create Custom Workflows

Before building a custom workflow, consider whether you actually need one. Workflows shine in specific scenarios: Automate repetitive multi-step tasks: If you find yourself repeatedly running the same sequence of agent interactions—like “analyze code, write tests, run tests, fix failures”—a workflow captures that pattern and makes it repeatable. Enforce specific processes: Workflows can encode your team’s practices. A TDD workflow that requires tests to fail before allowing implementation. A code review workflow that requires two agents to approve changes. A security audit that runs after every feature implementation. Create specialized agents: Sometimes you need an agent with specific tools, prompts, or behaviors. A workflow can define a “documentation writer” or “security auditor” persona with appropriate constraints. Build multi-agent coordination: When you need multiple agents working together—whether in debate, parallel competition, or sequential handoff—workflows provide the orchestration. If you just need to run a single agent with a specific prompt, consider using Presets instead. Workflows are for when you need control flow, loops, or multiple agents.

Three Ways to Create Workflows

Reliant provides three methods for creating custom workflows, each suited to different situations.

YAML (Manual)

Write workflow definitions directly in YAML files placed in .reliant/workflows/. This gives you full control over every aspect of the workflow—nodes, edges, conditions, loops, thread configuration, and inputs. Most of this guide covers YAML authoring in detail. Best for: Fine-tuning workflows, understanding exactly what’s happening, version-controlled workflow definitions.

Visual Editor

The visual editor provides a graphical interface for building workflows. You can drag and drop nodes, draw edges between them, and configure node properties through forms. The visual editor reads and writes the same YAML format, so you can switch between the visual editor and hand-editing YAML at any time. Best for: Exploring workflow structure visually, quickly prototyping node graphs, understanding how edges route execution.

AI Builder Assistant

The AI builder assistant creates workflows through conversation. Describe what you want the workflow to do, and the assistant generates the workflow definition for you. It can analyze your codebase to suggest appropriate tools, prompts, and patterns. The AI builder always creates scenario tests and runs static validation for every workflow it produces. This means workflows created by the AI builder come with built-in test coverage from the start. Best for: Getting started quickly, creating workflows for unfamiliar patterns, ensuring test coverage from day one.
All three methods produce the same YAML format. A workflow created with the AI builder can be edited in the visual editor or by hand, and vice versa.

Workflow File Location

Reliant automatically discovers workflow files in your project’s .reliant/workflows/ directory.
your-project
.reliant
workflows
code-review.yaml
security-audit.yaml
release-prep.yaml
src
Naming convention: Use lowercase with hyphens (for example, code-review.yaml). The filename becomes the workflow identifier. Discovery: When you start Reliant, it scans for .yaml files in .reliant/workflows/. Changes require restarting Reliant or reloading workflows. Commit your .reliant/workflows/ directory to version control to share workflows with your team.

Anatomy of a Workflow

A workflow file has five key sections. Here’s the minimal structure:
The following sections explain each part.

Metadata

The top of your workflow file contains identification metadata: Use status: draft while developing—draft workflows don’t appear in the workflow picker but can still be tested directly.

Inputs

Inputs define what parameters your workflow accepts. Every input needs either a default value or required: true:
Common input types: string, number, integer, boolean, enum, model, tools, preset. For complete input type documentation, see the Types Reference.

Entry Point

The entry field specifies which node starts execution:
For parallel starts, use an array:

Nodes

Nodes are the execution units. Each node has an id and a type that determines what it does: Workflow nodes run a child workflow, either by reference or inline:
Action nodes execute built-in activities:
Run nodes execute shell commands:
Loop nodes repeat a sub-workflow while a condition is true:

Edges

Edges define how execution flows between nodes. They’re only required when you have multiple nodes or need conditional routing:
For conditional routing:

Building Your First Custom Workflow

This section walks through building a code review workflow that analyzes code and provides structured feedback.

Step 1: Create the File

Create .reliant/workflows/code-review.yaml:

Step 2: Define Inputs

Think about what the user should be able to configure:

Step 3: Add the Review Node

The simplest approach uses the built-in agent workflow with a custom system prompt:

Step 4: The Complete Workflow

Here’s the full workflow file:

Step 5: Test It

Run your workflow to test it. Start a new chat, click the workflow selector (defaults to “Agent”), and select your code-review workflow. Change status: draft to status: published once you’re satisfied with the behavior.

Adding Loops

Loops let a workflow repeat while a condition is true. This is essential for patterns like “keep trying while tests fail” or “iterate while the agent has tool calls.”

When to Use Loops

Use loops when you need:
  • Retry logic: Run tests, if they fail have the agent fix issues, repeat while tests fail
  • Agent cycles: Continue calling the LLM while it has tool calls to execute
  • Iterative refinement: Keep improving output while quality threshold is not met

Loop Configuration

Loops use do-while semantics: the sub-workflow runs at least once, then iter.iteration increments before the while condition is checked:
*One of inline or workflow is required. Skipping the loop: Use the condition field to conditionally skip the entire loop before it starts. This is evaluated once, before the first iteration:

Accessing Loop Context

Inside loops, you have access to the iter.* namespace: The outputs.* namespace in while conditions contains results from the current iteration. Example using iteration context:
For retry loops, use thread: mode: fork with memo: false to give each iteration a fresh start from the original request, then use conditional inject to provide targeted error feedback from the previous iteration. This avoids accumulating stale context from failed attempts while still providing the agent with the specific issues to address. Iteration counting: In the loop body, iter.iteration is 0-indexed (0, 1, 2…). In the while check, it reflects completed iterations (1 after first, 2 after second). Use iter.iteration < N to run exactly N iterations.

Loop Outputs

After a loop completes, you can access both user-defined outputs and system fields: User outputs are flattened to the top level of the node’s output namespace. For example, if your inline workflow declares outputs.exit_code, access it as nodes.fix_loop.exit_code. System fields use an underscore prefix (_) to distinguish them from user-defined outputs. Currently, loop nodes provide:
  • _iterations: The total number of loop iterations that ran
Warning: Output names starting with _ are reserved for system use. User-defined outputs in inline.outputs cannot start with an underscore. Note on iter.iteration vs _iterations: Inside the loop’s while condition, use iter.iteration (no underscore) to check the current iteration count. After the loop completes, use _iterations (with underscore) to access the final count from outside the loop.

Example: Fix While Tests Fail

Here’s a workflow that keeps trying to fix test failures. It uses fork with memo: false so each iteration starts fresh from the original request, with targeted error feedback injected only after failures:
Key points:
  • thread: mode: fork gives each iteration the original user request
  • memo: false ensures a fresh fork each time (no accumulated context from failed attempts)
  • inject.condition: "iter.iteration > 0" only adds error feedback after the first iteration fails
  • The agent sees: original request + targeted error feedback (not the full messy history)

Conditional Nodes

Sometimes you want to skip a node entirely based on workflow inputs or previous node outputs. The condition field on nodes lets you do this without cluttering your edges.

Basic Node Conditions

Add a condition field with a CEL expression. If it evaluates to false, the node is skipped:
When a node is skipped:
  • A “skipped” event is emitted (visible in UI)
  • Node outputs are set to { "skipped": true }
  • No messages are added to the thread
  • Downstream edges can still route based on the skipped output

Condition Context

Node conditions can access:

Conditional Nodes vs Conditional Edges

Use node conditions when:
  • You want to skip a node entirely based on inputs
  • The decision doesn’t depend on which path led here
  • You’re implementing feature flags or optional phases
Use edge conditions when:
  • You need to route to different nodes based on outputs
  • The same node might be reached via different paths
  • You’re implementing success/failure branching

Conditional Routing

Edges can include conditions to route execution based on node outputs. Conditional edges let workflows handle success and failure differently.

Basic Conditional Edges

Use CEL expressions in the condition field:
Cases are evaluated in order—the first matching condition wins. A case without a condition acts as a default fallback.

Available Context in Conditions

Edge conditions can access:

Example: Different Handling for Pass/Fail

Multi-Agent Workflows

Complex tasks often benefit from multiple agents with different roles. Reliant supports several multi-agent patterns.

Using Groups for Agent Configuration

Groups let you organize inputs for different agents in your workflow:
Access group inputs with the inputs.GroupName.field syntax:
Groups appear as expandable sections in the workflow configuration UI, making it easy for users to customize each agent’s behavior.

Thread Modes for Coordination

Thread configuration controls how agents share context:

Message Injection

Use thread.inject to add context when an agent starts:
In loops, inject frequency depends on memo: with memo: false (default), a fresh thread is created each iteration and inject is added every time. With memo: true, the thread is reused and inject is added on the first iteration only. See Thread Configuration for details.

Example: Two-Agent Review

Here’s a workflow where one agent reviews code and another validates the review:
For more multi-agent patterns including parallel execution, debate, and auditing, see Multi-Agent Patterns.

Using Presets in Nodes

When invoking sub-workflows, you can apply Presets to configure their inputs. This is cleaner than passing many individual args and lets you reuse configurations.

Basic Preset Usage

Use the presets field on workflow or loop nodes:
Preset params form the base layer; args are merged on top (args win conflicts).

Targeting Input Groups

If a sub-workflow has input groups, use a map to target specific groups:
The default key targets ungrouped inputs (or inputs matching the workflow’s tag).

When to Use Presets vs Args

Use presets when:
  • You want to apply a reusable configuration bundle
  • The sub-workflow has many inputs you don’t want to repeat
  • You want users to be able to swap configurations easily
Use args when:
  • You need dynamic values from CEL expressions
  • You’re overriding specific values from a preset
  • The value is workflow-specific and not reusable

Syntax Sugar

Reliant provides two syntactic sugar features that simplify common workflow patterns. Both compile to standard nodes and edges at parse time — they produce the exact same internal representation as writing the expanded form by hand.

sequence:

The sequence: field replaces the combination of entry:, nodes:, and sequential edges: for linear chains of nodes. Instead of manually wiring nodes together, list them in order and Reliant generates the entry point and edges automatically.
Rules:
  • sequence: cannot coexist with entry: — it automatically sets the first node as the entry point
  • sequence: can coexist with additional nodes: and edges: for mixed patterns where the main flow is linear but you need extra branches

type: parallel

A node with type: parallel and branches: desugars into multiple branch nodes, a join node, and the fan-out/fan-in edges between them. The parallel node’s id becomes the join node’s id, so downstream edges that reference it continue to work.
Rules:
  • The parallel node must have an id and branches (a list of node definitions)
  • All branches must complete before the join fires (condition: all)
  • Edges targeting the parallel node automatically fan out to all branches
  • The parallel node’s id is reused as the join node’s id — downstream edges work unchanged
Both sequence: and type: parallel are purely cosmetic — they produce the exact same proto representation as hand-written nodes, edges, and entry. You can freely mix sugar and explicit syntax in the same workflow.

Combining Sugar

You can use both sequence: and type: parallel together:
This creates a parallel fan-out for gather, waits for all branches, then proceeds to synthesize — all without writing a single edge.

Inline Message Saving

Often you want to save a node’s output as a message without adding a separate SaveMessage node. The save_message field on nodes does this automatically.

Thread Behavior

For workflow nodes with thread.mode: fork or thread.mode: new, the inline save_message saves to the parent workflow’s thread, not the forked child’s thread. This is because the save_message is declared on the node in the parent workflow, so it acts in the parent’s context. This makes it easy to capture summaries from forked workflows back into the orchestrating workflow’s thread:
The plan agent runs in a forked thread, but the summary is saved to the parent workflow’s thread—no separate SaveMessage node needed.

Basic Usage

The message is saved after the node completes, with access to output.* for the node’s outputs.

Conditional Saving

Use the condition field to save messages only in certain cases:

Available Fields

When to Use Inline vs Separate SaveMessage

Use inline save_message when:
  • The message content comes directly from the node’s output
  • You want cleaner, more compact workflow definitions
  • The save happens immediately after the node
Use a separate SaveMessage action when:
  • You need to combine outputs from multiple nodes
  • The message logic is complex
  • You want the save as an explicit node in the flow

Common Pitfalls and Caveats

Validation Errors

Reliant validates workflows on load and reports structural problems before your workflow ever runs. If your workflow has validation errors, it won’t appear in the workflow picker. For a full list of validation checks and common errors, see Static Validation.

Common Mistakes

Forgetting thread configuration: If agents don’t seem to see each other’s work, check that you’re using thread: inherit (not new). Wrong CEL syntax: Template expressions use {{}} for interpolation. Edge conditions are bare CEL without the braces.
Missing outputs in loops: The while condition uses outputs.*. Make sure your loop’s inline workflow defines the outputs you’re checking. Node reference timing: You can only reference a node’s outputs after that node has completed. Edge conditions can only use nodes that are upstream from the current node. These are the most common issues when building workflows. Understanding them will save you debugging time.

Edge Routing: First Match Wins

When an edge has multiple cases, only the first matching case executes:
Cases are evaluated in order. If you need parallel execution, create multiple edges:

Loop While is Do-While

Loops execute at least once, then check the condition:
Inside the loop: iter.iteration is 0-indexed (0, 1, 2, …) After each iteration: Counter increments before the while check So while: iter.iteration < 3 runs iterations 0, 1, 2, then checks 3 < 3 which is false.

CEL vs Interpolation Syntax

Pure CEL (no {{}}):
  • condition fields
  • while fields
Interpolation ({{}} required):
  • Almost everything else
  • String fields
  • Even non-string fields that reference dynamic values

Null Checks Before Access

Always check for null or existence before accessing potentially missing fields:

Thread Memo in Loops

By default, mode: new or mode: fork creates a fresh thread each iteration:
Set memo: true to reuse the same thread across iterations:

Parallel Agents Cannot Share Threads

Never create parallel agents that write to the same thread:

Skipped Node Outputs

When a node is skipped (via condition: false), its outputs are { "skipped": true }. You cannot access regular outputs from skipped nodes:

Response Tools Require ExecuteTools

When using response_tool, you must execute the tool call to get the structured data:

Next Steps

Now that you understand workflow fundamentals: Start simple—a single-node workflow with a custom system prompt—and add complexity as needed. The best workflows solve real problems you encounter repeatedly.