Workflow expressions
Read run data, pass it between steps, and decide when steps run.
Expressions let a workflow use data from its run. You write them in a few places:
| Where | What it holds | Field |
|---|---|---|
| A step's condition | A condition. The step runs only when it's true | run_if |
| The loop's stop condition | A condition, checked after the last step of each pass | loop.until |
| A step's input | Values to hand to the agent | input |
| A parallel step's items | A list, one part per item | split.items |
| The workflow's output | The value the run returns | output |
Conditions are full expressions. Values are data in which a reference is replaced with what it points to.
Read run data with references
Every reference starts with $:
| Reference | What it reads |
|---|---|
$input | The input the run started with |
$steps.<key>.output | The output of the step with that key |
$steps.<key>.status | completed, failed, or skipped. null if the step hasn't run yet |
$steps.<key>.error | The error of a step that failed and was set to continue |
$previous.output, $previous.status, $previous.error | The step that came just before this one in the run |
$loop.iteration | Which pass through the loop this is, starting at 1 |
A step's key is the short name you give it, such as draft or review. Keys start with a lowercase letter and use lowercase letters, numbers, and underscores.
The step before. $previous is the step that came just before in the run. A skipped step still counts: its status is skipped and its output is null. On the first step of a later pass through the loop, $previous is the last step of the pass before. On the very first step of a run, everything in $previous is null.
Reach inside a value
Add a path to read part of a value:
- a dot and a name for a field:
$input.customer.email - a number in brackets for a list item, counting from 0:
$input.items[0] - a negative number to count from the end:
$input.items[-1] - a quoted name for fields with spaces or dashes:
$input["first-name"]
Paths can go as deep as you need: $steps.research.output.companies[0].name.
Missing values are null
If a path leads nowhere, the reference reads as null. That happens when a field doesn't exist, a list is shorter than the index, or the step hasn't run. It isn't an error. You decide what null should mean with a condition, as described below.
Pass values between steps
In a step's input, a parallel step's items, or the workflow's output, any text that starts with $ is a reference. Everything else is passed through as written.
{
"brief": "$input.brief",
"feedback": "$steps.review.output.feedback",
"sources": ["$steps.search.output.top_result", "internal wiki"],
"tone": "formal"
}The agent receives brief and feedback from the run, and sources with the first entry filled in. tone arrives exactly as written.
A value holds one reference, and the whole text must be that reference. To pass text that starts with a dollar sign, write two: "$$5 per seat" arrives as $5 per seat.
If you don't set a step's input, the first step receives the run's input. Later steps receive the run's input and the output of the step before.
Instructions are plain text and never contain references. Data reaches an agent only through its input, so one agent's output can't rewrite the next agent's instructions.
Write conditions
A condition compares values and combines the results:
$steps.review.output.approved == true
$steps.draft.output.score >= 0.8 and $loop.iteration < 3
not ($input.priority == "low" or $input.dry_run)Values you can write
- Numbers:
3,-1,0.75 - Text, in single or double quotes:
'EU'or"EU". Use\",\',\\,\n, and\tfor quotes, backslashes, new lines, and tabs. true,false, andnull, in lowercase
Operators
| Operator | Meaning |
|---|---|
== and != | Equal and not equal. Works for any values, including null |
<, >, <=, >= | Order. Needs two numbers or two pieces of text |
and, or, not | Combine conditions |
( ) | Group parts of a condition |
not applies first, then and, then or. Use parentheses when a condition mixes and and or, so it reads the way you mean it.
Write each comparison on its own and join them with and. 1 < $input.count < 5 isn't accepted; write $input.count > 1 and $input.count < 5.
Text is ordered character by character, so capital letters come before lowercase ones.
Equality is exact
Values are equal only when they have the same type and value:
1 == 1.0is true.true == 1is false."1" == 1is false.- Lists and objects are equal when everything inside them is equal.
True and false values
A condition is true or false. When you use a value on its own, such as $input.dry_run, these count as false: false, null, 0, empty text, an empty list, and an empty object. Everything else counts as true.
Functions
| Function | Result |
|---|---|
len(value) | The length of text, a list, or an object. len(null) is 0 |
any(list) | True if any item counts as true. False for null |
all(list) | True if every item counts as true, and true for an empty list. False for null |
value.contains(x) | For text: whether x appears in it. For a list: whether x is an item. For an object: whether x is one of its field names |
value.startswith(x) and value.endswith(x) | Whether text begins or ends with x |
Calling contains, startswith, or endswith on null gives false. A missing list is never treated as a list whose items all pass.
When an expression can't work
When you save. The workflow isn't saved if an expression is malformed or points somewhere it can't. Each problem is shown on the step it belongs to, including:
- a typo or a missing quote, with the character where reading stopped;
TrueorANDwritten in capitals;- a reference to a step key that doesn't exist;
- a reference to a step that runs later, unless both steps are inside the loop (see below).
While the run is going. Some problems only show up with real data. For example, $steps.score.output.value > 0.8 can't be answered if that step produced no value, because null has no order. When that happens the step fails with the error workflow.expression_error, naming the expression. It is never quietly treated as false.
To allow for a missing value, check it first. The rest of an and is skipped once its first part is false, so this never fails:
$steps.score.output.value != null and $steps.score.output.value > 0.8Loops and references
Inside the loop, a step can read a step that comes after it. On the first pass that value is null. On later passes it's the result from the pass before. That's how a draft can respond to the review of the previous draft.
A step can read its own earlier result the same way, but only inside the loop.
Outside the loop, a step can only read steps that come before it.
Parallel step output
A parallel step returns every part, both as a list and by part name:
{
"parts": [
{
"key": "acme",
"label": "Acme",
"status": "completed",
"output": { "summary": "..." },
"error": null
}
],
"by_key": {
"acme": { "summary": "..." }
}
}Read a single part with $steps.research.output.by_key.acme, or pass all of them to a combining step with $steps.research.output.parts.
Examples
Skip a step when there is nothing to do. Give the summary step this condition:
len($steps.search.output.results) > 0Revise until the review approves. Loop from draft to review with this stop condition:
$steps.review.output.approved == trueGive draft this input:
{ "brief": "$input.brief", "feedback": "$steps.review.output.feedback" }On the first pass feedback is null. After that it holds the latest review.
Research a list of companies at once. Set a parallel step's items to $input.companies. Then give a combining step this input:
{ "findings": "$steps.research.output.parts" }Handle a failed step. Set the lookup step to continue when it fails. Then give a follow-up step this condition:
$steps.lookup.status == "failed"and this input:
{ "reason": "$steps.lookup.error.message" }Stop a loop early. End after three passes, or as soon as the score is high enough:
$loop.iteration >= 3 or ($steps.review.output.score != null and $steps.review.output.score >= 0.9)Limits
- An expression can be up to 2,000 characters.
- Parentheses,
not, and function calls can nest up to 32 levels deep.