
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.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
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.
Anatomy of a Workflow
A workflow file has five key sections. Here’s the minimal structure:Metadata
The top of your workflow file contains identification metadata:| Field | Required | Description |
|---|---|---|
name | Yes | Unique identifier for the workflow |
version | No | Semantic version (for example, v0.0.1) |
description | No | Human-readable description shown in UI |
status | No | Visibility: draft, published, or internal |
tag | No | Category for preset matching (typically agent) |
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 orrequired: true:
string, number, integer, boolean, enum, model, tools, preset.
For complete input type documentation, see the Types Reference.
Entry Point
Theentry field specifies which node starts execution:
Nodes
Nodes are the execution units. Each node has anid and a type that determines what it does:
Workflow nodes run a child workflow, either by reference or inline:
Edges
Edges define how execution flows between nodes. They’re only required when you have multiple nodes or need 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. Changestatus: 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, theniter.iteration increments before the while condition is checked:
| Field | Required | Description |
|---|---|---|
while | Yes | CEL expression that continues the loop when true (uses outputs.*, iter.*, inputs.*) |
condition | No | CEL expression to skip the loop entirely if false (evaluated before the loop starts) |
inline | Yes* | Inline sub-workflow definition |
workflow | Yes* | External workflow reference (alternative to inline) |
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 theiter.* namespace:
| Variable | Description |
|---|---|
iter.iteration | Current iteration (0-indexed in loop body; increments before while check) |
outputs.* namespace in while conditions contains results from the current iteration.
Example using iteration context:
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:| Output | Description |
|---|---|
nodes.<loop_id>.<output> | User outputs from the final iteration (as declared in inline.outputs) |
nodes.<loop_id>._iterations | System field: total number of iterations completed |
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
_ 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 usesfork with memo: false so each iteration starts fresh from the original request, with targeted error feedback injected only after failures:
thread: mode: forkgives each iteration the original user requestmemo: falseensures 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. Thecondition field on nodes lets you do this without cluttering your edges.
Basic Node Conditions
Add acondition field with a CEL expression. If it evaluates to false, the 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:| Namespace | Description |
|---|---|
inputs.* | Workflow input values |
nodes.<id>.* | Outputs from previously completed nodes |
workflow.* | Workflow metadata |
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
- 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 thecondition field:
Available Context in Conditions
Edge conditions can access:| Namespace | Description |
|---|---|
inputs.* | Workflow input values |
nodes.<id>.* | Outputs from completed nodes |
workflow.* | Workflow metadata |
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:inputs.GroupName.field syntax:
Thread Modes for Coordination
Thread configuration controls how agents share context:| Mode | Description | Use Case |
|---|---|---|
inherit | Use parent’s thread | Agents that should see each other’s work |
new | Create isolated thread | Independent parallel agents |
fork | Copy parent thread at start | Agents that need initial context but work independently |
Message Injection
Usethread.inject to add context when an agent starts:
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: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 thepresets field on workflow or loop nodes:
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: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
- You need dynamic values from CEL expressions
- You’re overriding specific values from a preset
- The value is workflow-specific and not reusable
Inline Message Saving
Often you want to save a node’s output as a message without adding a separateSaveMessage node. The save_message field on nodes does this automatically.
Thread Behavior
For workflow nodes withthread.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:
SaveMessage node needed.
Basic Usage
output.* for the node’s outputs.
Conditional Saving
Use thecondition field to save messages only in certain cases:
Available Fields
| Field | Type | Description |
|---|---|---|
role | string | Message role: user, assistant, tool, system |
content | string | Message text (supports {{output.*}} expressions) |
condition | string | CEL expression; message only saved if true |
tool_calls | string | For assistant messages with tool calls |
tool_results | string | For tool result messages |
When to Use Inline vs Separate SaveMessage
Use inlinesave_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
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
Testing Workflows
Running Your Workflow
Test a workflow by selecting it in the Reliant UI or using the CLI:Validation Errors
Reliant validates workflows on load. Common errors: “input X has no default and is not required”: Every input must have eitherdefault or required: true.
“node X not found”: An edge references a non-existent node ID. Check spelling.
“entry node X not found”: The entry field references a node that doesn’t exist.
“loop must have either ‘workflow’ or ‘inline’”: Loop nodes need either an external workflow reference or an inline definition.
Common Mistakes
Forgetting thread configuration: If agents don’t seem to see each other’s work, check that you’re usingthread: inherit (not new).
Wrong CEL syntax: Template expressions use {{}} for interpolation. Edge conditions are bare CEL without the braces.
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.
Config-as-Code
Version Control
Workflow files are meant to be checked into git alongside your code:your-project
.reliant
workflows
code-review.yaml
deploy-check.yaml
security-audit.yaml
src
.gitignore
- History: Track who changed what and when
- Review: Workflow changes go through code review
- Consistency: Everyone on the team uses the same workflows
- Rollback: Easily revert problematic workflow changes
Team Sharing
When workflows are in your repository:- Team members get workflows automatically when they clone or pull
- Workflow changes can be reviewed alongside code changes
- Branch-specific workflows are possible (feature branches can experiment)
Workflow Versioning
Use theversion field to track workflow iterations:
- Patch (
v1.0.1): Bug fixes, prompt tweaks - Minor (
v1.1.0): New optional inputs, additional steps - Major (
v2.0.0): Breaking changes to inputs or behavior
Common Pitfalls and Caveats
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:Loop While is Do-While
Loops execute at least once, then check the condition: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{{}}):
conditionfieldswhilefields
{{}} 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:
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 (viacondition: false), its outputs are { "skipped": true }.
You cannot access regular outputs from skipped nodes:
Response Tools Require ExecuteTools
When usingresponse_tool, you must execute the tool call to get the structured data:
Next Steps
Now that you understand workflow fundamentals:- Multi-Agent Patterns: Learn advanced orchestration patterns like debate, parallel competition, and auditing
- Presets: Create reusable parameter bundles for your workflows
- Types Reference: Review workflow config types and output helper types
- CEL Expressions Reference: Master the expression language for dynamic values