Skip to main content
ABL provides lifecycle handlers and hooks that execute at specific points in an agent’s execution cycle. These allow you to initialize state, run side effects, and handle errors without embedding that logic in the main conversation flow.

Overview

ON_START handler

The ON_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:
This renders the template named welcome from the agent’s TEMPLATES: block.

ON_START with tool call

The tool call executes first, and its result is available in the session context when the RESPOND message is rendered.

Guarding a CALL with WHEN

An ON_START or hook CALL can carry an indented WHEN: guard so it only runs when the condition holds:
The WHEN: must be indented under CALL:, not written as its sibling at the same indent. A sibling WHEN: is not read — the call runs unconditionally, and the parser warns W496.

Conditional greeting

Use a BRANCHES: 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 startup SET/CALL and apply it before the welcome renders:
The tool needs an execution binding in the project. Top-level language directives resolve after top-level 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

A BRANCHES: 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:
A top-level 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:
DELEGATE and any other action in an ON_START list is dropped with a warning (W380-W388) — it is never run from here. A handoff target that isn’t a declared local HANDOFF entry is a compile error (ON_START_HANDOFF_TARGET_INVALID). The start-time handoff is local only: a route WHEN that only a model could judge (natural language, intent.*) is never satisfied at start, and a remote (A2A) target is always refused.
How it runs, by start type:
  • Eager start (no customer message yet — Web SDK/web-debug connect, pipeline voice answer, an identified agent-assist welcome): the welcome is delivered, then the child starts on its own with no synthetic customer message and speaks its own opener. Order: entry agent’s before_agent output, the welcome, the handoff announcement (if the experience mode speaks one), the child’s before_agent output, the child’s own first output. A RETURN: true child stays active after that unanswered opener; ordinary return rules apply from the customer’s first real message on.
  • Lazy start (the first customer 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, its before_turn hook) — taking precedence over the entry agent’s own first-turn routing.
  • Realtime speech-to-speech sessions run the list’s SET/CALL but never the handoff itself; the entry agent keeps the session.
Every outcome is recorded in the dsl_on_start_handoff trace (executed, blocked, failed, call_failed, superseded_by_tool_hook, skipped_mode) with a reason and identifiers only — never customer data. 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

The HOOKS: 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:
  1. Literal tag — a value that canonicalizes as BCP-47 (es, fr-CA, …) is validated and fixed at compile time.
  2. 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 ordinary SET.
  3. 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: a WHEN: <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_turn hook, 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 WHEN that can’t be evaluated does not hold — evaluation falls through to the next branch (or to an unconditional RESPOND, if present).
  • A branch can sit next to an unconditional RESPOND: in the same hook; the branches are the conditional alternative, evaluated independently.
A hook’s WHEN must be valid CEL. An invalid WHEN (including old plain-English text left over from before this feature) is demoted to an unconditional RESPOND for backward compatibility, and the compiler warns HOOK_WHEN_BRANCH_INVALID_CEL. Fix the condition, or remove WHEN: if the response really is meant to run unconditionally.

Example: audit logging hook

Example: turn timing

ON_ERROR handlers

The ON_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 error type (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:
Three names from older examples are not valid handler types and are silently rewritten as warnings, never matched at runtime: tool_timeout (use tool_error with SUBTYPE: tool_timeout), invalid_input (use validation_error), and api_error (use tool_error with an appropriate SUBTYPE, for example network_error).
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 form error_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

  1. Step-level handlers are checked first, matching on error type and optional subtypes.
  2. Agent-level handlers (ON_ERROR: block) are checked if no step-level handler matches.
  3. If no handler matches, the runtime uses the default error message from the MESSAGES: block (error_default).

MESSAGES

The top-level MESSAGES: 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

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.
Filler overrides are static, deterministic text — unlike other MESSAGES: keys, they do not support {{}} interpolation. User input is never substituted into a filler, by design.

Related articles: