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.
A successfully rendered, non-empty MODEL_CONTEXT view is preserved exactly through later history compaction, summarization, and automatic conversation windowing — it’s never auto-shortened, replaced with a summary, or dropped for being old. It’s evaluated once, when the response is produced; later turns reuse that same rendered text rather than re-evaluating the template against newer data. This doesn’t raise the model’s context window or bypass the usual budget: an authored view may exceed the ordinary per-value/aggregate soft budget, but must still fit the aggregate hard capacity, or the runtime reports AUTHORED_MODEL_CONTEXT_CAPACITY_EXCEEDED rather than silently shrinking it. Keep MODEL_CONTEXT to the identifiers, status, and decision facts a later turn actually needs, and fetch further detail through a read tool — a large preserved view repeats in every later prompt, which adds up in prompt size, model cost, and latency. (This guarantee needs a matching Runtime version on every process handling the session; a rollback or a mixed-version deployment can silently lose it.)

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:

Map form (alternate syntax)

Fields can also be written as a map, without the leading -. Each lower-case name one level under GATHER: starts a field, with the same properties, inline required/optional/default= values, and field-level VOICE:/ASR:/DTMF: blocks as the list form:
Upper-case keys at that level (PROMPT, STRATEGY, and other recognized GATHER-block keywords) stay GATHER block properties, not fields. Only a lower-case (or mixed-case) name that is not a recognized GATHER-block keyword is read as a new field header.

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

Field-level listening settings (ASR:)

An ASR: block — a sibling of VOICE: on a single field — sets how the bot listens while that one field is being asked on a KoreVG voice call. Once the field is no longer being asked, the call’s normal listening settings return automatically:
Rules:
  • Keys are strict — an unknown key is a compile error (GATHER_ASR_UNKNOWN_KEY). Credentials and service IDs never go in ABL.
  • At most 500 vocabulary entries per field across every language, each at most 100 characters.
  • One listening-settings field per step. ASR: applies only when its field is asked on its own; a field sharing its step with other required fields gets warning GATHER_ASR_SHARED_STEP and uses the normal settings when asked together.
  • FLOW steps only. ASR: on a top-level (reasoning) GATHER field is preserved but has no effect, with compile warning GATHER_ASR_REASONING_UNSUPPORTED.
  • Deploy checks. A provider that no voice channel bound to the target environment supports blocks create/promote (GATHER_ASR_PROVIDER_UNRESOLVED); restore/rollback warn instead. An unsupported channel (non-KoreVG voice, realtime speech-to-speech), an unsupported language, too many words for the engine, a no-input wait with no-input off, or an uncovered language list are deploy warnings.
  • At runtime, anything that can’t be applied (an unknown engine, a rejected setting, a gateway that doesn’t confirm the change) falls back silently to the call’s normal listening settings — the call continues.
  • There’s no platform switch for this feature; to stop using it, remove the ASR: block and redeploy.
Session-value vocabulary (vocabulary.terms binding)
vocabulary.terms can read its words from a session value set earlier in the call, so the recognizer hears the specific names, streets, or codes a tool returned for this caller. Write one language’s list (or the whole list) as a single quoted "{{path}}" reference:
Rules:
  • The value is read each time the field is asked (first prompt, retries, rechecks). A list that changes between turns is used as it is at that moment. It applies only while that field is asked.
  • The reference must be exactly one quoted "{{path}}": dotted names with optional [n] indexes, at most 8 parts and 128 characters. Surrounding text, two references, filters, an unquoted {{...}}, or a reference inside a list are all compile errors (GATHER_ASR_TERMS_BINDING_INVALID).
  • Some values can never be bound (GATHER_ASR_TERMS_BINDING_FORBIDDEN): session identity and runtime values (session, config, env, history, messages, tenant_id, project_id, user_id, session_id, session_principal_id), the protected email aliases, any root starting with _, and any path through __proto__, prototype, constructor, _secret, or _secrets. A user.* value is personal data and needs restore_pii: true (see below).
  • The value can be a string (one word) or a list of strings and { phrase, boost } entries. On the Deepgram keyterm path a boost has no effect.
  • Each word is checked when read. A word that is missing, empty, longer than 100 characters, contains a comma/semicolon/line break, duplicates an earlier word, holds a redaction placeholder or a held secret value, or holds protected personal data is left out. The question is still asked. A field never fails because of its word list.
  • Words already present for a language come first. A language whose reference resolves to nothing stays empty for that question. It does NOT fall back to default.
  • Limits per field per question: 2,000 entries read, 64 KiB of text read, 500 words total including literal ones. On Deepgram, a list that includes session words is held to 400 estimated tokens (literal-only lists keep 500), and the gateway accepts at most 100 keyterms.
  • Only the KoreVG voice pipeline uses these words. Other channels and realtime speech-to-speech calls ignore the reference without reading the value.
Keep the list short and spoken-form (20 to 50 words is the range Deepgram recommends). Rank or cut the list in the tool, expand abbreviations, and use the form a caller says (“North Main Street”, not “N MAIN ST”). Never put session data in deepgramOptions.tag.
Personal data in bound words (restore_pii)
Values the platform has tokenized as personal data (names, phone numbers, emails) are left out by default. To send the original value to the speech engine, BOTH of these are required:
  1. The field sets vocabulary.restore_pii: true.
  2. The project’s PII pattern for that kind of data has an explicit speech_provider rule set to Original (PII Protection in Studio). A pattern whose default mode is Original does not count; without the rule the word is left out.
A restored word is sent only to the recognizer, never written to traces or logs, and each restore is recorded in the PII audit log without the value. On Deepgram, a call that sends a restored word also opts the request out of Deepgram’s model-improvement program. restore_pii on a field with no session-value reference is ignored with a warning.

Field-level keypad settings (DTMF:)

A DTMF: block, a sibling of ASR: under a single GATHER field in a FLOW step, sets how the bot collects a number the caller types on the phone keypad while that field is being asked on a KoreVG voice call. When the field is no longer being asked, the call’s normal keypad settings come back automatically (2 seconds between key presses, finish key #, up to 12 digits). A field without DTMF: behaves exactly as before. The caller can still answer by speaking; DTMF: changes only how typed digits are grouped into one answer:
The block can also be written over several lines (DTMF: followed by indented digits:, finishOnKey:, interDigitTimeoutMs: lines). Rules:
  • Keys are strict: an unknown key, a malformed value, or an out-of-range value is a compile error. min above max is a compile error. A DTMF: block with no settings is a compile error.
  • min above 1 is a compile warning: a caller who stops early is not finished by the pause.
  • One keypad-settings field per step. A field with DTMF: must be the only field in its step. Split other fields into their own steps.
  • FLOW steps only. DTMF: on a top-level (reasoning) GATHER field is reported as an unknown key (W170) and has no effect.
  • VALIDATE checks the stored value. A type: phone field is stored in international format (e.g. +919876543210), so ^[0-9]{10}$ never matches it. Use type: string to check digits exactly as typed, or write the pattern for the international format.
  • DTMF: does not change validation or retries: use VALIDATE, RETRY_PROMPT, MAX_RETRIES on the same field, plus the agent’s ON_ERROR handler for validation_error with SUBTYPE: max_retries_exceeded.
  • DTMF: collects ordinary data. The typed number reaches the agent, its memory, and the transcript like any GATHER value. For secrets (card numbers, security codes, PINs), use SECURE_INPUT instead.
  • Explicit keypad collections (SECURE_INPUT, the ivr_digit_input tool, voice surveys) keep their own settings and are unaffected.
  • At runtime, settings apply with the field’s listening settings (same mechanism as ASR:). If they can’t be applied (e.g. a realtime speech-to-speech channel, or a gateway that doesn’t confirm the change), the call keeps its normal keypad settings and continues.

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, call recordings, or traces: speech recognition is suppressed and recording is paused while the caller enters them, and the values are wiped from memory once the delivery call returns. 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. It also requires a live voice call and the SECURE_INPUT_ENABLED runtime setting (off by default): without a live voice session the step takes ON_FAIL; with the setting off, the step is skipped entirely. Both the delivery CALL and any validation CALL must be HTTP project tools (configured in Project Tools, not the agent’s own TOOLS: section) that send a JSON body.
AS, RESULT_FIELDS, and SUCCESS_WHEN may be written indented under the CALL line or beside it; AS: <name> may also end the CALL line itself.
Every outcome route (ON_SUCCESS, ON_TIMEOUT, ON_CANCEL, ON_FAIL) takes a step name only — a RESPOND or any other block indented under a route isn’t part of SECURE_INPUT; the parser reports the ignored line (W208) and an empty route is a compile error (SECURE_INPUT_EMPTY_OUTCOME_ROUTE). Put the response in the step the route names, as in the confirm step above.
Success. Without SUCCESS_WHEN, any 2xx delivery response takes ON_SUCCESS; a non-2xx status, a network failure, a timeout, or an unreadable body takes ON_FAIL. With SUCCESS_WHEN, a 2xx response must also match every listed field (compared as text, no case folding) or the step takes ON_FAIL — use this when an API can report a decline inside an HTTP 200. A delivery that timed out after the request was sent also takes ON_FAIL, even though the charge may have gone through — don’t retry payment from that step. Reading the response. AS binds only the RESULT_FIELDS the response contains, as top-level scalar values. A field is dropped (never bound) when its value contains any collected value’s digits, contains a valid card number, or is an object or list — the step’s trace names the bound/dropped fields, never their values. The bound fields are then ordinary session values for later steps, as {{payment_result.Status}} above.

Field properties

A failed check asks for the field again, up to MAX_ATTEMPTS. A CALL validation rule passes only when the tool returns valid: true, and may reference the field being checked plus any fields collected before it. A field accepts only PROMPT, COLLECT, and VALIDATE — anything else is ignored with a warning (W210). References inside the delivery 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 delivery/validation calls, 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.

Gateway turn detection

TURN_DETECTION turns KoreVG gateway turn detection on or off for a level and sets its patience. With it on, the voice gateway decides when the caller finished speaking (voice activity, acoustic/text end-of-turn models, speech-provider finals) instead of the speech provider’s silence timer. It can be set at any VOICE scope: a concrete RESPOND’s VOICE block, a flow-step VOICE block, a GATHER field VOICE block, a behavior-profile VOICE block, or EXECUTION.voice:
Precedence, most specific first: the response (or step, or field) that asks the caller something, the active behavior profile, EXECUTION.voice, the voice channel’s own turn-detection setting, off. A response/step value applies to the caller’s answer to it and holds while the step re-asks (retries); the next answered response with no key falls back up the chain. On a REASONING: true step (model writes the replies), the step’s own turn_detection applies to every caller turn while the conversation stays in that step (only turn_detection is taken from that step’s VOICE, nothing else). An invalid value is a compile error (VOICE_INVALID_TURN_DETECTION) and is never applied. A KoreVG gateway without the Dictation/Slow dictation profile uses its most patient profile instead (0.9 falls to 0.8, or 0.7 if 0.8 is also missing).
Gateway turn detection applies to KoreVG pipeline calls only, never realtime (speech-to-speech), other voice transports, or the PCI SECURE_INPUT gather. While turn detection is on, the gateway ignores the end-of-speech timeout, so a block that turns it on AND also sets END_OF_SPEECH_TURN_TYPE or END_OF_SPEECH_TIMEOUT_MS gets a compile warning VOICE_TURN_DETECTION_WITH_END_OF_SPEECH.

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