Handlers

Every task has a handler field that determines what it does. This page documents all available handlers and which fields are valid for each.

Handler types

HandlerWhat it does
chat_completionSend messages to an LLM, receive a text/tool-call reply
execute_tool_callsExecute the tool calls from the previous LLM reply
toolsCall a specific named tools tool directly (no LLM involved)
routeLLM picks exactly one of the declared branch labels; routing-only, input passes through unchanged
raise_errorImmediately halt the chain with an error message
noopPass input through unchanged

chat_completion

Sends the current input to the LLM and waits for a reply. If the model calls a tool, the transition evaluates to "tool-call".

Key fields:

FieldRequiredDescription
system_instructionNoSystem prompt (supports macros)
execute_config.modelYesModel name, e.g. qwen2.5:7b
execute_config.providerYesollama, openai, anthropic, mistral, vllm, gemini, bedrock, vertex-google
execute_config.toolsNoTools allowlist: []=none, ["*"]=all, ["a","b"]=named, ["*","!x"]=all-except. Absent=all (backward compat).
execute_config.hide_toolsNoTools to suppress from the model
execute_config.temperatureNoSampling temperature (0–1)
execute_config.thinkNoReasoning effort level. "low", "medium", "high", or "false". Supported by Ollama (v0.17.5+), Gemini 2.5+, vLLM, and OpenAI o-series models.
execute_config.max_tokensNoCap on the model's output tokens for this task. When unset, the engine falls back to the chain's token_limit so providers (notably Gemini thinking models) don't burn their entire output budget on hidden reasoning and emit empty content.
execute_config.shiftNoBoolean. If true, slides the context window by dropping old messages instead of erroring on token limits.
execute_config.truncateNoBoolean. If true, truncates the initial prompt instead of sliding the context window (Ollama-specific).
execute_config.modelsNoArray of fallback model IDs tried in order when the primary model is unavailable.
execute_config.providersNoArray of fallback provider types, paired index-for-index with models.

| execute_config.retry_policy | No | LLM-call retry and model-fallback settings — see retry_policy below. |

Transition values:

  • "tool-call" — model issued one or more tool calls
  • "stop" — model replied with text and stopped
  • "length" — reply was truncated at token limit

retry_policy

Controls automatic retries on transient LLM errors and optional model swapping after repeated failures.

FieldTypeDefaultDescription
max_attemptsint1Total attempts including the first (0 or 1 disables retry)
initial_backoffduration"500ms"Wait before the second attempt; doubled each retry
max_backoffdurationCap on exponential backoff
jitterfloat00–1 fraction of backoff added as random noise
rate_limit_min_waitdurationMinimum wait when the provider returns a rate-limit error
fallback_model_idstringAlternate model ID to switch to after fallback_after consecutive failures
fallback_afterintFailure count that triggers the model swap

Example:

{
  "id": "chat",
  "handler": "chat_completion",
  "system_instruction": "You are a helpful assistant. Today is <span v-pre>{{now:2006-01-02}}</span>.",
  "execute_config": {
    "model": "<span v-pre>{{var:model}}</span>",
    "provider": "<span v-pre>{{var:provider}}</span>",
    "tools": ["nws"]
  },
  "transition": {
    "branches": [
      { "operator": "equals", "when": "tool-call", "goto": "run_tools" },
      { "operator": "default", "when": "", "goto": "end" }
    ]
  }
}

execute_tool_calls

Executes the tool calls emitted by the previous chat_completion task, appends the results to the chat history, and loops back.

Key fields:

FieldRequiredDescription
input_varYesID of the chat_completion task whose output to use

Example:

{
  "id": "run_tools",
  "handler": "execute_tool_calls",
  "input_var": "chat",
  "transition": {
    "branches": [
      { "operator": "default", "when": "", "goto": "chat" }
    ]
  }
}

tools

Calls a specific tool on a named tool directly — no LLM involved. Use for deterministic side effects (e.g. writing a file, calling a fixed API endpoint).

Key fields:

FieldRequiredDescription
tools.nameYesRegistered tools name (e.g. local_shell)
tools.tool_nameYesTool/operation to call on that tool
tools.argsNoStatic arguments passed to the tool
output_templateNoGo text/template string rendered against the tools's JSON response. Variables are the response fields (e.g. {{.exit_code}}). Output stored as a string.

Example:

{
  "id": "write_file",
  "handler": "tools",
  "tools": {
    "name": "local_fs",
    "tool_name": "write_file",
    "args": { "path": "/tmp/output.txt" }
  }
}

route

Asks the LLM to classify the input into exactly one of the labels declared by this task's own equals transition branches, then routes to the matching task. The routes are the branch when values — there is no separate schema; the chain you can read is the route set.

route is routing-only: the task's input passes through to the next task unchanged. It produces a control-flow decision, never transformed data — so a router can never silently reshape what a downstream task sees. If the model's answer matches no declared label, the default branch is taken.

Key fields:

FieldRequiredDescription
system_instructionNoDescribes the classification; the engine appends the allowed labels
execute_config.model / providerYesModel to use
transition.branchesYesThe equals branches whose when values are the route labels; include a default

Template functions

The Go text/template fields — prompt_template and output_template — are rendered with the Sprig function library available in addition to the built-ins. This is what turns a structured task output into clean prompt input instead of Go's default struct formatting:

<span v-pre>{{ .scan_result | toJson }}</span>            serialize a JSON/struct value as clean JSON
<span v-pre>{{ .page_text | trunc 4000 }}</span>            cap an oversized tool output
<span v-pre>{{ (last .history.Messages).Content }}</span>   pull a single field out

Without it, injecting a non-string task output (a tool's JSON result, a chat history) into a template renders Go syntax like map[...] or {[{...}]} that the model cannot reliably parse — which is why transforming or evaluating tool output used to mean shelling out. toJson, trunc, and the rest of Sprig remove that step.


noop

Passes input through to the next task unchanged. Useful as an explicit routing node.


raise_error

Immediately halts the chain with the input string as the error message. Use as a terminal error branch — no transition needed.