Appearance
Actions & Effects
Actions are the primary mechanism for the AI to perform behaviors beyond generating text. Each stage defines a set of actions, and each action contains an ordered list of effects that execute when the action is triggered.
Action Structure
Each action in the actions map has a key (the action ID) and a value with these fields:
| Field | Description |
|---|---|
name | Display name |
condition | Optional JavaScript expression evaluated to determine if the action is active |
triggerOnUserInput | Whether this action can be triggered by user speech/text (default: true) |
triggerOnClientCommand | Whether this action can be triggered by a client command |
triggerOnTransformation | Whether this action runs after context transformation |
classificationTrigger | Descriptive label shown to the classifier LLM that tells it when this action should fire; the LLM matches user intent against this label and returns the action ID in its response |
overrideClassifierId | Use a specific classifier instead of the stage default |
parameters | Parameters extracted by the classifier when triggering |
effects | Ordered array of effects to execute |
examples | Example user phrases (included in classifier prompt) |
watchedVariables | Map of variable paths to trigger conditions (new, changed, removed, any) |
metadata | Arbitrary JSON |
Stage Lifecycle Actions
Stages support three reserved lifecycle actions with special names (prefixed with __):
__on_enter
Runs when the conversation enters this stage — either at the start of a conversation or via a go_to_stage effect. Executes before the enterBehavior (generate response or await input).
Restricted effects: cannot use end_conversation, abort_conversation, or go_to_stage. Calling goToStage() inside a script tool is also silently ignored.
__on_leave
Runs when the conversation is about to leave this stage (before loading the new stage). Useful for cleanup or persisting state.
Restricted effects: cannot use go_to_stage or generate_response. Calling goToStage() inside a script tool is also silently ignored.
__on_fallback
Runs when the classifier found no matching user-triggered action. Acts as the default behavior for unrecognized input. Has no effect restrictions.
Conversation Lifecycle Actions
In addition to stage-level lifecycle hooks, global actions with reserved IDs fire at conversation-level lifecycle events, independent of which stage is active. See Global Actions — Conversation Lifecycle Hooks for the full reference, restrictions, and use cases.
| Reserved Global Action ID | When it fires |
|---|---|
__conversation_start | Once, after the conversation and first stage are initialised |
__conversation_resume | When a previously-interrupted conversation is resumed |
__conversation_end | When the conversation is gracefully ended |
__conversation_abort | When the conversation is aborted (immediate stop) |
__conversation_failed | When the conversation encounters a fatal error |
There is also a moderation hook:
| Reserved Global Action ID | When it fires |
|---|---|
__moderation_blocked | User input is blocked by content moderation |
See Global Actions — Content Moderation Hook for details.
Trigger Modes
Actions can be triggered in multiple ways:
- User input (
triggerOnUserInput: true) — The classifier analyzes the user's text and matches it to the action'sclassificationTrigger. - Client command (
triggerOnClientCommand: true) — The client application sends arun_actionWebSocket command. - Transformation (
triggerOnTransformation: true) — A context transformer modifies stage variables, and the action'swatchedVariablesmatches the change.
Conditions
The condition field accepts a JavaScript expression that evaluates to a boolean. If the condition evaluates to false, the action is excluded from the classifier's consideration set. Variables are accessible in conditions:
javascript
vars.retryCount < 3 && userProfile.tier === 'premium'Action Parameters
Actions can define parameters that the classifier extracts from user input:
json
"parameters": [
{
"name": "productName",
"type": "string",
"description": "The name of the product the user is asking about",
"required": true
},
{
"name": "quantity",
"type": "number",
"description": "How many units",
"required": false
}
]Extracted parameters are available in effects via context.results.actions.<actionId>.<paramName>.
Effects
Effects are the building blocks of action behavior. They execute in order within an action.
end_conversation
Gracefully ends the conversation. Optionally generates a final AI response.
json
{ "type": "end_conversation", "reason": "User's issue has been resolved" }abort_conversation
Immediately ends the conversation without generating any AI response.
json
{ "type": "abort_conversation", "reason": "Session timeout" }go_to_stage
Navigates to a different stage. Triggers __on_leave on the current stage and __on_enter on the target stage.
json
{ "type": "go_to_stage", "stageId": "troubleshooting" }modify_user_input
Replaces the user's input text using a Handlebars template. This modifies what the LLM sees as the user's message.
json
{
"type": "modify_user_input",
"template": "The user wants to know about {{vars.currentTopic}}: {{userInput}}"
}modify_variables
Performs operations on stage variables:
json
{
"type": "modify_variables",
"modifications": [
{ "variableName": "status", "operation": "set", "value": "verified" },
{ "variableName": "retryCount", "operation": "reset" },
{ "variableName": "history", "operation": "add", "value": "step completed" },
{ "variableName": "pendingItems", "operation": "remove", "value": "item-1" }
]
}Operations:
set— Set a variable to a valuereset— Clear a variableadd— Append a value to an arrayremove— Remove a value from an array
modify_user_profile
Same operations as modify_variables, but applied to the user's profile instead.
json
{
"type": "modify_user_profile",
"modifications": [
{ "fieldName": "preferredLanguage", "operation": "set", "value": "es" }
]
}call_tool
Invokes a tool. The tool's type determines both its execution behaviour and when it runs relative to other effects (see Effect Execution Priority). See Tools.
json
{
"type": "call_tool",
"toolId": "sentiment-analyzer",
"parameters": { "text": "{{userInput}}" }
}Results are stored differently depending on the tool type:
smart_functionandscripttools — stored undercontext.results.tools.<toolId>webhooktools — stored undercontext.results.webhooks.<toolId>
generate_response
Explicitly triggers AI response generation. Two modes:
Generated (LLM produces the response):
json
{ "type": "generate_response", "responseMode": "generated" }Prescripted (predefined text, no LLM call):
json
{
"type": "generate_response",
"responseMode": "prescripted",
"prescriptedResponses": ["Welcome! How can I help?", "Hi there! What can I do for you?"],
"prescriptedSelectionStrategy": "random"
}Selection strategies: random (pick randomly) or round_robin (cycle through).
change_visibility
Sets the visibility of the current turn's messages (both the user input and the AI response). This controls whether those messages are included when building the conversation history sent to the LLM, templates and scripts on future turns.
json
{
"type": "change_visibility",
"target": "action",
"id": "collect-payment-info",
"visibility": "never"
}| Field | Description |
|---|---|
target | "action" or "stage" — what the id refers to |
id | ID of the action or stage this effect belongs to |
visibility | "always", "stage", "never", or "conditional" (see Message Visibility) |
condition | JavaScript expression evaluated against the conversation context — required when visibility is "conditional" |
The visibility is recorded on the message event and evaluated when building history on subsequent turns. See Message Visibility for how each value is interpreted.
Effect Execution Priority
Effects from all triggered actions are gathered into a single global list, sorted by priority, and then conflict-resolved before execution. Effects within the same priority tier run in the order they appeared across all actions.
| Priority | Effect type |
|---|---|
| 1 | call_tool (webhook tools) |
| 2 | call_tool (smart_function tools) |
| 3 | modify_variables |
| 4 | modify_user_profile |
| 5 | modify_user_input |
| 6 | call_tool (script tools) |
| 50 | change_visibility |
| 100 | generate_response |
| 200 | end_conversation |
| 201 | abort_conversation |
| 202 | go_to_stage |
call_tool effects are assigned a priority at runtime based on the referenced tool's type: webhook tools run at priority 1, script tools at priority 6, and smart_function tools at priority 2.
Conflict Resolution
- Multiple
go_to_stage— only the first one (lowest priority index) is kept; the rest are discarded abort_conversation+end_conversation—abort_conversationwins;end_conversationis removed- Multiple
modify_user_input— all are applied in sequence, each receiving the output of the previous - Multiple
change_visibility— the last one applied wins (highest priority index)
Execution Flow
When a user sends input, the system runs classifiers and context transformers in parallel, merges results, and executes effects:
Effects within a single action run in order, and their results can be used by subsequent effects. If any effect triggers end_conversation, abort_conversation, or go_to_stage, it takes effect after all current effects complete.
Message Visibility
Every message event (user input and AI response) can carry a visibility setting that controls whether it appears in the conversation history sent to the LLM on future turns. Visibility is set by the change_visibility effect and evaluated each time history is built.
| Value | Behaviour |
|---|---|
always | Always included in history (default when no visibility is set) |
never | Never included in history |
stage | Included only when the current stage matches the stage the message was recorded in |
conditional | Included only when a JavaScript condition expression evaluates to truthy |
The conditional value supports an arbitrary JavaScript expression evaluated against the full conversation context. It can be set via the change_visibility effect (using the condition field) or directly on the message event data:
json
{
"type": "change_visibility",
"target": "action",
"id": "my-action",
"visibility": "conditional",
"condition": "vars.includeHistory === true"
}Visibility is evaluated lazily: it is recorded on the message event when the turn completes and re-evaluated on every subsequent turn when history is assembled. This means a stage or conditional message can move in and out of the visible history as the conversation progresses.