Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.sirenspec.dev/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The swrm node type lets you fan out to a static, author-defined set of agents that all run concurrently against the same workflow input. After every agent completes, an optional synthesis step combines their outputs into a single response. This pattern is sometimes called a committee of experts: each agent contributes a specialist perspective, and a final model synthesises them into a coherent answer.
nodes:
  analyze:
    type: swrm
    concurrency: 3
    agents:
      - id: sentiment
        provider: openai
        model: gpt-4o-mini
        prompt: "Analyze market sentiment in: {{ inputs.message }}"
      - id: risk
        provider: anthropic
        model: claude-haiku-4-5-20251001
        prompt: "Identify top risks in: {{ inputs.message }}"
      - id: opportunity
        provider: openai
        model: gpt-4o-mini
        prompt: "Find the top 3 opportunities in: {{ inputs.message }}"
    synthesis:
      provider: anthropic
      model: claude-haiku-4-5-20251001
      prompt: |
        Sentiment: {{ analyze.agents.sentiment.output }}
        Risk:      {{ analyze.agents.risk.output }}
        Opportunity: {{ analyze.agents.opportunity.output }}
        Produce a 3-paragraph investment recommendation.

Node Fields

FieldRequiredTypeDefaultDescription
typeYes"swrm"Node type discriminator. Must be "swrm".
agentsYeslist of agent objectsOrdered list of agents to run in parallel. At least one required.
concurrencyNointegerall agentsMaximum number of agents to run at the same time.
on_failureNo"abort" | "continue""abort"Failure policy when an agent raises an error.
synthesisNosynthesis objectnoneStep that runs after all agents complete and produces the node’s canonical output.

Agents

Each entry in the agents list is a self-contained LLM call with its own provider, model, and prompt template.
agents:
  - id: sentiment          # unique within this swrm node
    provider: openai       # "openai", "anthropic", or "ollama"
    model: gpt-4o-mini     # optional; provider default used if omitted
    prompt: |
      Analyze the market sentiment in this report:
      {{ inputs.message }}
    guardrails:            # optional; overrides workflow-level guardrails
      - injection

Agent Fields

FieldRequiredTypeDescription
idYesstringUnique identifier within the swrm node. Used in template interpolation.
providerYesstringLLM provider: openai, anthropic, or ollama.
modelNostringModel name (e.g. gpt-4o-mini). Provider default used if omitted.
promptYesstringPrompt template. Supports {{ variable }} interpolation.
guardrailsNolist of stringsAgent-level guardrail override.

Template Interpolation

Prompts in agents and the synthesis block support {{ variable }} placeholders. The template namespace includes:
PathDescription
{{ inputs.message }}The raw user input string passed to the workflow.
{{ working.* }}Any value written to the working context by a prior node.
{{ output.* }}Any value written to the output context by a prior node.
{{ <node_id>.agents.<agent_id>.output }}Output of a specific agent (available in synthesis only).

Example

synthesis:
  provider: anthropic
  model: claude-haiku-4-5-20251001
  prompt: |
    Sentiment analysis: {{ analyze.agents.sentiment.output }}
    Risk analysis:      {{ analyze.agents.risk.output }}
    Synthesise a recommendation.

Synthesis

The optional synthesis block is a single LLM call that runs after all agents have completed. Its prompt can reference every agent’s output. The synthesis output becomes the node’s canonical output.
synthesis:
  provider: anthropic          # required
  model: claude-haiku-4-5-20251001  # optional
  prompt: |
    ...
  guardrails:                  # optional
    - injection

Synthesis Fields

FieldRequiredTypeDescription
providerYesstringLLM provider.
modelNostringModel name.
promptYesstringPrompt template. May reference agent outputs.
guardrailsNolist of stringsSynthesis-level guardrail override.

Output Shape

After a swrm node executes, its results are written to the workflow context:
Context pathValue
output.<node_id>Synthesis text (if synthesis defined); otherwise a list of agent outputs in definition order.
working.<node_id>.agents.<agent_id>.outputIndividual agent output text.

Accessing Results

In downstream nodes or edges, reference swrm outputs like any other context value:
# In a downstream agent's writes: path or edge when: expression
edges:
  - from: analyze
    to: report
    when: output.analyze != null

Concurrency

By default, all agents run in parallel. Set concurrency to limit the number of agents executing simultaneously:
nodes:
  analyze:
    type: swrm
    concurrency: 2    # run at most 2 agents at a time; 3rd waits for a slot
    agents:
      - id: a
        ...
      - id: b
        ...
      - id: c
        ...
This is useful when you are rate-limited by the provider or want to control cost.

Failure Policy

Control what happens when one or more agents raise an error using on_failure:
ValueBehaviour
abort (default)Raises SwrmAgentError with the agent’s id and underlying exception. The swrm node fails and execution stops.
continueRecords the error in the trace but continues. Failed agents contribute an empty string to the output list.
nodes:
  analyze:
    type: swrm
    on_failure: continue   # tolerate individual agent failures
    agents:
      ...

SwrmAgentError

When on_failure: abort (the default), any agent failure raises SwrmAgentError:
from sirenspec import SwrmAgentError

try:
    trace = await execute(workflow, user_input)
except SwrmAgentError as exc:
    print(f"Agent '{exc.agent_id}' failed: {exc.cause}")

Committee-of-Experts Pattern

The committee-of-experts pattern decomposes a complex analytical task into specialist sub-tasks and then synthesises the results:
           ┌──────────────────────────────┐
           │          swrm node           │
           │                              │
input ─────┤  ┌─────────┐ ┌─────────┐   │
           │  │sentiment│ │  risk   │   │──── synthesis ──► output
           │  └─────────┘ └─────────┘   │
           │  ┌──────────────────────┐   │
           │  │     opportunity      │   │
           │  └──────────────────────┘   │
           └──────────────────────────────┘
When to use swrm:
  • Analysing a document from multiple angles (sentiment + risk + opportunity)
  • Generating multiple candidate responses and picking the best
  • Parallelising independent LLM calls to reduce total latency
  • Building a reviewer panel where each agent judges a different criterion
When NOT to use swrm:
  • Tasks where agents need to share intermediate state (use sequential nodes with edges)
  • Dynamic agent sets determined at runtime (see factory: node type)

Complete Example

See examples/market-analysis-swrm.yaml for a full working workflow.
sirenspec run examples/market-analysis-swrm.yaml \
  --input "Q3 earnings exceeded expectations, but macro headwinds persist."
The trace will include a nodes entry for the swrm node with:
  • agents — per-agent prompt, response, token count, and latency
  • synthesis — synthesis prompt, response, token count, and latency
  • output — the synthesis text (or list of agent outputs if no synthesis)
  • tokens — total token count across all agents and synthesis
  • duration_ms — total elapsed time

Trace Structure

A swrm node produces the following trace entry:
{
  "id": "analyze",
  "type": "swrm",
  "agents": [
    {
      "id": "sentiment",
      "prompt_sent": "Analyze market sentiment in: ...",
      "response_received": "The market sentiment is cautiously optimistic...",
      "tokens": 142,
      "duration_ms": 823.4,
      "error": null
    },
    {
      "id": "risk",
      "prompt_sent": "Identify top risks in: ...",
      "response_received": "1. Rising interest rates...",
      "tokens": 218,
      "duration_ms": 1102.7,
      "error": null
    }
  ],
  "synthesis": {
    "prompt_sent": "Sentiment: The market sentiment is cautiously optimistic...",
    "response_received": "Based on the three analyses...",
    "tokens": 305,
    "duration_ms": 1450.2,
    "error": null
  },
  "output": "Based on the three analyses...",
  "tokens": 665,
  "duration_ms": 1450.2,
  "error": null
}