Swarms
Multi-agent collaboration with designated final-output agents, per-agent final tools, and member-style registration.
#Swarms
Runnable examples of multiple agents working together. A Swarm holds a list of Agents and routes work between them: the user message goes in, the result of the designated final-output agent comes out.
#A three-agent swarm
This example builds a realistic investment-analysis swarm: a financial analyst, a risk assessor, and a director who synthesizes their findings into a final memo.
from maivn import Agent, Swarm
from maivn.messages import HumanMessage
from pydantic import BaseModel, Field
# Specialist 1: financial analysis
financial_analyst = Agent(
name='Financial Analyst',
system_prompt='Analyze financial data (revenue, EBITDA, margins). Focus on numerical trends.',
api_key='...',
)
@financial_analyst.toolify(name='calculate_cagr')
def calculate_cagr(start_value: float, end_value: float, periods: int) -> float:
"""Compound Annual Growth Rate."""
return (end_value / start_value) ** (1 / periods) - 1
# Specialist 2: risk assessment
risk_assessor = Agent(
name='Risk Assessor',
system_prompt='Evaluate market volatility, competition, and regulatory risk. Score 1-10.',
api_key='...',
)
@risk_assessor.toolify(name='calculate_risk_score')
def calculate_risk_score(volatility: float, competition: float, regulation: float) -> float:
return round(volatility * 0.4 + competition * 0.3 + regulation * 0.3, 1)
# Final output: investment director
investment_director = Agent(
name='Investment Director',
use_as_final_output=True,
system_prompt=(
'Synthesize the Financial Analyst and Risk Assessor reports into an '
'Investment Memo. Always call generate_investment_memo as the final tool.'
),
api_key='...',
)
@investment_director.toolify(name='generate_investment_memo', final_tool=True)
class InvestmentMemo(BaseModel):
company_name: str
financial_data: dict
risk_data: dict
recommendation: str # Buy / Hold / Sell
swarm = Swarm(
name='Investment Analysis Swarm',
description='Comprehensive company evaluation and investment decisioning.',
agents=[financial_analyst, risk_assessor, investment_director],
)
swarm.invoke(
[HumanMessage(content=(
'Evaluate "TechNova Solutions" for a $50M Series C investment. '
'Revenue: [12.5, 18.2, 25.0, 38.5]M USD over 4 years. '
'Net income: [1.2, 2.5, 4.8, 8.2]M USD. Provide a memo.'
))],
force_final_tool=True,
)A few things worth noticing:
use_as_final_output=Trueoninvestment_directortells the swarm that this agent's output is what the swarm returns.final_tool=Trueongenerate_investment_memomarks it as a terminal, structured result tool.force_final_tool=Trueat the swarm level requires that path on every invocation where that keyword is supplied. Execution failures still need handling.
#Designating the final-output agent
A swarm needs to know which agent's output to return. Three ways:
use_as_final_output=Trueon one agent (most explicit):
``text director = Agent(..., use_as_final_output=True) ``
- The agent owns the only
final_tool— the swarm infers it.
- Let the swarm decide — no
use_as_final_outputand noforce_final_tool. The orchestrator picks the most relevant agent based on the task. Useful for exploratory swarms.
#Per-agent final_tool ownership
Multiple agents can each own their own final_tool. The runtime keeps them straight. Mark one agent use_as_final_output=True to disambiguate.
@category_classifier.toolify(name='emit_category', final_tool=True)
class CategoryDecision(BaseModel):
ticket_id: str
product_area: str
@priority_scorer.toolify(name='emit_priority', final_tool=True)
class PriorityDecision(BaseModel):
ticket_id: str
priority: str # P0 / P1 / P2 / P3
resolution_director = Agent(
name='Resolution Director',
use_as_final_output=True, # this one's verdict is the swarm's answer
system_prompt='Consume category + priority, then call emit_triage_decision.',
api_key='...',
)
@resolution_director.toolify(name='emit_triage_decision', final_tool=True)
class TriageDecision(BaseModel):
ticket_id: str
product_area: str
priority: str
assigned_team: str
next_action: str
triage_swarm = Swarm(
name='Support Ticket Triage Swarm',
agents=[category_classifier, priority_scorer, resolution_director],
)If you forget to designate one final agent and multiple own a final_tool, swarm.validate_tool_configuration() raises so the misconfiguration surfaces before you invoke.
#@swarm.member — factory-style registration
Sometimes it's cleaner to build the agent inside a factory that gets registered with the swarm. The @swarm.member decorator runs the factory and threads agent dependencies through it:
swarm = Swarm(
name='Member Planning Swarm',
system_prompt='Coordinate the member agents. Editor produces the final brief.',
)
@swarm.toolify(name='load_launch_context')
def load_launch_context(market: str = 'healthcare') -> dict:
return {'market': market, 'launch_window': 'Q3', 'constraints': ['HIPAA review']}
@swarm.member
@depends_on_tool(load_launch_context, arg_name='launch_context')
def research_agent() -> Agent:
agent = Agent(name='Member Research Agent', system_prompt='...', api_key='...')
@agent.toolify(name='compile_market_notes')
def compile_market_notes() -> dict:
return {'buyer': 'clinic operations leaders', 'risk': 'security review timing'}
return agentThe strategy editor depends on the research agent's output:
strategy_editor = Agent(
name='Member Strategy Editor',
system_prompt='Use write_launch_brief as the final tool.',
use_as_final_output=True,
api_key='...',
)
@strategy_editor.toolify(name='write_launch_brief', final_tool=True)
class LaunchBrief(BaseModel):
title: str
market: str
research_notes: dict # filled in from the research agent
recommended_motion: str
next_steps: list[str]
# Register the editor with a required dependency on research_agent
strategy_editor = swarm.member.depends_on_agent(
research_agent, arg_name='research_notes',
)(strategy_editor)The member dependency supplies research context before the editor runs. It does not directly inject the LaunchBrief.research_notes model field; that field is filled during the editor's tool call. For direct field injection, declare the dependency on the final model itself.
#Swarm-level tools
Tools attached directly to the swarm run in the parent invocation. Use them for context loaders or explicit member dependencies. To let a member call one itself, also register the tool on that member with member.add_tool(...):
@swarm.toolify(name='load_account_context')
def load_account_context(account_id: str) -> dict:
return {'tier': 'enterprise', 'mrr': 12000, 'csat': 4.6}Any agent in the swarm can call this tool by name.
#Scaling up: bound resources per agent
For larger swarms, each agent can be bound to its own set of memory resources (PDFs, transcripts, internal docs). The runtime makes each agent's bound resources available in its scope. A member's outputs can still share information with the swarm, so this is not a confidentiality barrier between members. It is useful when you want specialists with distinct knowledge bases:
brand_strategist = Agent(
name='Brand Strategist',
system_prompt='You know the brand strategy deeply. Answer from BRAND.pdf.',
api_key='...',
resources=[{'file': 'business/BRAND.pdf', 'name': 'Brand Strategy',
'mime_type': 'application/pdf', 'binding_type': 'agent'}],
)
valuation_analyst = Agent(
name='Valuation Analyst',
system_prompt='You know the financial model deeply. Answer from VALUATION.pdf.',
api_key='...',
resources=[{'file': 'business/VALUATION.pdf', 'name': 'Valuation Model',
'mime_type': 'application/pdf', 'binding_type': 'agent'}],
)
bi_swarm = Swarm(
name='Business Intelligence Swarm',
agents=[brand_strategist, valuation_analyst],
)See Memory for resource lifecycle, retrieval policies, and configuration.
#Async, streaming, batch
Both scopes support sync/async invocation, event streaming, and batching. Agent-only features such as structured_output are not available on Swarm:
swarm.invoke(messages)
swarm.stream(messages) # token-level streaming
await swarm.ainvoke(messages)
async for event in swarm.astream(messages):
handle(event)
swarm.batch([msgs_1, msgs_2, msgs_3]) # multiple invocations concurrentlySee Batch & Scheduling for batch semantics.
#What's next
- Multi-Agent guide — the deeper treatment of swarm orchestration, including final-output policy and resource binding.
- Memory — bound resources per agent, retrieval, and skills/insights.
- Agents & Tools — hook execution modes including swarm-wide hooks.