Skip to content

Swarm

A swarm is a peer-to-peer task pool. Agents pull tasks off a shared queue, run them, and write what they find to a shared context every peer reads. Nobody is in charge.

A swarm run Tasks enter one shared task queue, kept in priority order: tasks you seed with add_task, and the initial task passed to execute, which the swarm's model can also split into sub-tasks that are queued too. Three peer agents, Scout, Analyst and Writer, each claim the first pending task their capabilities match, work it, and claim again. Every agent reads a summary of the SharedContext in its prompt and writes its findings, blackboard notes and task results back to it. When no task is pending the run returns a SwarmResult with the completed and failed tasks and a summary written from the shared context. Agents add findings, never tasks, and the run also stops after max_iterations rounds. sub-tasks no task pending claims by capability reads summary · writes findings summarized Seeded tasks add_task() Initial task execute(initial_task) Decompose swarm model splits it Task queue pending, by priority SwarmResult completed, failed, summary Scout search · collect Analyst analyze · verify Writer write · summarize SharedContext findings · blackboard · task results Agents add findings, never tasks. The run also stops after max_iterations rounds.

What it is

Three pieces:

  • A SharedContext — a typed blackboard (findings, messages, and task results) every agent reads and writes.
  • A task queue — agents pull from it; add_task (and task decomposition) push to it.
  • N SwarmAgents — each with its own capabilities tags and system prompt.

Each iteration, every agent claims the next task it's qualified for, runs it, writes its findings to the shared context, and claims again until nothing it can handle is pending. Agents never add tasks. The swarm exits when no task is pending or max_iterations is hit.

When to use it

  • ✅ Open-ended research — the swarm's model can split the brief into sub-tasks, and each agent sees what the others have already found.
  • ✅ Heterogeneous specialists — each agent has its own capability tags and system prompt, and any of them can pick up the next task they're qualified for.
  • ✅ Long-running batch — a queue depth + a max-iteration budget is the natural shape.
  • ✅ No single coordinator should exist — peer-to-peer is the point.

When NOT to use it

  • ❌ The flow is actually linear → use Composition.
  • ❌ One agent should decide who runs → use Orchestrator.
  • ❌ The conversation transcript should follow one role to another → use Handoff.
  • ❌ You need strict execution order — priority orders the queue, but each agent works through every task it can handle before the next agent starts, so a lower-priority task can run before a higher-priority one.

Code

import asyncio
from tulip.models import get_model
from tulip.multiagent import create_swarm, create_swarm_agent


async def main():
    model = get_model("openai:gpt-4o-mini")

    scout = create_swarm_agent(
        name="Scout",
        capabilities=["search", "collect", "investigate"],
        system_prompt="You are a research scout. Find sources, pull data, scope leads.",
    )
    analyst = create_swarm_agent(
        name="Analyst",
        capabilities=["analyze", "verify", "compare"],
        system_prompt="You are an analyst. Verify claims, cross-check sources, quantify findings.",
    )
    writer = create_swarm_agent(
        name="Writer",
        capabilities=["write", "summarize", "document"],
        system_prompt="You are a writer. Take confirmed findings, draft the report.",
    )

    swarm = create_swarm(
        name="Research swarm",
        agents=[scout, analyst, writer],
        model=model,
    )
    swarm.max_iterations = 12

    result = await swarm.execute(
        initial_task="Research why checkout conversion dropped last week: collect data, verify causes, draft a report.",
    )
    print(result.summary)
    for task in result.completed_tasks:
        print(task.description, "→", task.status)


asyncio.run(main())

A SwarmAgent carries free-form capabilities tags (not a tool list); tasks are matched to agents by those tags. A task queued with add_task or execute can be claimed by any agent with a tag that appears in the task's description, or by an agent with no tags at all. execute() is async and returns a SwarmResult with completed_tasks, failed_tasks, a shared context, and a summary. The same shape runs an incident-response (IR) war room — a hunter, a forensics analyst, and a reporter pulling from one queue.

How tasks enter the queue

You can seed the queue directly with add_task(...) (each agent claims higher-priority tasks first), and execute(decompose_tasks=True) will also ask the swarm's model whether to break the initial task into sub-tasks:

swarm = create_swarm(name="Research swarm", agents=[scout, analyst, writer], model=model)

swarm.add_task("Collect last week's conversion-funnel metrics", priority=10)
swarm.add_task("Write the stakeholder status update", priority=3)

result = await swarm.execute()

Each iteration, any qualifying agent claims the next task it's eligible for and works it, recording findings on the shared context.

Termination

Swarms stop when:

  • No task is left pending, OR
  • max_iterations is hit. A pending task no agent can handle keeps the swarm going until then, and ends up in neither completed_tasks nor failed_tasks.

Notebook

notebook_24_swarm_multiagent.py — a three-agent outage war room (mitigation, diagnostics, comms) with shared context.

Source

multiagent/swarm.py — Swarm, SharedContext.

See also