Skip to main content

Workers

Workers are agents designed for task-based execution. Unlike interactive agents that handle multi-turn conversations, workers execute a sequence of steps and return an output value.

When to Use Workers

Workers are ideal for:

  • Background processing - Long-running tasks that don't need conversation
  • Composable tasks - Reusable units of work called by other agents
  • Pipelines - Multi-step processing with structured output
  • Parallel execution - Tasks that can run independently

Use interactive agents instead when:

  • Conversation is needed - Multi-turn dialogue with users
  • Persistence matters - State should survive across interactions
  • Session context - User context needs to persist

Worker vs Interactive

AspectInteractiveWorker
Structuretriggers + handlers + agentsteps + output
LLM ConfigGlobal agent: sectionPer-thread via start-thread
InvocationFire a named triggerDirect execution with input
SessionPersists across triggers (24h TTL)Single execution
ResultStreaming chatStreaming + output value

Protocol Structure

Workers use a simpler protocol structure than interactive agents:

yaml
# Input schema - provided when worker is executed
input:
  TOPIC:
    type: string
    description: Topic to research
  DEPTH:
    type: string
    optional: true
    default: medium

# Variables for intermediate results
variables:
  RESEARCH_DATA:
    type: string
  ANALYSIS:
    type: string
    description: Final analysis result

# Tools available to the worker
tools:
  web-search:
    description: Search the web
    parameters:
      query: { type: string }

# Sequential execution steps
steps:
  Start research:
    block: start-thread
    thread: research
    model: anthropic/claude-sonnet-4-5
    system: research-system
    input: [TOPIC, DEPTH]
    tools: [web-search]
    maxSteps: 5

  Add research request:
    block: add-message
    thread: research
    role: user
    prompt: research-prompt
    input: [TOPIC, DEPTH]

  Generate research:
    block: next-message
    thread: research
    output: RESEARCH_DATA

  Start analysis:
    block: start-thread
    thread: analysis
    model: anthropic/claude-sonnet-4-5
    system: analysis-system

  Add analysis request:
    block: add-message
    thread: analysis
    role: user
    prompt: analysis-prompt
    input: [RESEARCH_DATA]

  Generate analysis:
    block: next-message
    thread: analysis
    output: ANALYSIS

# Output variable - the worker's return value
output: ANALYSIS

settings.json

Workers are identified by the format field:

json
{
  "slug": "research-assistant",
  "name": "Research Assistant",
  "description": "Researches topics and returns structured analysis",
  "format": "worker"
}

Key Differences

No Global Agent Config

Interactive agents have a global agent: section that configures a main thread. Workers don't have this - every thread must be explicitly created via start-thread:

yaml
# Interactive agent: Global config
agent:
  model: anthropic/claude-sonnet-4-5
  system: system
  tools: [tool-a, tool-b]

# Worker: Each thread configured independently
steps:
  Start thread A:
    block: start-thread
    thread: research
    model: anthropic/claude-sonnet-4-5
    tools: [tool-a]

  Start thread B:
    block: start-thread
    thread: analysis
    model: openai/gpt-4o
    tools: [tool-b]

This gives workers flexibility to use different models, tools, skills, and settings at different stages.

Steps Instead of Handlers

Workers use steps: instead of handlers:. Steps execute sequentially, like handler blocks:

yaml
# Interactive: Handlers respond to triggers
handlers:
  user-message:
    Add message:
      block: add-message
      # ...

# Worker: Steps execute in sequence
steps:
  Add message:
    block: add-message
    # ...

Output Value

Workers can return an output value to the caller:

yaml
variables:
  RESULT:
    type: string

steps:
  # ... steps that populate RESULT ...

output: RESULT # Return this variable's value

The output field references a variable declared in variables:. If omitted, the worker completes without returning a value.

Available Blocks

Workers support the same blocks as handlers:

BlockPurpose
start-threadCreate a named thread with LLM configuration
add-messageAdd a message to a thread
next-messageGenerate LLM response
tool-callCall a tool deterministically
set-resourceUpdate a resource value
serialize-threadConvert thread to text
generate-imageGenerate an image from a prompt variable

start-thread (Required for LLM)

Every thread must be initialized with start-thread before using next-message:

yaml
steps:
  Start research:
    block: start-thread
    thread: research
    model: anthropic/claude-sonnet-4-5
    system: research-system
    input: [TOPIC]
    tools: [web-search]
    thinking: medium
    maxSteps: 5

All LLM configuration goes here:

FieldDescription
threadThread name (defaults to block name)
modelLLM model to use
systemSystem prompt filename (required)
inputVariables for system prompt
toolsTools available in this thread
skillsOctavus skills available in this thread
mcpServersMCP servers available in this thread
imageModelImage generation model
webSearchEnable built-in web search tool
thinkingExtended reasoning level (low/medium/high/max), "off", or variable reference
cachePrompt caching mode: auto (default), extended, or off
temperatureModel temperature (0-2), "off", or variable reference
maxStepsMaximum tool call cycles (enables agentic if > 1), or variable reference
maxToolOutputTokensCap a single tool result at this many tokens in the thread's model view (head+tail preview + note). Omit to leave tool output unbounded
maxImageDimensionCap the longest side (px) of any image in the thread's model view; over-cap images are delivered downscaled to fit. Omit for full resolution (see Image Delivery Limits)
maxOutputTokensCap output tokens for a single generation in this thread (per step, not a whole-thread budget). Omit to use the provider/SDK default
loopGuardtrue to abort a generation that degenerates into a repeated string (see Output Limits and Loop Guard)

Simple Example

A worker that generates a title from a summary:

yaml
# Input
input:
  CONVERSATION_SUMMARY:
    type: string
    description: Summary to generate a title for

# Variables
variables:
  TITLE:
    type: string
    description: The generated title

# Steps
steps:
  Start title thread:
    block: start-thread
    thread: title-gen
    model: anthropic/claude-sonnet-4-5
    system: title-system

  Add title request:
    block: add-message
    thread: title-gen
    role: user
    prompt: title-request
    input: [CONVERSATION_SUMMARY]

  Generate title:
    block: next-message
    thread: title-gen
    output: TITLE
    display: stream

# Output
output: TITLE

Advanced Example

A worker with multiple threads, tools, and agentic behavior:

yaml
input:
  USER_MESSAGE:
    type: string
    description: The user's message to respond to
  USER_ID:
    type: string
    description: User ID for account lookups
    optional: true

tools:
  get-user-account:
    description: Looking up account information
    parameters:
      userId: { type: string }
  create-support-ticket:
    description: Creating a support ticket
    parameters:
      summary: { type: string }
      priority: { type: string }

variables:
  ASSISTANT_RESPONSE:
    type: string
  CHAT_TRANSCRIPT:
    type: string
  CONVERSATION_SUMMARY:
    type: string

steps:
  # Thread 1: Chat with agentic tool calling
  Start chat thread:
    block: start-thread
    thread: chat
    model: anthropic/claude-sonnet-4-5
    system: chat-system
    input: [USER_ID]
    tools: [get-user-account, create-support-ticket]
    thinking: medium
    maxSteps: 5

  Add user message:
    block: add-message
    thread: chat
    role: user
    prompt: user-message
    input: [USER_MESSAGE]

  Generate response:
    block: next-message
    thread: chat
    output: ASSISTANT_RESPONSE
    display: stream

  # Serialize for summary
  Save conversation:
    block: serialize-thread
    thread: chat
    output: CHAT_TRANSCRIPT

  # Thread 2: Summary generation
  Start summary thread:
    block: start-thread
    thread: summary
    model: anthropic/claude-sonnet-4-5
    system: summary-system
    thinking: low

  Add summary request:
    block: add-message
    thread: summary
    role: user
    prompt: summary-request
    input: [CHAT_TRANSCRIPT]

  Generate summary:
    block: next-message
    thread: summary
    output: CONVERSATION_SUMMARY
    display: stream

output: CONVERSATION_SUMMARY

MCP Servers

Workers can declare and use MCP servers, just like interactive agents. Define them in mcpServers: and reference them in start-thread:

yaml
mcpServers:
  sentry:
    description: Error tracking and debugging
    source: remote
    display: name
  browser:
    description: Chrome DevTools browser automation
    source: device
    display: name

steps:
  Start research:
    block: start-thread
    thread: research
    model: anthropic/claude-sonnet-4-5
    system: system
    mcpServers: [sentry, browser]
    maxSteps: 10

Workers resolve their own MCP connections independently - they don't inherit MCP servers from a parent interactive agent. Remote MCP connections are project-scoped, so a worker in the same project automatically has access to the same OAuth connections.

See MCP Servers for full documentation.

Workers can use Octavus skills, image generation, and web search, configured per-thread via start-thread:

yaml
skills:
  qr-code:
    display: description
    description: Generate QR codes

steps:
  Start thread:
    block: start-thread
    thread: worker
    model: anthropic/claude-sonnet-4-5
    system: system
    skills: [qr-code]
    imageModel: google/gemini-2.5-flash-image
    webSearch: true
    maxSteps: 10

Workers define their own skills independently - they don't inherit skills from a parent interactive agent. Each thread gets its own sandbox scoped to only its listed skills.

Skills with execution: device work the same way in workers as in interactive agents - the skill runs on the agent's computer. Workers resolve their device execution independently, so a worker can use device skills even if the parent agent does not.

See Skills for full documentation.

Tool Handling

Workers support the same tool handling as interactive agents:

  • Server tools - Handled by tool handlers you provide
  • Client tools - Pause execution, return tool request to caller
typescript
// Non-streaming: get the output directly
const { output } = await client.workers.generate(
  agentId,
  { TOPIC: 'AI safety' },
  {
    tools: {
      'web-search': async (args) => await searchWeb(args.query),
    },
  },
);

// Streaming: observe events in real-time
const events = client.workers.execute(
  agentId,
  { TOPIC: 'AI safety' },
  {
    tools: {
      'web-search': async (args) => await searchWeb(args.query),
    },
  },
);

See Server SDK Workers for tool handling details.

Stream Events

Workers emit the same events as interactive agents, plus worker-specific events:

EventDescription
worker-startWorker execution begins
worker-resultWorker completes (includes output)
usageCost and token summary (standalone executions only, after finish)

All standard events (text-delta, tool calls, etc.) are also emitted.

Calling Workers from Interactive Agents

Interactive agents can call workers in three ways:

  1. Deterministically - Using the run-worker block
  2. Agentically - LLM calls worker as a tool
  3. Automatically - Octavus invokes the worker as part of a built-in capability, not the model. Context management's summarizerWorker (see Context Management) works this way: declare it in workers: but leave it out of agent.workers so the model never sees it as a tool.

Worker Declaration

First, declare workers in your interactive agent's protocol:

yaml
workers:
  generate-title:
    description: Generating conversation title
    display: description
  research-assistant:
    description: Researching topic
    display: stream
    tools:
      search: web-search # Map worker tool → parent tool

run-worker Block

Call a worker deterministically from a handler:

yaml
handlers:
  request-human:
    Generate title:
      block: run-worker
      worker: generate-title
      input:
        CONVERSATION_SUMMARY: SUMMARY
      output: CONVERSATION_TITLE

LLM Tool Invocation

Make workers available to the LLM:

yaml
agent:
  model: anthropic/claude-sonnet-4-5
  system: system
  workers: [generate-title, research-assistant]
  agentic: true

The LLM can then call workers as tools during conversation.

Display Modes

Controls how worker execution appears to users. The default for workers is stream.

ModeBehavior
hiddenWorker runs silently. No events reach the client - no UIWorkerPart is created.
nameShows a running/done indicator with the worker name. No nested content (text, tool calls, reasoning) is forwarded.
descriptionShows a running/done indicator with the worker description. No nested content is forwarded.
streamFull visibility. All nested events are forwarded - text, reasoning, tool calls, sources, files. Worker input is included on start.
titleLike description, but shows the worker's title field instead of its description. No nested content or input is forwarded.

Progressive input streaming: When a worker with display: stream is invoked agentically (LLM calls it as a tool), the UIWorkerPart appears in the UI immediately as the LLM starts generating the worker's arguments. The worker input streams progressively into the worker part, the same way text tokens stream into a text part. Once input finishes, worker execution begins and nested content flows into the same worker part. There is no intermediate tool card.

name and description modes: Worker input is stripped from the worker-start event (it may contain sensitive data). Only the running/done status and the final worker-result are forwarded to the parent stream. Use these for workers where the user only needs to know the worker ran, not what it did internally.

hidden mode: The worker executes normally but produces no UI presence at all. Use for internal workers that are implementation details.

Tool Mapping

Map parent tools to worker tools when the worker needs access to your tool handlers:

yaml
workers:
  research-assistant:
    description: Research topics
    tools:
      search: web-search # Worker's "search" → parent's "web-search"

When the worker calls its search tool, your web-search handler executes.

Next Steps