Overview
ON_START handler
TheON_START handler executes once when a new session initializes, before the agent processes any user input. Use it for greeting messages, initial tool calls, and variable initialization.
Syntax
ON_START properties
Template references in ON_START
You can reference named templates in the RESPOND value:welcome from the agent’s TEMPLATES: block.
ON_START with tool call
Guarding a CALL with WHEN
AnON_START or hook CALL can carry an indented WHEN: guard so it only runs when the condition holds:
Conditional greeting
Use aBRANCHES: block to vary the greeting based on session context. Each branch is an IF
(or CONDITION) with a fallback ELSE, and can carry RESPOND, MESSAGE_KEY, VOICE, rich
content, and ACTIONS.
ON_START language resolution
A reasoning agent with no FLOW can resolve the caller’s language from a startupSET/CALL and apply it before the welcome renders:
SET/CALL and before branch selection; a BRANCHES DO: list (below) can set the language too. On Gemini Live specifically, ON_START already runs before the initial provider setup, so the first connection uses this language in legacy, runtime_guarded, and runtime_owned orchestration modes — the channel must have the language configured in its own supported-language policy, or an unresolved/unsupported value is rejected without changing the policy default.
Start-time HANDOFF
ABRANCHES: entry (or a bare top-level HANDOFF:) can hand the session to another declared local agent at start, before the first customer message is even understood. A branch’s DO: list runs these actions in order, by default after the welcome renders:
If the list sets the interaction language, the prefix through the last language directive (before any
HANDOFF) runs before the welcome renders instead, and the rest after:
HANDOFF: <Agent> (bare, bulleted, or the sole entry of a top-level DO:) runs only when no branch matched — a matched branch with no DO: list never falls back to it. With no branches at all, a bare HANDOFF: is instead decided by that declared route’s own WHEN (and ENABLED_WHEN), checked once at session start:
How it runs, by channel and start type:
- Eager start (no user message yet — Web SDK/web-debug connect, pipeline voice answer, an identified agent-assist welcome): the welcome is delivered and the child is activated without running — its
before_agenthook runs, but neither a FLOW child’s entry step nor a reasoning child’s model runs before the user says anything. The user’s first message is then forwarded to the child exactly as on a lazy start: a FLOW entry step with an extraction contract (extraction_hints,options, or a non-stringtype) takes its value from that message, a plain field asks its own question instead, and anON_INPUTon the entry step takes the message as its input; a reasoning child answers it as its first request. Output order: the entry agent’sbefore_agentoutput, the welcome, the handoff announcement (whenEXPERIENCE_MODEspeaks one), the child’sbefore_agentoutput — the child’s own opener is no longer part of this list, because the child hasn’t run yet. A child that has answered no request yet stays active even if it returns (RETURN: true); ordinary return rules apply from the user’s first message on. A child that hands the very first message straight back (for example an off-topic question) ends there instead of parking, so a later route to it starts the child fresh. A start with no welcome and no childbefore_agentoutput sends nothing — on pipeline voice the caller hears only the welcome (if any) and the child stays silent until the caller speaks. - Lazy start (the first user message starts the session — HTTP chat, LiveKit, SDK
initialMessage): the welcome is not delivered, and that first message is forwarded to the child once — after the entry agent’s input guardrails/constraints (and, for a FLOW entry, itsbefore_turnhook) — taking precedence over the entry agent’s own first-turn routing. - Realtime speech-to-speech sessions run the list’s
SET/CALLbut never the handoff itself; the entry agent keeps the session.
dsl_on_start_handoff trace event (executed, blocked, failed, call_failed, superseded_by_tool_hook, skipped_mode) with a reason and identifiers only — never customer data. This event isn’t one of Studio’s labeled Traces Explorer cards (unlike, say, a mid-conversation “Agent Handoff” or “Handoff Check” card) — look for it through a session trace export or the debug API if you need to confirm why a start-time handoff did or didn’t run. When the handoff doesn’t run: a route WHEN simply not met shows nothing, not even ON_FAILURE text; a refused or failed handoff (a handoff guardrail, an unloaded target) shows only what the route’s ON_FAILURE authors (RESPOND shows its text; CONTINUE, or no ON_FAILURE, shows nothing — never a generic failure message) — except ESCALATE, which escalates the session instead of keeping it with the entry agent; a target an ENABLED_WHEN switched off follows the normal authored-route rule (use ON_FAILURE: CONTINUE to keep the start silent).
After the start, the route behaves as an ordinary handoff — RETURN, CONTEXT, and ON_RETURN apply as usual — and the child never runs its own ON_START (which fires once per session, for the entry agent only). A route WHEN that reads start data stays true for the rest of the session, so a later turn can still route back to that agent after it returns. If the handoff should really happen only at start, put the condition on a branch IF: and leave the route’s own WHEN unset, or clear the triggering value on return. Pairing with ENABLED_WHEN cleared on return (ENABLED_WHEN: promo_open == true with ON_RETURN: set: promo_open = false) also stops the model from choosing that agent on later turns.
Caller data asserted by the browser (public-key SDK init) is not verified. When a start-time routing decision matters, mint the caller data on your own server through the customer bootstrap endpoint rather than trusting a client-asserted value in a branch
IF: or route WHEN.HOOKS
TheHOOKS: block defines actions that run at four lifecycle points. Unlike ON_START, hooks fire repeatedly throughout the session.
Syntax
Hook points
Hook action properties
Each hook supports the same set of action properties:SET_INTERACTION_LANGUAGE and CLEAR_INTERACTION_LANGUAGE are action-list actions — like SET and CLEAR, they can appear in any action list (ON_START, HOOKS, ON_ACTION handlers). Use them to pin the language the agent responds in — for example, after detecting a caller’s preferred language or reading it from a profile.
The value is classified when the agent is compiled:
- Literal tag — a value that canonicalizes as BCP-47 (
es,fr-CA, …) is validated and fixed at compile time. - Dynamic value — anything else that looks like a session-variable path (
user.preferred_language), a{{template}}, or a${dotted.path}expression is resolved at runtime, using the same value resolution as an ordinarySET. - Anything that is neither a valid literal tag nor a recognized dynamic form is a compile error.
A dynamic value is resolved and re-validated at runtime: if it doesn’t resolve to a canonical BCP-47 tag, or resolves to a language the voice channel isn’t configured to support, the override is not applied and the session keeps its current language.
WHEN / RESPOND branches
A hook can respond conditionally: aWHEN: <condition> with an indented RESPOND: "<text>" is a respond branch. Branches are checked in declaration order, after the hook’s own CALL and SET run, and the first branch whose condition holds wins:
- In a reasoning agent’s
before_turnhook, a matched branch’s text becomes the turn’s reply and the model is not called for that turn. Input guardrails and escalation triggers still run first and can still intervene. - In other hooks (
before_agent,after_agent,after_turn), a matched branch’s text is sent as a message at that point in the lifecycle. - A
WHENthat can’t be evaluated does not hold — evaluation falls through to the next branch (or to an unconditionalRESPOND, if present). - A branch can sit next to an unconditional
RESPOND:in the same hook; the branches are the conditional alternative, evaluated independently.
Conditional interaction-language actions (before_turn)
Abefore_turn hook can switch the session’s interaction language for the turn’s reply, its filler, and (on KoreVG voice without a voice language profile) the TTS voice, conditionally based on a WHEN: guard. A WHEN: whose indented body is SET_INTERACTION_LANGUAGE: or CLEAR_INTERACTION_LANGUAGE is a language branch. This is the same WHEN: mechanism as a respond branch, but with a language action instead of RESPOND:. A language directive written at hook level with no WHEN: is a catch-all.
- A value that is a BCP 47 tag (quoted or not) is a compile-time literal, canonicalized (
"es-mx"becomeses-MX). A value path or{{template}}is resolved at runtime, like a hookSETvalue; a result that is not a language the channel supports is refused (with a trace) and the language stays as it was. Any other value is a compile error (E394in.abl,E396in YAML). CLEAR_INTERACTION_LANGUAGEtakes no value (E395). A trailing colon is fine.- One
WHEN:body holds exactly one action: aRESPOND:(a reply branch) or one language action. Both in one body, or two language actions, drop the whole branch withHOOK_WHEN_BODY_MULTIPLE_ACTIONS. Write two separateWHEN:branches instead. - Language branches are tried in order. The first whose
WHEN:holds wins, so the catch-all (noWHEN:) belongs last. One placed earlier warns withHOOK_LANGUAGE_CATCH_ALL_NOT_LAST. - This grammar is
before_turnonly. The same directives under aWHEN:in any other hook are a compile error (HOOK_LANGUAGE_ACTION_NOT_BEFORE_TURNfrom the parser;HOOK_LANGUAGE_ACTION_UNSUPPORTED_HOOKif a language action reaches compilation attached to a different hook by another path). - A
WHEN:that is not valid CEL drops that action with the compiler warningHOOK_LANGUAGE_WHEN_INVALID_CEL. Unlike a respond branch, it never becomes unconditional. - YAML form: the hook carries
interaction_language_actions, a list of entries with an optionalwhenand exactly one ofset_interaction_language,set_interaction_language_expression, orclear_interaction_language: true. Anything else isHOOK_LANGUAGE_ENTRY_INVALID.
session.languageDetection). The hook’s own CALL and SET run later in the turn, so a language guard never sees values the hook itself just set. It sees what the previous turn left. The chosen language becomes the session’s interaction language and holds until another action changes or clears it.
Gating. Language actions take effect only when the project’s language-detection setting is on (a project-level Studio setting, not declared in ABL), and only on chat and on voice without a voice language profile. Omitting the project setting while authoring this grammar produces a compiler reminder (HOOK_LANGUAGE_ACTIONS_REQUIRE_SETTING). Realtime speech-to-speech sessions never run them: their language is fixed by the provider session.
session.languageDetection shape
Thesession.languageDetection object holds the latest detection result.
The project’s language-detection setting can configure an allowed-languages list and a minimum message length (default 12 characters). A message in a language outside the allowed list comes back
no_language with reason: "not_allowed", so an unsupported language is never offered as candidate. A message shorter than the minimum comes back low_confidence with reason: "too_short" and its guess as candidate, whatever the confidence: a single short word like “Ciao” can still score 1.0. Scripts used by only one supported language (Korean, Japanese, Thai, Greek, Hebrew, and most Indic scripts) are exempt from the minimum-length rule. The session’s current interaction language is sent as a hint to the detection service, improving accuracy on short or romanized input.
Recipe: avoid language flapping
Short or code-mixed turns (Hinglish, for example) can flip between two detected languages turn to turn. Applying everydetected result makes the reply, filler, and voice change each turn. Switch only on two consecutive matching detections of useful length. The hook SET records this turn’s detection for the next turn’s guard:
Recipe: FLOW agent with language switching
A FLOW agent takes the samebefore_turn actions. They run before the turn’s step. A model-written reply (a REASONING: true step) and fillers follow the chosen language. A fixed RESPOND: in a REASONING: false step is delivered as written and is never auto-translated. Localize it with {{locale.KEY}} from the project’s locale files: the language setters refresh the locale catalog in the same turn, so the step renders the text for the new language.
In this example the English branch comes first (the first matching WHEN: wins), so a confident English message clears back to the default language while any other confident detection switches:
Example: audit logging hook
Example: turn timing
ON_ERROR handlers
TheON_ERROR: block defines how the agent responds to specific error types. Each handler matches an error type and specifies a response, optional retry logic, and a follow-up action.
Syntax
Error types
The errortype (the handler’s key) is matched against a fixed, canonical set — a handler whose
key is not one of these is flagged at compile time (ON_ERROR_HANDLER_TYPE_DRIFT) because it can
never match at runtime:
Use
SUBTYPE (single value) or SUBTYPES (array) to match more finely within a type — both keys are case-insensitive but conventionally written uppercase.
Inline shorthand
For a handler that only takes a terminal action, use the single-line formerror_name: ACTION:
Error handler properties
Retry strategies
Then actions
Step-level error handlers
Error handlers can be overridden at the step level within a flow. Step-level handlers take precedence over agent-level handlers for the same error type.Step-level error handler properties
Error handler resolution order
- Step-level handlers are checked first, matching on error type and optional subtypes.
- Agent-level handlers (
ON_ERROR:block) are checked if no step-level handler matches. - If no handler matches, the runtime uses the default error message from the
MESSAGES:block (error_default).
MESSAGES
The top-levelMESSAGES: block overrides the default system messages the runtime uses for errors,
constraint violations, gathering, handoffs, escalation, completion, and voice/telephony prompts. Any
key you don’t set keeps its built-in default, and the templates support {{}} interpolation.
Common message keys
On the Genesys Bot Connector specifically, setting
escalation_format: "" (an explicit empty string, not omitting the key) suppresses the whole reply — text, buttons, and citations — on a successful terminal escalation; the configured escalation intent is still sent, so the customer’s session still escalates, it just gets no message. Omitting the key keeps the normal message behavior, and any nonempty value behaves as before. This only changes what the Bot Connector sends outward — it doesn’t affect the internal execution transcript, ongoing (non-terminal) replies, an unsuccessful escalation, or any other channel.tool_confirmation_duplicate_effect | “I already completed this earlier in our conversation. Would you like me to do it again?” | A tool that already ran is about to run again. Supports {{action_suffix}}. |
MESSAGES: accepts any message key, not only the ones above — including error-subtype
messages (error_tool_timeout, error_llm_error, error_validation, …), multi-intent UX prompts
(multi_intent_disambiguate_header, multi_intent_queued_notice, …), handoff/routing messages
(handoff_message, handoff_message_voice, routing_message), voice prompts (voice_repeat,
voice_nomatch, voice_noinput, …), and the greeting / out_of_scope messages. Set only the
keys you want to override.Filler overrides (voice)
On a voice channel, the runtime speaks a short filler phrase (for example “One moment.”) to avoid dead air while a tool call, reasoning step, handoff, delegation, extraction, or constraint check runs.MESSAGES: lets you override these with deterministic, author-controlled text using filler_<operation> keys:
Add a
_<language> suffix to supply a localized variant — either a bare BCP-47 primary subtag (filler_tool_call_fr) or a full region tag (filler_tool_call_fr-CA). At runtime the first match wins, checking the region tag before the bare subtag, and the operation-specific key before the general fallback:
filler_<operation>_<region-tag> → filler_<operation>_<language> → filler_<operation> → filler_general_<region-tag> → filler_general_<language> → filler_general → the platform’s built-in localized fallback.
A key with a region tag (
filler_tool_call_fr-CA) contains a hyphen — write it unquoted as a plain key: value line; both the .agent.abl and .agent.yaml formats parse the hyphen correctly.Related articles:
- Memory and constraints — variables initialized in ON_START and hooks
- Multi-agent and supervisor — ESCALATE and HANDOFF used in THEN actions
- Rich content — voice and rich content in handler responses