# Condition expressions

A `condition` node evaluates a small expression against the current
`variables` map and routes to `'true'` or `'false'` based on the result. Use
it to branch on data already in the run, such as an upstream output, an
initial context value, or a derived value, without paying for an AI call.
This page covers the expression language, what is in scope, and the
pitfalls authors hit most often.

## The shape of a condition node

```yaml
check-env:
  type: condition
  label: Production?
  expression: env == 'production'
  on:
    'true': deploy
    'false': stage
```

Every condition node has:

- `expression` — the expression to evaluate.
- `on` — must declare exactly `'true'` and `'false'` keys.

The result of the expression is coerced to boolean: any truthy value routes
to `'true'`, any falsy value (`false`, `0`, `null`, `undefined`, empty
string) routes to `'false'`.

## What is in scope

Expressions are evaluated against the workflow's `variables` map, with each
top-level key in scope as a bare identifier. So if the run has
`variables.env === 'production'` and `variables.score === 92`, you can write:

```yaml
expression: env == 'production' and score >= 80
```

You **cannot** access `process`, `require`, or any Node.js global.
Expressions run in a sandboxed evaluator, not in `vm.runInNewContext`. This
is intentional: condition expressions are part of the workflow file, which
may be authored or edited by less-trusted humans or models.

### Operators and syntax

The evaluator (built on `expr-eval`) supports a useful subset of JavaScript-
like syntax, but not the full language:

| Category         | Supported syntax                                                                             |
| ---------------- | -------------------------------------------------------------------------------------------- |
| Arithmetic       | `+`, `-`, `*`, `/`, `%`, `^` (power)                                                         |
| Comparison       | `==`, `!=`, `<`, `<=`, `>`, `>=` (not `===` / `!==`)                                         |
| Logic            | `and` / `or` / `not`, or equivalently `&&` / `\|\|` / `!`                                    |
| Member access    | `foo.bar` and `foo[0]` for object/array fields                                               |
| String literals  | Single or double quoted                                                                      |
| Numeric literals | Integers and floats                                                                          |
| Function calls   | A small built-in set (`length`, `min`, `max`, `abs`, etc.); custom functions are not exposed |

What you cannot do:

- Define variables (`let`, `const`, `var`).
- Call methods on objects (`foo.toUpperCase()` does **not** work).
- Use the spread operator, destructuring, or templates.
- Access globals like `Math`, `JSON`, `Date`.

If you need something more expressive, run a `script` node and branch
on the value it returns, or call an `ai_judge`.

## Worked examples

### Branch on an initial context variable

```yaml
name: env-branch
version: 1
initial: pick
states:
  pick:
    type: condition
    label: Is this production?
    expression: env == 'production'
    on:
      'true': prod
      'false': stage
  prod:
    type: end
    label: Production path
    outcome: success
  stage:
    type: end
    label: Staging path
    outcome: success
```

Run with `roscoe run env-branch --var env=production`.

### Branch on an upstream score

```yaml
score-gate:
  type: condition
  label: Is the score high enough?
  expression: judge.score >= 80
  on:
    'true': publish
    'false': revise
```

This assumes an upstream `ai_judge` node with id `judge` ran first using a
`confidence` validator (which writes `judge.score`). See
[Output chaining](/docs/authoring/output-chaining) for the field reference.

### Compound conditions

```yaml
gate:
  type: condition
  label: Production AND high confidence?
  expression: env == 'production' and judge.score >= 90
  on:
    'true': deploy
    'false': hold
```

### Empty-list check

```yaml
any-failures:
  type: condition
  label: Any failures?
  expression: length(failures) == 0
  on:
    'true': pass
    'false': fail
```

`length(...)` is one of the built-in functions and works on strings and
arrays.

## Pitfalls

### Undefined variables

If you reference a variable that has not been set yet, typically because
the upstream node has not run or did not produce that field, the identifier
evaluates to `undefined`. Most comparisons against `undefined` produce
`false`, which silently routes to `'false'`. This can make a bug look like
an intentional path.

**Fix:** make sure the condition node runs _after_ the producer. Look at
the run trace in the web UI to confirm the field is in `variables` at the
moment the condition fires.

### String vs number comparisons

Variables seeded from the CLI (`--var key=value`) are always strings.

```bash
roscoe run my-flow --var threshold=80
```

```yaml
# This compares the string "80" to the number 80 — false!
expression: threshold == 80
```

**Fix:** quote the literal, or pass the variable through a producing node
that emits a number.

```yaml
expression: threshold == '80'
```

For numeric comparisons, prefer pulling the value from a node that produces
typed output (e.g. `ai_judge` with `confidence` validator → `judge.score`
is a real number).

### Boolean variables

Same caveat: `--var debug=true` gives you the string `"true"`, not the
boolean. Use `expression: debug == 'true'`.

### Accidental assignment

A single `=` parses as `expr-eval`'s assignment operator, not equality:
`x = 5` sets `x` to `5` and returns `5` (truthy), so the condition silently
routes to `'true'` instead of failing to parse. The assignment itself
doesn't stick around: Roscoe rebuilds `variables` from node outputs after
every step, so nothing downstream sees the change. The routing bug is real
though, and easy to miss because the expression evaluates without an
error. Always use `==` for equality.

### Quoting in YAML

YAML interprets some unquoted strings specially. If your expression starts
with `{` or contains `:`, wrap it in single quotes or use a block scalar:

```yaml
expression: 'env == "production"'
# or
expression: |
  env == "production"
```

## What the node writes back

A condition node writes `<id>.result` to `variables`: the boolean it
routed on. You can reference this downstream:

```yaml
log-decision:
  type: script
  label: Log decision
  script: |
    console.log(`Gate decided: ${process.env.ROSCOE_OUT_check_env_result}`);
    export default true;
  validator:
    kind: boolean
  on:
    'true': done
    'false': done
```

## Where to next

- [Condition node](/docs/nodes/condition) — the per-field reference.
- [Output chaining](/docs/authoring/output-chaining) — what fields each
  upstream node writes for you to reference here.
- [Validators](/docs/authoring/validators) — when an AI judge is a better
  fit than a condition.
