Skip to main content
This page documents multi-agent orchestration constructs (DELEGATE, HANDOFF, ESCALATE, COMPLETE) and the SUPERVISOR: declaration for top-level routing.

SUPERVISOR declaration

A Supervisor is a top-level orchestrator that routes user messages to the appropriate child agent based on intent, context, and declarative rules. Supervisor documents use the SUPERVISOR: keyword instead of AGENT: and define agent references, routing rules, state schemas, policies, and communication settings.

Overview

While agents handle domain-specific tasks, the Supervisor decides which agent should handle each user message. It does not execute tools or gather information directly; it classifies intent and routes accordingly.

Agent references

The Supervisor declares which agents are available for routing. Each reference includes a file path, an alias, and a list of capabilities.

Syntax

Agent reference properties

Routing rules

Routing rules define conditional logic for directing messages to agents. Rules are evaluated in priority order.

Syntax

Alternatively, routing can be declared using the HANDOFF: block within a Supervisor, following the same syntax as agent handoffs:

Routing rule properties

Routing actions

Intent-based routing

For more granular routing, use INTENT_MATCH with intent-to-agent mappings:

Routing flags

Conditional routing (WHEN clauses)

WHEN clauses use the same expression syntax as Expressions & functions, with the same size limits. A long WHEN can combine parts with OR / AND, but each single comparison or list stays under 4,096 characters and each regex pattern under 500. Common patterns include:

Routing constraint blocks

Add constraints to limit when a rule applies beyond the WHEN condition:

State schema

The Supervisor can declare a state schema that defines typed variables organized by namespace.
State variables follow the same VariableDefinition structure as agent variables, with the addition of namespace grouping.

State variable properties

Policies

Policies define high-level behavioral rules for the Supervisor, constraining what it is allowed and forbidden to do.

Policy properties

Policy rule properties

Communication settings

Communication settings define the Supervisor’s language, tone, and vocabulary preferences.

Communication properties

Behavior settings

The BEHAVIOR block declares the Supervisor’s execution role, which governs how it participates in coordination.
*role is optional, but omitting it on an agent that has a BEHAVIOR block produces a deprecation warning and falls back to legacy inference. Declaring role is the recommended, forward-compatible form.

Execution roles

Breaking changes in this release - Artemis 1.4.0:
  • canRespondDirectly is deprecated in favor of role. If both are present and conflict (canRespondDirectly: true ↔ orchestrator, false ↔ router), compilation fails (AGENT_EXECUTION_ROLE_CONFLICT). Remove the deprecated field or pick the matching role.
  • A SUPERVISOR must be router or orchestrator — role: assistant fails compilation.
  • role: router requires at least one HANDOFF ownership target.
  • Only an orchestrator may declare DELEGATE: — a router (or assistant) that uses DELEGATE fails compilation (AGENT_RETURNED_WORK_REQUIRES_ORCHESTRATOR). Routers hand off; orchestrators own returned work.

Intents

In addition to INTENT_MATCH inside a routing rule, a Supervisor may declare a top-level INTENTS: block listing intent labels (and optional lexical-fallback behavior) that routing and classification draw on. Supervisors can also hand off to other supervisors, enabling hierarchical composition, and may declare an ON_ERROR block to handle routing failures.

Execution pipeline (pre-classification)

This is an advanced, opt-in optimization. Most supervisors do not need it — routing works without an EXECUTION block.
A Supervisor can enable a pre-classification pipeline that runs a smaller, faster model before the main reasoning LLM. The classifier detects user intent and can short-circuit routing for obvious cases, avoiding the cost of a full reasoning call.
The classifier model is not selected in the agent’s EXECUTION.pipeline block — it is resolved from project-level runtime configuration via modelSource (default uses the platform’s tool-selection model; tenant uses a specific tenant model identified by tenantModelId). A model: key inside the pipeline block is deprecated and ignored by the runtime. The top-level EXECUTION.model still sets the main reasoning model as usual.

Pipeline options

Parent return options

When a supervisor hands off with RETURN: true, the runtime checks the caller’s replies against the supervisor’s routes while the specialist is active, so a caller who changes topic can be routed back. The parentReturn options control that check. Each is read from the supervisor’s EXECUTION.pipeline.parentReturn block first, then the project’s pipeline.parentReturn runtime configuration (Studio: Runtime Config → Pipeline), then the default.
By default the check runs before the specialist sees the reply, and a matching route wins even when the reply answers the specialist’s question. A short answer such as “no” to “Does anyone else have access to this card?” can be classified as another route. With answerFirst: true the specialist gets the reply first: A skipped check is traced as a pipeline_classifier_skipped decision with reason parent_return_answer_first.
  • A flow reply that answers the pending question and also names a new topic stays with the flow.
  • Instruct a reasoning specialist to hand back requests outside its scope with __return_to_parent__. Without that instruction, a caller who changes topic stays with the specialist.
Write the pipeline keys in camelCase in an agent file, such as shortCircuit and confidenceThreshold. Snake_case keys are dropped without a message. An agent file does not range-check the threshold values. The project runtime configuration does. For pure routing supervisors, sequential mode with shortCircuit.enabled: true gives the best cost savings. In parallel mode the classifier adds latency protection but no cost savings, since both calls run regardless.

HANDOFF

HANDOFF transfers conversational control from the current agent to another agent, passing context and optionally expecting a return.
When a handoff enters a target agent whose entry point is a REASONING zone (or a scripted flow step), the target begins executing immediately on entry — it does not wait for a new user message. This lets a receiving agent act on the forwarded context (see history, below) as soon as control transfers.

Syntax

Properties

The primary keyword is EXPECT_RETURN. The older RETURN keyword is still parsed for backward compatibility, but new agents should use EXPECT_RETURN. The legacy top-level MAP block is superseded by ON_RETURN.map (see Return expectations).

Handoff context

The CONTEXT block defines what information the target agent receives.
The legacy grant_memory: [path, ...] form is no longer supported and produces a parse error. Use memory_grants with explicit path/access entries instead (see Memory grants).
CONTEXT may also be expressed as a shorthand where pass, summary, and history appear as direct siblings of the handoff entry rather than nested under CONTEXT:.

History strategies

The history property controls how much conversation history the target agent receives.
The legacy shorthand history: last_10 is still accepted during the compatibility window, but new agents should use the typed mode + count block.
Three forms parse without error and are silently ignored, each falling back to full: a single-line flow-mapping value such as history: { mode: last_n, count: 3 } (use the indented block shown above instead); an uppercase key inside CONTEXT:, such as HISTORY: or PASS: (keys inside CONTEXT: must be lowercase, unlike the handoff entry’s own TO: / WHEN: keys); and a misspelled value, such as history: recent. Check the Request view of the target agent’s first LLM step (see LLM step request and response) if the history you expect isn’t showing up.

Return expectations

When EXPECT_RETURN: true, the calling agent pauses and waits for the target agent to complete. When the target explicitly returns control (via its built-in return capability), the parent resumes. When EXPECT_RETURN: false, the handoff is a one-way transfer and the calling agent’s turn ends — the parent does not automatically get control back.
EXPECT_RETURN: false disables automatic return, not every path back. If the child agent (while that same nested handoff call is still unwinding) itself authors a HANDOFF route back to that exact direct parent, and the route is otherwise authorized, Runtime resumes the parent’s completed thread as the conversation owner — it does not re-enable automatic returns, and it does not make any other completed ancestor resumable. For a FLOW child, this only applies while its authored FLOW is still live; once the child reaches COMPLETE, that parent frame is closed for good. Routes to an unrelated or non-direct ancestor remain blocked as a cycle.
ON_RETURN controls what happens when the target returns. It accepts either a string (the name of a named return handler, or a built-in action) or a structured block:

ON_RETURN properties

Return handlers

A top-level RETURN_HANDLERS: block declares reusable named handlers that run on the parent after a EXPECT_RETURN: true child returns. Reference one from ON_RETURN.handler.
A handler runs CLEAR before map, and RESPOND after set. If any CLEAR, map, or set target is one the parent cannot write, the runtime applies none of that return’s effects, including RESPOND. The parent’s ON_RETURN also runs when a child completes on a silence route turn, that is, after the caller’s silence retries run out. From 1.9.1 the map, handler, and resume behavior apply the same way as when the child completes on a caller answer. For a reasoning parent without a FLOW, a non-blank forwarded customer request belongs to bounded continue. This includes a named handler with CONTINUE: true.
  • The handler runs once. Completion and deterministic routing come before model-based routing.
  • Within the forwarded continuation’s own deterministic coordination chain, the runtime dispatches each authored DELEGATE entry automatically by WHEN at most once, even if WHEN stays true. Nested ordinary continuations keep their earlier dispatch policy. Explicit model-selected delegation stays eligible. Distinct same-target entries keep their own input and result mappings.
  • The parent can synthesize successful typed results. Later customer turns can select the entries again.
  • The returned child keeps its waiting state and cannot block its own forwarded request. Other pending children keep their waiting-work protection.
  • A return without an authoritative forwarded request keeps the existing continuation policy.

Handoff failure strategies

ON_FAILURE defines a parent-side fallback for failures that occur before the target accepts the handoff — for example target lookup, pre-transfer validation, or dispatch failures. It does not replace the timeout path after an accepted returnable handoff, and it does not fire when a downstream child later reports its own failure.

Experience modes

EXPERIENCE_MODE shapes how the transfer is presented to the end user. It is valid on both HANDOFF and DELEGATE.

Async dispatch

For remote agents, set ASYNC: true to dispatch the handoff asynchronously. The calling agent receives a notification when the remote agent completes rather than blocking.

Memory grants

Use memory_grants to give the target agent scoped access to specific persistent memory paths. Without this, the target agent cannot read or write the parent’s persistent variables. Each grant declares a path and an access level.

DELEGATE

DELEGATE invokes a sub-agent synchronously, waits for it to complete, and maps the result back into the calling agent’s context. The sub-agent runs in its own scope and does not have direct access to the parent’s session variables.
Breaking changes in this release - Artemis 1.4.0:
  • DELEGATE may only be declared by an orchestrator. A router or assistant that uses DELEGATE fails compilation.
  • DELEGATE is a one-shot internal call — it cannot wait for multi-turn user input. If the target agent has required GATHER fields, you must supply them in INPUT: (or make them optional/defaulted). An unmapped required field fails compilation (DELEGATE_CHILD_GATHER_INPUT_UNMAPPED); model a user-interactive child as an EXPECT_RETURN: true HANDOFF instead.

Syntax

Properties

Input/output mapping

The INPUT block maps values from the parent agent’s session context into the sub-agent’s input parameters:
The RETURNS block maps the sub-agent’s result fields back into the parent’s session variables:

Delegate failure strategies

Structured failure example:

Remote agent support

DELEGATE supports invoking agents running on remote services. Add a REMOTE block to configure the connection.

Remote properties

A remote agent’s credentials never live in the ABL file. The project’s external agent registry record holds them. The record either references an Auth Profile through authProfileId, or stores inline authType (bearer, api_key, or none) plus an authConfig reference to the project secret. When authProfileId is set, it takes precedence and the runtime ignores inline auth.
  • Header-rendering profiles add headers to the outgoing request. These are bearer, header API key, basic, custom header, OAuth client credentials, OAuth app and token grants, and Azure AD.
  • An aws_iam profile in IAM role mode (credentialMode: assume_role) makes the runtime assume the customer’s IAM role and SigV4-sign every request to the agent.
  • Static-key AWS IAM, AWS credential-pair, mTLS, query-parameter API key, and personal profiles are rejected.
Rotating a credential is a registry or profile update, not an agent edit.
LOCATION: remote is explicit and recommended, but the compiler also treats an ENDPOINT without LOCATION as remote. The remote block can be authored either nested under REMOTE: or as top-level LOCATION/ENDPOINT/PROTOCOL keys on the entry.

Availability (ENABLED_WHEN)

ENABLED_WHEN turns a HANDOFF or DELEGATE route on or off for the whole session, decided once before any turn is understood. WHEN still decides which available route a given turn takes — ENABLED_WHEN decides which routes exist at all:
Writing the condition. Put ENABLED_WHEN: on the entry, after WHEN: (YAML key: enabled_when). Read only session data known before the turn: _metadata.*, env.*, caller.*, session.*, user.*, and memory fields. Don’t read what the current turn means (intent, routing.*, input) or write natural language — both are compile errors (ENABLED_WHEN_SEMANTIC_REFERENCE). A long condition can continue on deeper-indented lines, after an inline value, an empty value, or a block indicator (|, >, >-); a line that looks like a key or starts a new entry ends it, and # lines are comments. The compiler warns (ROUTE_WHEN_MULTILINE_JOINED) when it joins lines written after an inline or empty value; a condition opened with a block indicator is joined without the warning. How availability is decided. Each target is judged by its own entries: a HANDOFF to X by the HANDOFF entries for X, a DELEGATE to X by the DELEGATE entries for X. The target is available when any of its entries is enabled — an entry without ENABLED_WHEN is always enabled, so one unconditional entry keeps the target permanently available (the compiler warns, ENABLED_WHEN_SHADOWED_BY_UNCONDITIONAL_ENTRY). The decision fails closed: a condition that can’t be evaluated disables the entry, and so does one that reads a value the session doesn’t have (unset, null, or an empty string) — even inside OR — unless the condition explicitly allows that value to be missing. A value compared with == null, != true, or IS NOT SET anywhere in the condition counts as known, and an unset value then reads as null:
_metadata changes made during the session apply to every agent in the conversation, including agents waiting for a child to return. When a target isn’t available. The route is withheld from the model entirely — left out of prompt route lists in both text and voice. A model that calls it anyway is refused and told which routes are available. A route the author wrote (a FLOW step HANDOFF, ON_ACTION, a digression, a tool hook, ON_ERROR, an ON_START handoff) is refused and runs its own failure policy: the entry’s ON_FAILURE for a handoff, the delegate’s ON_FAILURE for a delegate. RESPOND shows its message, ESCALATE escalates the same turn, CONTINUE skips the route and runs the rest of the step. A handoff with no ON_FAILURE tells the user the option isn’t available and the agent stays on its current step; a delegate with no ON_FAILURE is simply skipped — a refused route never leaves a turn without an answer. A constraint’s ON_FAIL/ON_EXHAUSTED handoff is a safety route that must always be reachable, so its target can’t be fully conditional: the compiler rejects one whose every HANDOFF entry has ENABLED_WHEN (ENABLED_WHEN_SAFETY_ROUTE_CONDITIONAL). When every route of an agent is unavailable, the agent answers the user directly. Diagnostics
_metadata is supplied by the caller. Use ENABLED_WHEN to shape the experience, not as an authorization boundary. Availability is always enforced — there’s no runtime setting that turns it off, and an entry without ENABLED_WHEN behaves exactly as before this feature existed. The trace records which routes were offered and withheld on each model turn, and every refusal, with the condition text — never the session values it evaluated.

Parallel delegation (fan-out)

There is no FAN_OUT: section in ABL. Fan-out is a runtime capability, not an authored construct — agents do not declare it directly.
When a single user message contains multiple distinct requests that need different specialists, a supervisor’s runtime can dispatch them in parallel and then synthesize the branch results into one unified response. This is exposed to the reasoning model as a built-in __fan_out__ system tool (you never write this tool yourself) and is bounded to 2–5 sub-tasks per message. Separately, when multiple DELEGATE entries have their WHEN conditions satisfied on the same turn, the runtime can run those delegations concurrently. Each delegation runs in its own scope and its RETURNS are mapped back into the parent context as it completes.

ESCALATE

ESCALATE transfers the conversation to a human operator. It is designed for situations where the agent cannot or should not continue autonomously.

Syntax

Trigger properties

PRIORITY is optional and defaults to medium. Note that ESCALATE inside an ON_ERROR handler is not parsed — to escalate from an error handler use THEN: ESCALATE with REASON: "..." instead of an inline ESCALATE: block.

Connector action

CONNECTOR_ACTION names a connector action to invoke for ITSM integration when the escalation fires (for example, opening an incident in an external ticketing system). It is declared at the top level of the ESCALATE block, alongside triggers, context_for_human, routing, and on_human_complete.

Priority levels

Context for human

The context_for_human block lists session variable names to include in the escalation package. The human agent sees these values in their interface.
You can also use structured context items with templates:

Routing configuration

The routing block controls how the escalation is routed in the human agent system.
Earlier drafts used skill_tags and priority_boost. The current runtime contract uses skills and priority — the older keys are silently ignored. The routing block also accepts several advanced routing keys for richer human-agent integrations.

Conditional routing entries

routing: accepts two shapes, distinguished purely by what follows the colon:
  • A mapping (routing: { connection: ..., queue: ..., ... }, as above) — a single, unconditional destination. This is the legacy, still-supported form.
  • A YAML sequence (a list of --prefixed entries) — an ordered list of conditional routing entries, each with the same fields as the single form plus id and when. When authored as a list, routing itself is left unset and the entries compile into routing_entries instead.
Every other field (connection, queue, skills, priority, post_agent, voice, and all the advanced routing keys) means exactly the same thing per entry as it does in the single-object form.
A routing entry’s when must be deterministic — it’s evaluated in code against session data and the reserved system.* values (system.channel, system.locale, and similar), never deferred to the model. This is stricter than a trigger’s WHEN, which may be prompt-only/semantic: a condition that’s valid on a triggers.WHEN but isn’t deterministic is a compile-time error on a routing entry (ESCALATE_ROUTING_WHEN_PROMPT_ONLY) — a live transfer destination can never depend on model judgment.
Evaluation is first-match-wins, in authored order. The compiler enforces the entry list’s shape:
An entry whose WHEN guards system.channel == "voice" without a voice: block, or guards a non-voice channel without a connection, gets an advisory (non-blocking) warning — worth checking, since a matched entry with neither is unlikely to route anywhere useful.
sip_transfer_id
A voice routing entry (or the legacy single routing.voice block) may declare an explicit SIP transfer destination directly in the DSL with sip_transfer_id — a literal or {{ }}-templated SIP URI:
This requires call_transfer_type: sip — declaring sip_transfer_id under pstn or custom is a compile error (PSTN transfers always use the project’s PSTN transfer target, not a SIP transfer id). The compiler always stamps the resulting destination’s provenance as config itself — an authored sip_transfer_id_source value is discarded and never trusted verbatim, the same trust posture as the call_transfer tool’s default_source: config gate. Omit sip_transfer_id to keep today’s behavior: the transfer target comes from the project-configured Agent Transfer settings.

Advanced routing keys

Beyond connection / queue / skills / priority, the routing block accepts additional keys for richer human-agent (contact center) integrations. Some are structured; others (flow_policy, provider_config, metadata) are opaque pass-throughs whose shape is defined by the receiving human-agent integration rather than validated by the ABL compiler. Every key accepts snake_case or camelCase.
These keys are hints for the receiving human-agent system — the valid sub_type values, provider_config shape, and inline flow_policy structure are defined by that integration, not the ABL compiler. Voice-channel transfer settings live under routing.voice (see Voice transfer settings).

Handoff context for CCAI

A routing object, or each conditional routing entry, can declare user_info as a map of display labels to scalar literals or {{...}} templates. Use it only for CCAI webhook_channel connections. Select the connection by its connector name or connection ID, not its display name. The camelCase aliases userInfo and userInfoInCustomData are also accepted.
  • A null member is valid. The runtime drops it.
  • An object or array member is a compile error (ESCALATE_ROUTING_USER_INFO_NON_SCALAR). A user_info value that is not a map is also a compile error (ESCALATE_ROUTING_USER_INFO_NOT_MAP).
  • An empty map (user_info: {}) is still an explicit opt-in.
  • By default the values are display context only. Set user_info_in_custom_data: true to copy the surviving values into customData.userInfo. The flag applies per entry. A flag without a user_info map produces the warning ESCALATE_ROUTING_USER_INFO_COPY_WITHOUT_USER_INFO.
  • A transfer with handoff context replaces the AI for Service user’s UserContext.customData. Do not rely on existing custom data being merged.
  • The values are a snapshot taken when the transfer starts. AI for Service flows that route on time-sensitive values should compare customData.artemis.updatedAt with the queue time. Gate priority or other sensitive routing on customData.artemis.identityVerified.
  • When a template directly references a SENSITIVE: true field or entity, the compiler warns. Mask the value at the source, for example by having the CRM tool return the masked value.
  • With PII redaction on (the default in staging and production), a user_info value that contains a vault token is sent as [protected]. metadata values, including vault tokens, are forwarded as stored.
  • AI for Service stores customData unencrypted for up to 24 hours. Send only the values its flows need. For metadata keys that AI for Service flows address by path, use letters, digits, _, and -.
Republish agents after you upgrade so the compiled IR includes user_info. Agents compiled before the release do not contain it.

Handoff language

The first CCAI call carries the session’s current interaction language in InteractiveLanguage, metaInfo.agentTransferConfig.language, and, with handoff context, customData.artemis.language. Follow-up messages keep the language the transfer started with. To transfer in the language the customer writes in:
  1. Turn on the project’s language detection setting. The runtime also needs a Language Detection service.
  2. Apply the detection with SET_INTERACTION_LANGUAGE in a before_turn hook. See Hook action properties. The language is set before the escalating turn’s reply, so the first call carries it.
{{session.languageDetection.language}} and {{session.languageDetection.status}} are ordinary session values. You can send them in user_info or metadata. The language value keeps the region (zh-CN) that the language fields drop.
The escalation confirmation the customer sees follows the same interaction language. The runtime looks for a Localization asset for that language first: <locale>/<agent>.json, then <locale>/_shared.json, using the channel key (for example escalation.digital) and then escalation_format. It falls back to the project’s Escalation Templates and then the built-in English text. Add one asset for each supported language in Settings > Localization, for example hi/_shared.json.

Voice transfer settings

When an escalation routes over a voice channel, the optional routing.voice block controls how the live call leg is handed off at the SIP/transport layer. It is ignored on non-voice channels.
Without sip_transfer_id, call_transfer_type selects a transport, not a destination — the transfer target is project-owned (Agent Transfer settings), and authored DSL cannot name a phone number or SIP URI, the same boundary the built-in call_transfer tool enforces by default. sip_transfer_id is the one explicit exception to that rule (see above). For a Genesys Audio Connector transfer specifically, use the dedicated genesys_sip_headers block below to reach the Genesys TRANSFER_TO_AGENT callevents call — sip_headers here also reaches that transfer, but only on the call-ending SIP BYE (see the note under Genesys SIP transfer headers), which is a separate wire from the callevents POST.

Custom voice transfer

call_transfer_type: custom (ABLP-3552, CXone/NICE integration) replaces the SIP/PSTN leg transfer with an ordered sequence of project tools. Instead of moving the call, the runtime keeps the Artemis audio leg live and runs the named tools serially as a notification sequence — for example, obtaining a token from one HTTP tool and signaling an external contact-center API with the next.
  • custom_tools is an ordered list of 1–8 exact, existing project HTTP tool names, invoked in the authored order. The first tool may capture a SecretRef that a later tool consumes.
  • custom routing is mutually exclusive with a connection — a custom escalation has no SIP/PSTN connection.
  • The compiler validates shape (blocking errors): the transfer type must be sip/pstn/custom; custom requires 1–8 custom_tools; each name must be a non-empty ASCII identifier (^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$); names must be unique. Tool existence and HTTP kind are enforced at runtime (a non-HTTP or unknown tool is rejected then).
Custom voice transfer is notification-only in this version — it signals an external system while the caller stays on the Artemis leg; it does not hand the live call off to a human queue. Use sip/pstn (or ESCALATE routing to a human connection) when the call itself must be transferred.

Genesys SIP transfer headers

When an escalation transfers a voice call to Genesys (through the Genesys Audio Connector), genesys_sip_headers attaches custom SIP headers to the transfer — carrying the target queue, a reason code, and optional metadata. Declare a default under routing: and/or an override on individual triggers with the GENESYS_SIP_HEADERS: block. A trigger’s block takes precedence over the routing default.
Each field is optional. The *_header_key fields name a SIP header; the paired field supplies its value. The compiler validates the block against the same limits the runtime transfer enforces:
  • Header-name fields (*_header_key) must be RFC 3261 tokens, at most 64 characters.
  • Header values must contain no CR/LF/NUL characters. queue_name and reason emit as real SIP headers and stay within 1024 UTF-8 bytes; the serialized metadata and additional_headers values ride the HTTP callevents body instead of a SIP header, so they are not byte-bounded.
  • metadata must be a string or a non-empty map — arrays and scalar values are rejected; an empty {} is ignored.
  • Two header-name fields must not resolve to the same SIP header key (compared case-insensitively). A collision (GENESYS_SIP_HEADER_KEY_COLLISION) is a compile error, because the transfer rejects colliding header keys at runtime.
  • Unrecognized fields are ignored with a warning. Keys may be written in snake_case or camelCase (for example queue_header_key or queueHeaderKey).
genesys_sip_headers and the plain sip_headers (under Voice transfer settings) are two different wires on the same Genesys Audio Connector transfer, and both can be authored together: genesys_sip_headers (plus additional_headers, below) populates the TRANSFER_TO_AGENT callevents call; sip_headers is carried separately on the SIP BYE that ends the call leg once the transfer succeeds. A conversation-summary User-to-User (UUI) header on that same BYE is planned but not yet wired for an ESCALATE-triggered transfer as of this release — it’s currently only populated on the call_transfer tool path.
additional_headers (map of header name → value) adds arbitrary extra headers to the callevents TRANSFER_TO_AGENT payload, each as its own header alongside the queue/reason/metadata trio — the equivalent of a contact-center “additional context” bag. UNLIKE the verbatim fields above, its values are template-resolved against session values at runtime, so a {{...}} reference is substituted on the wire (a key that resolves empty is dropped); header names are still validated as RFC 3261 tokens at compile time. A name that collides (case-insensitively) with the resolved queue/reason/metadata key, or a reserved platform header, fails the transfer.

Transfer flow policies

routing.flow_policy_ref (or transfer_flow_policy_ref) names a policy declared in a top-level TRANSFER_FLOW_POLICIES: block. A transfer flow policy defines what happens while a caller waits in queue or when no agent is available — for example, playing a message or triggering a follow-up workflow. The policy body is free-form (its shape is defined by the human-agent integration, not the ABL parser).

Post-completion actions

The on_human_complete block defines what happens after the human agent finishes.
Each entry has a condition and an action. The action can be COMPLETE (end the conversation), HANDOFF (transfer to another agent), or CONTINUE (resume the current agent).
When the human agent closes the conversation, a FEEDBACK survey card can be shown — authored either on the agent itself or inside a FLOW: step. See Post-transfer surveys for exactly which block is picked and when.

COMPLETE

COMPLETE defines the conditions under which the agent considers its task finished. Each completion condition specifies a WHEN expression and an optional response.

Syntax

Properties

COMPLETE’s WHEN is deterministic — it must be a boolean expression over session state. Plain-English / intent-style text is rejected at compile time (PLAIN_ENGLISH_COMPLETE_WHEN). Use NLU/routing for intent matching, not COMPLETE WHEN. A fully-quoted WHEN (WHEN: "status == \"done\"") is unwrapped and evaluated the same as an unquoted one when it contains an operator — see Quoting a condition.
A COMPLETE condition that matches does not complete the agent if the agent’s own CONSTRAINTS fail on the state it would complete with — the failing constraint’s ON_FAIL runs instead, so a completion can never bypass a constraint that would otherwise block the same state.
Example of a non-terminal completion that collects feedback and then keeps the session open:
Always clear the condition that triggered a COMPLETE block when using TERMINAL: false (for example, by using SET). Otherwise, the same completion condition may be evaluated again on the next user message, creating a completion loop. When such a loop is detected, the platform automatically closes the session and logs a warning.

Rich content in completion

COMPLETE responses support voice configuration and rich content, the same as any RESPOND:
FORMATS/RICH_CONTENT, VOICE, and ACTIONS are recognized regardless of where they appear in the condition — immediately after RESPOND, after STORE/SET/TERMINAL, or on a condition with no RESPOND at all (for example, a completion that renders only a FEEDBACK card):

SIP headers on the closing BYE (voice)

A condition’s VOICE block can name the SIP headers sent on the BYE that ends a KoreVG voice call. A contact center that receives the call back can route on them, for example X-RouteType: hangup.
  • Names must be RFC 3261 tokens of at most 64 characters. Values must be strings without control characters. Violations are compile errors: COMPLETE_SIP_HEADERS_INVALID_NAME, COMPLETE_SIP_HEADERS_INVALID_VALUE, and COMPLETE_SIP_HEADERS_WRONG_TYPE. Quote a value that starts with {{.
  • The platform adds its own correlation headers to the BYE: x-session-id, x-project-id, x-tenant-id, and the call metadata x-caller-number and x-call-start-time (the same set a transfer sends). You cannot set or override these names. X-RouteType is not an ABL or platform header. It is an example of a header that a contact center such as Five9 reads, and hangup is the value that example uses. The set of values is defined by the system that receives the BYE, so use the names and values from its routing documentation. The runtime sends any valid string value as written.
  • {{variable}} placeholders resolve from session values when the condition fires. A header whose value resolves empty is not sent.
  • The headers pass the project’s outbound SIP header policy. Its block list and headerPassthrough: false apply. The runtime drops authored headers that use reserved platform names (x-session-id, x-tenant-id, and similar), and the count and size limits hold. These headers need no allow-list row, and the project’s transfer allow rows are never added to a hangup.
  • The platform always sets X-ArtemisReason: BotHangup and you cannot override it.
  • The headers apply to pipeline and realtime voice calls that this condition ends.
  • sip_headers anywhere other than a COMPLETE condition’s VOICE block is ignored with a warning (VOICE_SIP_HEADERS_ILLEGAL_PLACEMENT). Transfer headers belong in ESCALATE.routing.voice.sip_headers.

Completion evaluation

Completion conditions are evaluated after every turn, in declaration order. The first matching condition triggers completion. If no condition matches, the agent continues the conversation.

Context passing

Evaluation order across constructs

When multiple multi-agent constructs apply on the same turn, the runtime evaluates them in this order:
  1. ESCALATE triggers — checked first; critical safety and compliance.
  2. HANDOFF rules — evaluated by priority (lower first).
  3. DELEGATE conditions — evaluated in declaration order.
  4. COMPLETE conditions — checked last.

Complete supervisor example


Related articles: