Skip to main content

Handlers

Handlers define what happens when a trigger fires. They contain execution blocks that run in sequence.

Handler Structure

yaml
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:

yaml
handlers:
  user-message:
    Respond to user:
      block: next-message
      # Uses main conversation thread by default
      # Display defaults to 'stream'

With options:

yaml
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 UI

For structured output (typed JSON response):

yaml
Respond with suggestions:
  block: next-message
  responseType: ChatResponse # Type defined in types section
  output: RESPONSE # Stores the parsed object

When responseType is specified:

  • The LLM generates JSON matching the type schema
  • The output variable receives the parsed object (not plain text)
  • The client receives a UIObjectPart for custom rendering

See Types for more details.

add-message

Add a message to the conversation:

yaml
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 UI

For internal directives (LLM sees it, user doesn't):

yaml
Add internal directive:
  block: add-message
  role: user
  prompt: ticket-directive
  input: [TICKET_DETAILS]
  visible: false # LLM sees this, user doesn't

For structured user input (object shown in UI, prompt for LLM context):

yaml
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: hidden

When 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:

yaml
Create ticket:
  block: tool-call
  tool: create-support-ticket
  input:
    summary: SUMMARY # Variable reference
    priority: medium # Literal value
  output: TICKET # Store result

set-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:

yaml
Save summary:
  block: set-resource
  resource: CONVERSATION_SUMMARY
  value: SUMMARY # Variable to save
  display: name # Show block name

start-thread

Create a named conversation thread:

yaml
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 model

The 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.

yaml
Start summary thread:
  block: start-thread
  thread: summary
  model: SUMMARY_MODEL # Resolved from input variable
  system: escalation-summary

serialize-thread

Convert conversation to text:

yaml
Serialize conversation:
  block: serialize-thread
  thread: main # Which thread (default: main)
  format: markdown # markdown | json
  output: CONVERSATION_TEXT # Variable to store result

generate-image

Generate an image from a prompt variable:

yaml
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 UI

Edit an existing image using reference images:

yaml
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...
FieldRequiredDescription
promptYesVariable name containing the image prompt or edit instructions
imageModelYesImage model identifier (e.g., google/gemini-2.5-flash-image)
aspectRatioNoAspect ratio (default 1:1); clamped to the model's supported set. See Aspect Ratios and Resolution
resolutionNoOutput resolution (1K, 2K, 4K) - Gemini 3 image models only; ignored elsewhere
sizeNoDeprecated alias for aspectRatio (1024x10241:1, 1792x102416:9, 1024x17929:16)
referenceImagesNoVariable names containing image URLs for editing/transformation
outputNoVariable name to store the generated image URL
threadNoThread to associate the output file with
descriptionNoDescription 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:

yaml
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
FieldRequiredDescription
textYesVariable name containing the text to convert to speech
speechModelYesSpeech model identifier (e.g., openai/gpt-4o-mini-tts)
voiceNoVoice id to speak with (provider-specific)
formatNoOutput audio format (mp3, opus, aac, flac, wav; default mp3)
instructionsNoOptional delivery instructions (model-dependent)
languageNoOptional ISO 639-1 language hint (model-dependent)
speedNoOptional speech speed multiplier (0.25 - 4.0; model-dependent)
outputNoVariable name to store the generated audio file URL
threadNoThread to associate the output file with
descriptionNoDescription 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:

yaml
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
FieldRequiredDescription
audioYesVariable name holding the audio/video file (a file reference or URL)
transcriptionModelYesTranscription model identifier (e.g., openai/gpt-4o-transcribe)
languageNoOptional ISO 639-1 language hint (default auto-detect)
timestampsNoRequest timestamped segments where the model supports them
outputNoVariable name to store the resulting transcript text
threadNoThread to attribute the block's events to
descriptionNoDescription 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:

ModeDefault ForBehavior
hiddenadd-messageNot shown to user
nameset-resourceShows block name
descriptiontool-call, generate-imageShows description
streamnext-messageStreams content
title-Shows the block's title field

Complete Example

yaml
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-message

Block 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.

yaml
# 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: TICKET

Independent Blocks

Use independent: true for content that shouldn't go to the main chat:

yaml
Generate summary:
  block: next-message
  thread: summary
  independent: true # Output stored in variable, not main chat
  output: SUMMARY

This is useful for:

  • Background processing
  • Summarization in separate threads
  • Generating content for tools