Overview
Tool nodes let you integrate external services and custom Python code directly into your workflow — without needing to write an LLM prompt. Any node with type: tool is a tool node.
nodes:
fetch_context:
type: tool
tool: http
config:
url: "https://api.example.com/context"
method: GET
output_key: context_data
Two adapters are currently supported:
| Adapter | tool: value | Description |
|---|
| HTTP | http | Sends an HTTP GET or POST request and stores the response. |
| Python | python | Imports a Python module and calls a function with keyword arguments. |
Common fields
All tool nodes share these top-level fields.
| Field | Required | Default | Description |
|---|
type | Yes | — | Must be "tool" to identify this as a tool node. |
tool | Yes | — | Adapter to use: "http" or "python". |
config | Yes | — | Adapter-specific configuration (see below). |
output_key | No | "output" | Key under which the result is stored in the workflow context. |
retry | No | 0 | Number of extra attempts if the tool call fails. |
on_failure | No | "raise" | What to do after all retries fail: "raise" stops the workflow; "skip" stores null and continues. |
output_key and context storage
Tool output is stored under the node ID and its output_key. For example, a node named fetch_diff with output_key: diff exposes its result as {{ fetch_diff.diff }}. Downstream agent nodes reference this value in their system prompts using Jinja-style template syntax:
agents:
summarizer:
system: |
Summarize the following diff:
{{ fetch_diff.diff }}
If output_key is omitted it defaults to output, so the reference becomes {{ <node_id>.output }}.
Reference upstream nodes by their ID ({{ fetch_diff.diff }}), not via the
working namespace. The load-time linter rejects {{ working.<node_id>.* }}.
Reserve working.* for custom paths you write to or seed in state.
HTTP adapter
Use tool: http to call an HTTP endpoint.
nodes:
fetch_context:
type: tool
tool: http
config:
url: "https://api.github.com/repos/owner/repo/pulls/42"
method: GET
headers:
Authorization: "Bearer {{ env.GITHUB_TOKEN }}"
Accept: "application/vnd.github.v3.diff"
timeout: 15
output_key: diff
HTTP config fields
| Field | Required | Default | Description |
|---|
url | Yes | — | Full URL of the endpoint to call. Supports template placeholders. |
method | No | GET | HTTP method — GET or POST. |
headers | No | {} | Key/value map of request headers. Values support template placeholders. |
body | No | null | Request body string (typically JSON). Used for POST requests. |
timeout | No | 10 | Request timeout in seconds (minimum 1). |
Response handling
- If the response
Content-Type is application/json, the body is parsed and the result is a Python dict or list.
- Otherwise, the raw response text string is stored.
Error behaviour
Any non-2xx status code raises a ToolError with the HTTP status code and response body excerpt in the message. Network errors (DNS failures, connection refused) and timeouts also raise ToolError.
Python adapter
Use tool: python to call any importable Python function.
nodes:
run_parser:
type: tool
tool: python
config:
module: mypackage.tools
function: parse_diff
args:
raw: "{{ fetch_diff.diff }}"
output_key: parsed
Python config fields
| Field | Required | Default | Description |
|---|
module | Yes | — | Fully-qualified Python module path (e.g. mypackage.tools). |
function | Yes | — | Name of the callable on the module. |
args | No | {} | Keyword arguments passed to the function. Values support template placeholders. |
Module resolution
The module is resolved using importlib.import_module against the user’s runtime environment (sys.path), not against the sirenspec package. This means any module that is importable in the process where sirenspec runs can be used — your own application code, installed packages, or local scripts.
Synchronous functions are run in a thread-pool executor so they do not block the event loop. Async functions are awaited directly.
Error behaviour
ModuleNotFoundError → ToolError with message "Cannot import module '...'"
- Missing attribute →
ToolError with message "Module '...' has no attribute '...'"
- Exception during call →
ToolError with the original exception type and message included
Retry and failure handling
nodes:
flaky_api:
type: tool
tool: http
config:
url: "https://unstable.example.com/data"
timeout: 5
retry: 3 # try up to 4 times total
on_failure: skip # if all 4 attempts fail, store null and continue
retry: N — the tool is attempted N + 1 times total before giving up.
on_failure: raise (default) — propagates a ToolError and marks the workflow as "failed".
on_failure: skip — stores null under the output key and lets the workflow continue.
Full example: PR summarizer
This workflow fetches a GitHub pull-request diff over HTTP and then asks an LLM to summarise it.
version: "0.1"
agents:
summarizer:
model: "openai:gpt-4o-mini"
system: |
You are a senior engineer reviewing a GitHub pull request.
Summarise the diff below in under 200 words using markdown bullet points.
Focus on what changed, why it was likely changed, and any risks.
nodes:
fetch_diff:
type: tool
tool: http
config:
url: "https://api.github.com/repos/owner/repo/pulls/42"
method: GET
headers:
Authorization: "Bearer {{ env.GITHUB_TOKEN }}"
Accept: "application/vnd.github.v3.diff"
timeout: 15
output_key: diff
summarize:
agent: summarizer
writes: output.summary
edges:
- from: fetch_diff
to: summarize
Run it with:
export GITHUB_TOKEN=ghp_...
sirenspec run docs/cookbook/pr-summarizer/workflow.yaml --input "Summarise this PR."
A working copy of this example is available in the PR Summarizer cookbook recipe.
ToolError
All tool failures raise sirenspec.exceptions.ToolError (a subclass of SirenSpecError). The exception message always includes the adapter name and the upstream error:
[tool:http] HTTP 404 Not Found from https://api.example.com/missing: resource not found
[tool:python] Cannot import module 'mypackage.tools': No module named 'mypackage'
ToolError exposes three attributes:
| Attribute | Type | Description |
|---|
tool_name | str | The adapter that raised the error ("http" or "python"). |
cause | BaseException | None | The upstream exception, if any. |
status_code | int | None | HTTP status code when the failure originated from a non-2xx response (e.g. 404, 503). None for network-level errors (connection refused, DNS failure, timeout). Used by retry policies to match numeric triggers like on: ["429", "503"]. |
JSON Schema
The sirenspec.schema.json artifact validates tool nodes inline with agent nodes. IDEs that support JSON Schema (VS Code, JetBrains) will autocomplete type: tool, tool:, and all config fields automatically.