Handlers
Handlers define what happens when a trigger fires. They contain execution blocks that run in sequence.
Handler Structure
handlers:
trigger-name:
Block Name:
block: block-kind
# block-specific properties
Another Block:
block: another-kind
# ...Each block has a human-readable name (shown in debug UI) and a block field that determines its behavior.
Block Kinds
next-message
Generate a response from the LLM:
handlers:
user-message:
Respond to user:
block: next-message
# Uses main conversation thread by default
# Display defaults to 'stream'With options:
Generate summary:
block: next-message
thread: summary # Use named thread
display: stream # Show streaming content
independent: true # Don't add to main chat
output: SUMMARY # Store output in variable
description: Generating summary # Shown in UIFor structured output (typed JSON response):
Respond with suggestions:
block: next-message
responseType: ChatResponse # Type defined in types section
output: RESPONSE # Stores the parsed objectWhen responseType is specified:
- The LLM generates JSON matching the type schema
- The
outputvariable receives the parsed object (not plain text) - The client receives a
UIObjectPartfor custom rendering
See Types for more details.
add-message
Add a message to the conversation:
Add user message:
block: add-message
role: user # user | assistant | system
prompt: user-message # Reference to prompt file
input: [USER_MESSAGE] # Variables to interpolate
display: hidden # Don't show in UIFor internal directives (LLM sees it, user doesn't):
Add internal directive:
block: add-message
role: user
prompt: ticket-directive
input: [TICKET_DETAILS]
visible: false # LLM sees this, user doesn'tFor structured user input (object shown in UI, prompt for LLM context):
Add user message:
block: add-message
role: user
prompt: user-message # Rendered for LLM context (hidden from UI)
input: [USER_INPUT]
uiContent: USER_INPUT # Variable shown in UI (object → object part)
display: hiddenWhen uiContent is set:
- The variable value is shown in the UI (string → text part, object → object part)
- The prompt text is hidden from the UI but kept for LLM context
- Useful for rich UI interactions where the visual differs from the LLM context
tool-call
Call a tool deterministically:
Create ticket:
block: tool-call
tool: create-support-ticket
input:
summary: SUMMARY # Variable reference
priority: medium # Literal value
output: TICKET # Store resultset-resource
Deprecated: Resources are superseded by tools. Persist state with a tool call to a consumer-defined tool instead. Still executed for now, but protocol validation emits a deprecation warning.
Update a persistent resource:
Save summary:
block: set-resource
resource: CONVERSATION_SUMMARY
value: SUMMARY # Variable to save
display: name # Show block namestart-thread
Create a named conversation thread:
Start summary thread:
block: start-thread
thread: summary # Thread name
model: anthropic/claude-sonnet-4-5 # Optional: different model
backupModel: openai/gpt-4o # Failover on provider errors
thinking: low # Extended reasoning level
cache: auto # auto (default) | extended | off
maxSteps: 1 # Tool call limit
system: escalation-summary # System prompt
input: [COMPANY_NAME] # Variables for prompt
mcpServers: [figma, browser] # MCP servers for this thread
skills: [qr-code] # Octavus skills for this thread
sandboxTimeout: 600000 # Skill sandbox timeout (default: 5 min, max: 1 hour)
imageModel: google/gemini-2.5-flash-image # Image generation modelThe cache field controls prompt caching for this thread and defaults to auto when omitted. Threads do not inherit the agent's cache value - see Prompt Caching.
The model field can also reference a variable for dynamic model selection. The backupModel, temperature, thinking, and maxSteps fields also support variable references - see Dynamic Configuration.
Start summary thread:
block: start-thread
thread: summary
model: SUMMARY_MODEL # Resolved from input variable
system: escalation-summaryserialize-thread
Convert conversation to text:
Serialize conversation:
block: serialize-thread
thread: main # Which thread (default: main)
format: markdown # markdown | json
output: CONVERSATION_TEXT # Variable to store resultgenerate-image
Generate an image from a prompt variable:
Generate image:
block: generate-image
prompt: OPTIMIZED_PROMPT # Variable containing the prompt
imageModel: google/gemini-2.5-flash-image # Required image model
aspectRatio: 16:9 # Aspect ratio (default 1:1)
output: GENERATED_IMAGE # Store URL in variable
description: Generating your image... # Shown in UIEdit an existing image using reference images:
Edit image:
block: generate-image
prompt: EDIT_INSTRUCTIONS # e.g., "Remove the background"
referenceImages: [SOURCE_IMAGE_URL] # Variable(s) containing image URLs
imageModel: google/gemini-2.5-flash-image
output: EDITED_IMAGE
description: Editing image...| Field | Required | Description |
|---|---|---|
prompt | Yes | Variable name containing the image prompt or edit instructions |
imageModel | Yes | Image model identifier (e.g., google/gemini-2.5-flash-image) |
aspectRatio | No | Aspect ratio (default 1:1); clamped to the model's supported set. See Aspect Ratios and Resolution |
resolution | No | Output resolution (1K, 2K, 4K) - Gemini 3 image models only; ignored elsewhere |
size | No | Deprecated alias for aspectRatio (1024x1024→1:1, 1792x1024→16:9, 1024x1792→9:16) |
referenceImages | No | Variable names containing image URLs for editing/transformation |
output | No | Variable name to store the generated image URL |
thread | No | Thread to associate the output file with |
description | No | Description shown in the UI during generation |
This block is for deterministic image generation pipelines where the prompt is constructed programmatically (e.g., via prompt engineering in a separate thread). When referenceImages are provided, the prompt describes how to modify those images.
For agentic image generation where the LLM decides when to generate, configure imageModel in the agent config.
generate-speech
Generate spoken audio from a text variable:
Read aloud:
block: generate-speech
text: ARTICLE_TEXT # Variable containing the text to speak
speechModel: openai/gpt-4o-mini-tts # Required speech model
voice: marin # Optional voice id
format: mp3 # Optional output format (default mp3)
output: NARRATION_URL # Store the audio file URL in a variable
description: Generating audio... # Shown in UI| Field | Required | Description |
|---|---|---|
text | Yes | Variable name containing the text to convert to speech |
speechModel | Yes | Speech model identifier (e.g., openai/gpt-4o-mini-tts) |
voice | No | Voice id to speak with (provider-specific) |
format | No | Output audio format (mp3, opus, aac, flac, wav; default mp3) |
instructions | No | Optional delivery instructions (model-dependent) |
language | No | Optional ISO 639-1 language hint (model-dependent) |
speed | No | Optional speech speed multiplier (0.25 - 4.0; model-dependent) |
output | No | Variable name to store the generated audio file URL |
thread | No | Thread to associate the output file with |
description | No | Description shown in the UI during generation |
For agentic speech generation where the LLM decides when to speak, configure speechModel in the agent config.
transcribe-audio
Transcribe an audio (or video) file referenced by a variable, storing the transcript text:
Transcribe recording:
block: transcribe-audio
audio: RECORDING_FILE # Variable holding a file reference or URL
transcriptionModel: openai/gpt-4o-transcribe # Required transcription model
timestamps: true # Optional timestamped segments
output: TRANSCRIPT # Store the transcript text in a variable
description: Transcribing... # Shown in UI| Field | Required | Description |
|---|---|---|
audio | Yes | Variable name holding the audio/video file (a file reference or URL) |
transcriptionModel | Yes | Transcription model identifier (e.g., openai/gpt-4o-transcribe) |
language | No | Optional ISO 639-1 language hint (default auto-detect) |
timestamps | No | Request timestamped segments where the model supports them |
output | No | Variable name to store the resulting transcript text |
thread | No | Thread to attribute the block's events to |
description | No | Description shown in the UI during transcription |
For agentic transcription where the LLM decides when to transcribe, configure transcriptionModel in the agent config.
Display Modes
Every block has a display property:
| Mode | Default For | Behavior |
|---|---|---|
hidden | add-message | Not shown to user |
name | set-resource | Shows block name |
description | tool-call, generate-image | Shows description |
stream | next-message | Streams content |
title | - | Shows the block's title field |
Complete Example
handlers:
user-message:
# Add the user's message to conversation
Add user message:
block: add-message
role: user
prompt: user-message
input: [USER_MESSAGE]
display: hidden
# Generate response (LLM may call tools)
Respond to user:
block: next-message
# display: stream (default)
request-human:
# Step 1: Serialize conversation for summary
Serialize conversation:
block: serialize-thread
format: markdown
output: CONVERSATION_TEXT
# Step 2: Create separate thread for summarization
Start summary thread:
block: start-thread
thread: summary
model: anthropic/claude-sonnet-4-5
thinking: low
system: escalation-summary
input: [COMPANY_NAME]
# Step 3: Add request to summary thread
Add summarize request:
block: add-message
thread: summary
role: user
prompt: summarize-request
input:
- CONVERSATION: CONVERSATION_TEXT
# Step 4: Generate summary
Generate summary:
block: next-message
thread: summary
display: stream
description: Summarizing your conversation
independent: true
output: SUMMARY
# Step 5: Save to resource
Save summary:
block: set-resource
resource: CONVERSATION_SUMMARY
value: SUMMARY
# Step 6: Create support ticket
Create ticket:
block: tool-call
tool: create-support-ticket
input:
summary: SUMMARY
priority: medium
output: TICKET
# Step 7: Add directive for response
Add directive:
block: add-message
role: user
prompt: ticket-directive
input: [TICKET_DETAILS: TICKET]
visible: false
# Step 8: Respond to user
Respond:
block: next-messageBlock Input Mapping
The input field on blocks controls which variables are passed to the prompt. Only variables listed in input are available for interpolation.
Variables can come from protocol.input, protocol.resources, protocol.variables, trigger.input, or outputs from prior blocks.
# Array format (same name)
input: [USER_MESSAGE, COMPANY_NAME]
# Array format (rename)
input:
- CONVERSATION: CONVERSATION_TEXT # Prompt sees CONVERSATION, value comes from CONVERSATION_TEXT
- TICKET_DETAILS: TICKET
# Object format (rename)
input:
CONVERSATION: CONVERSATION_TEXT
TICKET_DETAILS: TICKETIndependent Blocks
Use independent: true for content that shouldn't go to the main chat:
Generate summary:
block: next-message
thread: summary
independent: true # Output stored in variable, not main chat
output: SUMMARYThis is useful for:
- Background processing
- Summarization in separate threads
- Generating content for tools