Skip to main content
The FLOW: section adds structured execution steps to any agent. It defines a step-by-step execution graph where each step declares actions (collect information, call tools, respond, branch) and transitions to other steps. Agents operate in reasoning mode by default, where the LLM autonomously decides actions based on the goal. Adding a FLOW: section gives an agent a structured step graph, with each step declaring whether it uses LLM reasoning or deterministic execution via the REASONING: toggle.

Flow structure

Basic syntax

Entry point

The entry_point: property declares which step the flow begins at:
If omitted, execution starts at the first step listed in the steps array.

Step list

The steps: array declares the ordered list of step names. While steps can transition to any other step (not just the next in order), the list establishes the canonical ordering:

Step definitions

Each step is defined as a named block under FLOW: with its properties indented:

Per-step REASONING toggle

Every step in a FLOW: section must declare REASONING: true or REASONING: false. This controls whether the step uses LLM reasoning or deterministic execution.

Reasoning step properties

When REASONING: true, the following additional properties are available: Validation rules:
  • A step with REASONING: true must have either a step-level GOAL or an agent-level GOAL: defined.
  • A step with REASONING: false should not have a GOAL (it has no effect on deterministic steps).
  • A step with REASONING: false should not have AVAILABLE_TOOLS (use CALL to invoke tools deterministically).

Entry guards

The WHEN: property on a step defines a condition that must be true for the step to execute. If the condition is false, the step is skipped:

Attempt limiting

Steps can limit the number of times they execute (useful for retry loops):

Step actions

SAY / RESPOND

The RESPOND: action sends a message to the user. It supports template interpolation with {{variable}} syntax:
Multi-line responses use pipe block syntax:

MODEL_CONTEXT on a response

An optional MODEL_CONTEXT sits beside a FLOW step’s RESPOND (or a COMPLETE condition’s RESPOND) and replaces that response only in future model context — it never changes what’s actually sent to the user, the stored transcript, or session values:
This is useful when the delivered response carries a large presentation payload (a widget, a card, a big table) that would otherwise bloat every later model call for no benefit — MODEL_CONTEXT lets the model keep a short textual summary of what the customer saw instead. MODEL_CONTEXT is a text scalar; a non-text value fails validation (MODEL_CONTEXT_INVALID). An explicitly empty value omits the response from later model requests entirely. Omit the property to keep the historical full-response behavior. See also MODEL_CONTEXT on a tool result for the equivalent policy on a tool’s own result.

PRESENT

The PRESENT: action displays a formatted presentation before collection. It is used alongside GATHER: to show the user what has been collected so far:

GATHER in flow steps

Within a flow step, GATHER: uses a list syntax different from the top-level GATHER: section:

Multi-field collection

Collect multiple fields in a single step using the FIELDS: sub-block:

Flow gather field properties

Each field in a flow GATHER: supports these properties:
Flow gather fields also accept the same additional properties as top-level GATHER fields: entity_ref, options, message_key, satisfied_by, validation_process, retry_prompt, max_retries, preferences, sensitive, sensitive_display, mask_config, pii_type, transient, extraction_pattern, extraction_group, and semantics. In addition, a flow gather field may carry rich_content format variants for its prompt. Note that flow gather uses validation: where the top-level section uses validate:.

GATHER block properties

Alongside FIELDS:, a flow GATHER: block accepts these block-level properties: The STRATEGY: value controls the collection approach:

CORRECTIONS

When CORRECTIONS: true, the user can naturally correct previously collected values without restarting the step:

COMPLETE_WHEN

The COMPLETE_WHEN: condition specifies when the gather step is considered complete:

CALL…WITH…AS

The CALL: action invokes a tool. Use WITH: to pass parameters and AS: to bind the result to a variable:

WITH parameters

The WITH: block maps tool parameter names to values or variable references:
Values can be:
  • Variable references: amount (resolves the session variable amount)
  • Literal strings: "domestic"
  • Expressions: COALESCE(reference, "")
  • Nested objects — a key with no inline value followed by an indented sub-block becomes a nested object argument, for tools whose schema requires object parameters:
Nesting is preserved by indentation. Scalar leaves keep their raw form and are coerced at runtime ("30" → 30, "[15, 16]" → [15, 16]).

Localized WITH arguments

A WITH value can take its text from the project’s locale files (locales/<locale>/<agent>.json or locales/<locale>/_shared.json) with "{{locale.KEY}}", or a bare locale.KEY. The text is chosen for the caller’s current interaction locale (with the same fallback chain as MESSAGE_KEY — for example hi-IN, then hi), and {{…}} placeholders inside the locale text are rendered too:
If a referenced key isn’t defined for the caller’s locale (and no less-specific locale in the chain provides it), the tool is not called — the CALL result is success: false with error.code: "LOCALE_KEY_MISSING", so the step’s failure branch runs and raw {{locale.KEY}} text never reaches the tool or the caller. Only authored text is treated as a locale reference; a session value that happens to contain {{locale.…}} passes through as plain data. Supported forms are "{{locale.KEY}}" (optionally with filters, like "{{locale.KEY | upper}}", and mixed with other text) and a bare locale.KEY; an expression such as locale["KEY"] or locale.KEY + "!" is not supported and the CALL is not run.

SET

The SET: action assigns values to session variables:
Each assignment uses variable = expression syntax. The expression is resolved at execution time and can reference:
  • Literal values: "pending", 0, true, false
  • Variable references: acctResult.balance
  • Function calls: FORMAT_CURRENCY(amount, "USD"), COALESCE(value, "default"), ADD(a, b), SUB(a, b), ROUND(n, decimals)
  • Unique ID generation: UNIQUE_ID(12)
  • Current timestamp: NOW()
An unquoted JSON array or object literal is stored as a structured value. A JSON string’s string-typed leaves may contain {{variable}} templates — the collection is parsed first, then its string values are rendered, so substituted data is never reinterpreted as JSON:
This stores an array of objects correctly even when employer_name contains quotes or backslashes. Prefer a direct variable reference when constructing a computed collection instead of interpolating one into JSON-shaped text:
Surround the whole value with quotes when JSON-shaped text is intentional — SET: payload_text = '[{"customer":"{{employer_name}}"}]' stores a string, not a structured value. A session TYPE declaration alone doesn’t validate every SET write; validate structured arguments at the tool boundary (pass the variable to a typed parameter, such as filters: object[], through WITH).

CHECK

The CHECK: action evaluates a condition. If the condition is false, execution transitions to the ON_FAIL: step:

CLEAR

The CLEAR: action removes variables from the session context:

TRANSFORM

The TRANSFORM: action filters, maps, sorts, and limits an array from the session context:

LOG

The LOG: action emits a trace-only diagnostic (not shown to the user). Use the inline or block form:

AWAIT_ATTACHMENT

The AWAIT_ATTACHMENT: action pauses the step until the user uploads a file.

SECURE_INPUT

The SECURE_INPUT: action pauses the step to collect one or more sensitive values — a card number, CVV, or similar — via DTMF and/or speech, and delivers them directly to one tool, without the raw values ever passing through the LLM, transcript, session state, logs, or traces. It’s the PCI-safe alternative to GATHER for data that must never reach the model.
SECURE_INPUT is currently authored only in the legacy .agent.abl (line-based) format. The .agent.yaml parser does not yet accept this action.

Field properties

References inside deliver’s WITH: block must be a whole-value placeholder — pan: "{{secure.card_number}}" is valid; embedding it in a larger string (pan: "card:{{secure.card_number}}") or referencing an undeclared field is a compile error.
Every collected value lives only in a local secure buffer for the duration of the gate and is zeroized on every exit path (success, timeout, cancel, or failure) — it is never written to session state, so it cannot be referenced by {{ }} outside the deliver block, and it never appears in a trace, span, or log.

MESSAGE_KEY and BEHAVIOR

Two additional step-level properties:
  • MESSAGE_KEY: — a locale catalog key used to localize the step’s RESPOND: message.
  • BEHAVIOR: — free-form guidance text appended to the step’s reasoning goal (a convenience alias that folds into GOAL).

RESOLVE_SIP_DESTINATION

RESOLVE_SIP_DESTINATION: is a step action that computes a dynamic SIP transfer authority (VDN/host/port) before a call_transfer step runs. It runs a feeder — a workflow tool or a session memory key — matches the candidate against the approved_authorities allow-list declared on the call_transfer tool’s sipTransferId parameter, and commits the approved authority to a trusted server-side routing channel that the transfer then reads. The model never sees or sets the destination.
The feeder’s result supplies the {{route.vdn}} / {{route.ip}} / {{route.port}} values for the sipTransferId template. The two-zone template and the approved_authorities allow-list are declared on the tool parameter — see Dynamic destinations in the tools reference.

Branching and control flow

THEN / ON_FAIL

The most basic branching: THEN: specifies the next step on success, and ON_FAIL: specifies the step on failure:
ON_FAIL: also accepts a structured block for recovery flows — COLLECT: (fields to re-collect), GOTO: (step), RETRY: true, RESPOND:, and THEN::

ON_RESULT

ON_RESULT: provides multi-way branching based on the result of a CALL: action. Each branch has an IF: condition, optional actions, and a THEN: transition:

ON_RESULT branch properties

The ELSE branch (a branch with no IF condition) matches when no other condition is true.

ON_SUCCESS / ON_FAIL

An alternative to ON_RESULT: for simpler success/failure branching:
Both ON_SUCCESS: and ON_FAIL: support conditional branches:

ON_INPUT

ON_INPUT: provides branching based on user input, typically used after a GATHER: action:
A step that has only ON_INPUT branches — no RESPOND/prompt and no default (ELSE) branch — traps any unmatched input on the same step forever. The compiler warns (FLOW_PROMPTLESS_ON_INPUT_UNMATCHED_TRAP): add a default ELSE route or an authored prompt unless the wait is intentional.

Digressions

Digressions are intent-based escapes that can interrupt the current step. They match user intent patterns and respond accordingly:

Digression properties

When RESUME: true, the user returns to the interrupted step after the digression response. When GOTO: is specified, the flow transitions to that step instead. RESUME, GOTO, and HANDOFF are terminal.
For multiple ordered actions, use the canonical DO: block instead of flat keys. Each list item is an action (RESPOND, SET, CLEAR, CALL, HANDOFF, DELEGATE, GOTO, RESUME), and HANDOFF/DELEGATE may carry RETURN: and ON_RETURN: (with MAP:). Mixing flat keys with a DO: block is discouraged.

Global digressions

Digressions declared at the flow level (under global_digressions:) are available at every step:

Sub-intents

Sub-intents are scoped intents valid only within a specific step. They handle step-specific user requests like corrections or clarifications:

Sub-intent properties


Interactive actions

Flow steps can present interactive UI elements (buttons, selects, inputs) to the user. These are attached to RESPOND: messages and handled with ON_ACTION: callbacks.

Action elements

Interactive actions are defined in an ACTIONS: block within a step:

Element types

Buttons

Select (dropdown)

Input fields

Action element properties

Form submission

When a step contains input type elements, you can configure a form submission button:
The ACTIONS: block also accepts an optional RENDER_ID: to give the whole action set a stable identifier for rendering/telemetry.

ON_ACTION callbacks

The ON_ACTION: block defines handlers for user interactions with action elements:

ON_ACTION handler properties

For multiple ordered actions in one handler, use a canonical DO: block whose list items are actions (SET, CLEAR, LOG, RESPOND, CALL, GOTO/TRANSITION/THEN, HANDOFF, DELEGATE, COMPLETE).

Rich content

Steps can include rich content in multiple formats alongside RESPOND: messages. The runtime selects the appropriate format based on the channel. The block keyword is RICH_CONTENT: (canonical) or FORMATS: (original spelling, still accepted); the .agent.yaml format accepts rich_content or formats the same way.

Rich content format properties

The runtime recognizes these format keys. In addition to the messaging formats, several structured template types are available (see Rich content for their schemas):

Voice configuration

Steps can include voice-specific overrides for text-to-speech engines:

Per-turn endpointing

END_OF_SPEECH_TURN_TYPE is a small, deliberately-closed vocabulary describing what kind of answer the caller is expected to give next — the runtime resolves it to a concrete end-of-speech (silence/pause) timeout for the active speech recognizer, so authors don’t have to guess a millisecond value per turn: It can be set at any VOICE scope — agent-wide under EXECUTION.voice, on a behavior profile, on a flow step, or per RESPOND — with the narrowest scope winning, same as BARGE_IN/STREAMING:
END_OF_SPEECH_TIMEOUT_MS overrides the preset’s timeout with an explicit millisecond value when set alongside (or instead of) END_OF_SPEECH_TURN_TYPE.
Per-turn endpointing applies to the platform’s own speech-recognition pipeline. It does not affect TTS streaming, and is ignored on native realtime/speech-to-speech voice models where the provider owns turn detection. Omit it to fall back to the agent’s conversation-level pause configuration.

Scoped barge-in

barge_in (values allow / disallow) sets whether the caller can interrupt a specific spoken output — for example, forcing a compliance disclosure to finish. It may be authored at three scopes, from broadest to narrowest, with the narrower winning:
It can also be set on a named template’s VOICE: block. Placing barge_in on the agent-wide EXECUTION.voice block, or on branch/handler voice blocks, is rejected (VOICE_BARGE_IN_ILLEGAL_PLACEMENT); an invalid value is VOICE_INVALID_BARGE_IN.
This scoped, per-output policy is distinct from the agent-wide barge-in behavior authored under CONVERSATION → listening. Use scoped barge_in to protect (or open up) individual utterances.

Scoped streaming

streaming (true / false) sets the TTS delivery mode for a specific spoken output — whether audio starts playing as soon as the first tokens are ready (streaming, the default) or the runtime waits for the complete response before speaking it (non-streaming). It follows the same authoring scopes as barge_in: step-wide, per-RESPOND, or on a named template’s VOICE: block, with the narrower scope winning.
Omitting streaming defaults to true (streaming) at every scope.

Step-level no-input (caller silence)

A FLOW step can carry its own caller-silence policy in its own VOICE: block, as ON_NO_INPUT. It takes the same keys as CONVERSATION.listening.on_no_input (agent/project-wide) and applies on KoreVG pipeline voice while that step is the one waiting on the caller:
  • Owner. When the silence timer starts, the step currently waiting owns the window. The block applies only to that step; after COMPLETE/ESCALATE, or on a step with no block, the agent/project policy applies as usual.
  • Merging. Fields merge one at a time over the project and agent policy — a step can override just timeout_ms and inherit everything else. max_retries, relaxed_interval_ms, on_exhausted, and exhausted_message all follow the same rule, and enabled: true on a step turns silence handling on for that step even when the agent has it off.
  • Messages. Step messages don’t replace the agent-level list — each silence plays agent message N followed by step message N as one utterance (“Are you still there? Could you tell me your account number, Sam?”). Once the step’s own list is used up, the agent message plays alone (or the platform default, if the agent has none), so a step block never ends the call sooner than the agent alone would.
  • Retry count. Restarts when the caller speaks, and when a different step (or the same step entered again) takes ownership of the window.
  • Variables. {{var}} placeholders render in agent messages, step messages, and exhausted_message. A step message with a missing variable is skipped; an agent or exhausted message with a missing variable falls back to the platform default text.
ON_NO_INPUT is only legal directly in a FLOW step’s own VOICE: block. Placed under a GATHER field, RESPOND, a branch, a handler, or a response format, it’s a compile error (VOICE_NO_INPUT_ILLEGAL_PLACEMENT). The YAML format uses a step-level on_no_input: key.

Step-level error handling

Steps can define local error handlers that override agent-level ON_ERROR: handlers:
The error type (the handler’s key) is matched against a fixed, canonical set — default, llm_error, tool_error, routing_error, validation_error, timeout_error, and unknown_error. A key outside this set (for example the older tool_timeout, invalid_input, or api_error) is flagged at compile time (ON_ERROR_HANDLER_TYPE_DRIFT) and never matches at runtime — see Error types for the full table and the deprecated-name mapping. Narrow tool_error with SUBTYPE/SUBTYPES (known values: rate_limit, auth_failure, network_error, tool_timeout).

Step ON_ERROR handler properties

Complete example


Related articles:
  • Language overview — syntax rules and auto-detection
  • Tools — tool definitions used by CALL actions
  • GATHER — top-level gather field definitions
  • Agent declaration — GOAL used by REASONING: true steps, max_flow_iterations and model settings