Skip to main content
This page documents ABL’s expression language: operators, variable paths, {{}} template-string interpolation, the 43 built-in functions, and type coercion rules. Expressions are used in conditions (WHEN, CHECK, constraint rules), value assignments (SET), template interpolation ({{}}), and function calls. For rich content output formats (voice, cards, carousels, interactive actions, templates), see Rich content.

Expression syntax

Comparison operators

Logical operators

Write logical operators in uppercase (AND, OR, NOT). ! is also accepted as a prefix negation.
contains, matches, startsWith, and endsWith are infix operators (a contains b), not callable functions — a condition that calls one as a bare function (CONTAINS(ani_national, "1234567890")) parses but has no matching overload, so it can never evaluate true; the compiler warns CEL_CONDITION_UNRESOLVED_CALL. Use the infix form, or the abl.* CEL method form (x.contains("y")).

Unary operators

Operator precedence

  1. Parentheses ()
  2. Unary operators (NOT, IS SET, IS NOT SET)
  3. Comparison operators (==, !=, >, <, >=, <=, contains, matches)
  4. AND
  5. OR
Use parentheses to override default precedence:

Quoting a condition

A WHEN value — in HANDOFF, ON_RETURN WHEN, ESCALATE triggers, COMPLETE, and hook WHEN/RESPOND branches — is unwrapped from its quotes and evaluated as a condition whenever it contains a condition operator: ==, !=, <, >, &&, ||, !, IS [NOT] SET, contains, matches, startsWith, endsWith, in, or a function call. So WHEN: "dispute == true" and WHEN: dispute == true are the same condition. A quoted value with none of these — a natural-language phrase such as WHEN: "task done" — stays a quoted literal string, which most deterministic evaluators treat as always-truthy.
Write a genuinely deterministic condition either unquoted or quoted-with-an-operator; reserve a bare quoted phrase for constructs that intentionally accept free text (a prompt-only/semantic condition). A deterministic routing or escalation entry’s when must be an operator expression regardless of quoting — see Deterministic-only routing.

Variable paths and dot notation

Reference variables using dot notation to access nested values.

Path resolution rules

  • The evaluator looks up the full path in the session context.
  • The Platform resolves each segment left to right: user.address.city resolves user, then address on the result, then city.
  • If any segment resolves to null or undefined, the entire path resolves to undefined.
  • Array access uses bracket notation: items[0], items[2].name.

Template strings

Template strings use {{}} syntax for variable interpolation within RESPOND, summary, and other string properties — including inside a TEMPLATES: block body:

Conditional sections

Templates support conditional rendering with {{#if}}...{{/if}}:

Function calls in templates

You can call built-in functions within template strings:

Built-in function reference

ABL provides 36 built-in functions organized into seven categories (plus the CEL-only content-analysis helpers described below). All functions are called with FUNCTION_NAME(arg1, arg2, ...) syntax. Function names are uppercase.

Math functions

Math functions coerce string arguments to numbers automatically: ADD("2", "3") returns 5.

String functions

The string functions gracefully handle null and undefined and return an empty string: UPPER(null) returns "". To prevent memory issues, the limit on string output is 100,000 characters.

Identity-matching functions

These compare what a caller actually said against a stored value — they are deterministic similarity scoring, not a semantic/model judgment, built for voice identity verification (see Write authority for a READONLY field that stores the result). kind is optional — omitted, it defaults to text (unchanged behavior, no warning) — and otherwise one of: Platform-owned match bands (authors never pick a threshold): A -1 score (nothing to judge) always reports "missing".
The guiding principle: values that sound the same cannot be told apart by a caller (speech-to-text just picks a spelling), so they score as a "match" — only values that sound genuinely different can fail. For example email compares the domain by sound but matches its ending exactly (kore.ai is not kore.com); person_name matches common respellings (Jon/John, Steven/Stephen) and scores a near-same name (Ahmed/Ahmad) as "retry", never a straight mismatch, while a genuinely different name (Mark/Mary) is a "mismatch"; address reads house/unit letters and matches street words that sound alike. The matcher reads the value itself, not the sentence around it — filler words (“no”, “sorry”, “I mean”) are not stripped, so an answer that contains them alongside the correct value still fails to match as written.
said must be the extracted field (for example supplied_email), not the raw caller utterance. An extracted field is written by the model and may already hold the stored value (for example, after a lookup tool returned the profile) — a match on the field alone does not prove the caller actually said it. A rule that grants access on a match should also confirm the caller’s own words contained the value.
The function MATCHES(said, stored[, kind]) and the infix operator a MATCHES "pattern" are unrelated — do not mix up their second argument. Writing MATCHES(supplied, "^gold$") (the function form with a regex-shaped literal) is almost certainly meant to be the infix form supplied MATCHES "^gold$", and the compiler warns MATCHES_FUNCTION_REGEX_LITERAL. Conversely, writing tier MATCHES "gold" (the infix form with a plain literal) is true whenever "gold" appears anywhere in tier as a substring — not a fuzzy spoken-answer comparison — and the compiler warns MATCHES_INFIX_LITERAL_AMBIGUOUS since this is easy to mean the other way.

Formatting functions

The mask patterns are explained below. The default mask character is *. Pass a third argument to use a different character: MASK(ssn, "last4", "x"). The date format uses the following notations: Example: FORMAT_DATE("2024-03-15T10:30:00Z", "YYYY-MM-DD HH:mm") returns "2024-03-15 10:30". FORMAT_DATE formats in UTC by default — a date-time string without an explicit offset is interpreted as UTC, so the output is deterministic regardless of where the runtime executes. Pass an optional IANA timezone name as the third argument to format in that zone instead:
An unrecognized timezone falls back to UTC.

Type checking and coercion functions

Array functions

Object functions

OBJECT_KEYS and OBJECT_VALUES return an empty array for non-object inputs (null, arrays, strings). OBJECT_MERGE accepts any number of arguments and skips non-object values.

Utility functions

COALESCE considers 0 and false as valid (non-null) values: COALESCE(0, "fallback") returns 0. UNIQUE_ID generates a random alphanumeric string suitable for reference numbers. It is not cryptographically secure — do not use it for tokens or secrets.

Content-analysis functions (CEL guardrails)

CEL-based guardrail checks have access to an additional abl.* namespace of content-analysis helpers:

Pattern limits

abl.matches_pattern and abl.not_matches_pattern run on the RE2 engine.
  • Each pattern can be at most 500 characters and must use RE2 syntax. RE2 has no backreferences or lookarounds.
  • A pattern outside these limits never matches. abl.not_matches_pattern is then true for every input. Validation warns with CEL_MATCH_PATTERN_REJECTED.
  • To use a longer pattern, split it into several calls joined with ||, or with && for abl.not_matches_pattern.
  • The checked text is scanned up to 100,000 characters. Text past that length is not scanned.
In CEL expressions the core functions above are also available under the abl. namespace with lowercase names (for example, abl.upper(x)), and CEL-native operators (&&, ||, !, %, in, ternary ? :) and macros (size(), has()) can be used.
abl.pii_equal(left, right) compares two values without ever exposing the underlying plaintext to the expression — both left and right must be live PII-vault tokens (the opaque form a PII-classified value takes once captured, for example, via a sensitive: true GATHER field), and the comparison happens inside the vault itself. Values are canonicalized before comparing (email lowercased/trimmed, phone/SSN/credit-card digits-only, etc.), so abl.pii_equal(session.data.values.email, caller_provided_email) matches Member@Example.com against member@example.com.
If either operand isn’t a live vault token for the same PII type — missing, expired, or a plain string — abl.pii_equal throws rather than returning false. This is deliberate: silently returning false on a fault would make !abl.pii_equal(x, y) evaluate true on a comparison that couldn’t actually be performed. Guard against this with IS SET checks on both operands before comparing, or route the resulting evaluation error through your usual error handling.

Nested function calls

Functions can be nested as arguments to other functions:
Nesting is limited to a maximum depth of 32 to prevent stack overflow.

Using expressions in ABL

In conditions (WHEN, CHECK, constraint rules)

Long boolean conditions

Ordinary boolean conditions can combine AND, OR, and NOT beyond the original 4,096-unit CEL limit. The extended path accepts up to 65,536 prepared UTF-16 source units, 4,096 syntax nodes, and 64 nesting levels. Each indivisible comparison, call, or ternary stays limited to 4,096 units. CEL evaluates the complete parsed expression and keeps precedence and short-circuit order. Short conditions, strict checkpoint conditions, PII comparisons, and scalar SET evaluation keep their existing protections. A supported long rule can now match where the old fallback disagreed. No migration is required. An unsupported expression produces the non-blocking warning CEL_EXPRESSION_LIMIT, with its agent, path, and prepared length.

Size limits for conditions and regex patterns

The same limits apply to every condition: step IF / WHEN branches, HANDOFF and DELEGATE WHEN, COMPLETE, constraints, hooks, and guardrail checks. Lengths are counted in characters after ABL syntax is converted to CEL, so they can differ slightly from what you typed. HANDOFF and DELEGATE WHEN evaluate each top-level OR / AND alternative on its own, so the limits apply to each alternative. A condition or pattern over a limit does not stop the agent and is not an error:
  • At publish, the compiler warns: CEL_EXPRESSION_LIMIT for a condition and CEL_MATCH_PATTERN_REJECTED for a pattern written as text. Studio shows up to three of these warnings as toasts after you publish an agent, with a “+N more” note. A pattern held in a variable can only be checked at run time.
  • At run time, a pattern over the limit matches nothing: abl.matches_pattern is false and abl.not_matches_pattern is true for every input. A condition over the limit is not evaluated as written. Most conditions fall back to a simpler evaluator that can return a different result. Hook CALL / RESPOND guards, checkpoint conditions, and PII comparisons fail closed. In step branches and in HANDOFF / DELEGATE WHEN, the session trace records a warning event with code condition_limit_reached, the reason, and the length.
To stay within the limits, split long rules into shorter parts joined with OR / AND (|| / &&):

Comments in conditions

On condition surfaces that accept line comments, // outside a complete string starts a comment through the end of the line. Comment text does not add variables, operators, or WHEN, BEFORE, or IMPLIES clauses. A quoted URL, or // inside an escaped, raw, bytes, or triple-quoted string, stays literal. YAML enabled_when keeps its separate single-line scalar restriction. A condition that contains only comments is invalid for these constructs:
  • A constraint requirement or applicability gate.
  • A HANDOFF or DELEGATE WHEN or ENABLED_WHEN.
  • A conditional GATHER activation.
An omitted condition and an ordinary blank value keep the policy of the construct that owns them. Malformed expressions receive compiler diagnostics.

In SET assignments

Missing paths in value expressions

A value expression computes a value for a CALL argument (WITH or a named inline argument), a tool PROCESS mapping, a TRANSFORM MAP field, or a SET. Inside one, a key that is missing from a record reads as null wherever it appears, including inside COALESCE, a ternary, an object or list literal, and arithmetic. So COALESCE(order.list.items, fallback) falls through to fallback when order has no list. Only the branch a ternary takes is evaluated. x IS SET over a missing nested path is false. An expression has no value in two cases:
  • A single path that is not set, such as empty.list.kind on its own. A CALL argument written this way is left out of the tool input, and a MAP field has no value. Use COALESCE(path, default) when the tool needs a value.
  • An expression that cannot be evaluated, such as text plus a number, a ternary whose condition is not true or false, or bracket access to a missing key (order["list"].kind). The runtime never sends a partial reading of it. The trace records a warning event that names the argument or field.
Behavior change. An expression that cannot be evaluated now sends no value instead of a partial one. Check agents that pass computed arguments to tools.

Types in conditions and computed values

  • A conditional needs a true/false condition. In flag ? "a" : "b", flag must hold true or false. Text, a number, or a missing value is an evaluation error. Write flag == true ? "a" : "b" when flag may be missing.
  • !x needs a true/false x. Write flag != true when flag may be missing.
  • contains needs text or a list on the left. On text it is a substring test. On a list, an element must equal the right side. A number or an object on the left is an evaluation error.
  • Arithmetic needs numbers. + also joins text to text and a list to a list.

When a SET value cannot be evaluated

A computed value is a function call, field read, index, conditional, comparison, logical operator, or arithmetic with an operand that is quoted text or a variable the session holds or declares. When one cannot be evaluated:
  • A step SET clears the variable. A HANDOFF CONTEXT SET leaves it unchanged.
  • The trace gets a warning event with code set_expression_not_evaluated. It carries field, source, outcome (no_value, or stored_expression_text under keep_text), reason (ternary_condition_not_bool, no_matching_overload, or evaluation_failed), and a hint. It never carries values or the expression text.
recent_input is a built-in available in SET contexts: the caller’s last 3 utterances, joined. input_transcript is a built-in, read-only SET value on realtime voice channels — the provider’s transcript of the current caller turn (empty when there is none). An authored session variable with either name takes precedence over the built-in.

In template interpolation

In TRANSFORM pipelines

Type coercion rules

The evaluator applies these coercion rules during expression evaluation:
Related articles: