Guest Machines

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:

WhereWhat it holdsField
A step's conditionA condition. The step runs only when it's truerun_if
The loop's stop conditionA condition, checked after the last step of each passloop.until
A step's inputValues to hand to the agentinput
A parallel step's itemsA list, one part per itemsplit.items
The workflow's outputThe value the run returnsoutput

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 $:

ReferenceWhat it reads
$inputThe input the run started with
$steps.<key>.outputThe output of the step with that key
$steps.<key>.statuscompleted, failed, or skipped. null if the step hasn't run yet
$steps.<key>.errorThe error of a step that failed and was set to continue
$previous.output, $previous.status, $previous.errorThe step that came just before this one in the run
$loop.iterationWhich 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 \t for quotes, backslashes, new lines, and tabs.
  • true, false, and null, in lowercase

Operators

OperatorMeaning
== and !=Equal and not equal. Works for any values, including null
<, >, <=, >=Order. Needs two numbers or two pieces of text
and, or, notCombine 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.0 is true.
  • true == 1 is false.
  • "1" == 1 is 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

FunctionResult
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;
  • True or AND written 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.8

Loops 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) > 0

Revise until the review approves. Loop from draft to review with this stop condition:

$steps.review.output.approved == true

Give 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.

On this page