Multi-Agent Crew Guide¶
What you'll build¶
A three-agent crew that collaborates to produce a research report:
- Researcher — gathers information using web search
- Critic — identifies gaps, contradictions, and missing citations
- Writer — produces the final polished report
The agents run sequentially: each agent's output becomes the next agent's input.
Prerequisites¶
- Grampus installed with Anthropic support:
pip install "grampus-ai[anthropic]" - Dapr and Docker running locally
GRAMPUS_MODEL__ANTHROPIC_API_KEYset
Step 1 — Define the tools¶
# crew_agent.py
import asyncio
import os
from typing import Any
from grampus.core.models.anthropic import AnthropicClient
from grampus.core.types import AgentDefinition, ToolParameter
from grampus.orchestration.crew import Crew, CrewMember, CrewPattern
from grampus.orchestration.runner import AgentRunner, RunnerConfig
from grampus.tools.executor import ToolExecutor
from grampus.tools.registry import ToolRegistry
# Each agent gets its own registry (tools can overlap)
researcher_registry = ToolRegistry()
critic_registry = ToolRegistry()
writer_registry = ToolRegistry()
@researcher_registry.tool(
name="web_search",
description="Search the web for information.",
parameters=[
ToolParameter(name="query", type="string", description="Search query", required=True),
],
)
async def web_search(query: str) -> dict[str, Any]:
return {
"results": [
{"title": f"Article about {query}", "snippet": f"Key finding: {query} is important."},
{"title": f"{query} overview", "snippet": f"Background on {query}."},
]
}
@critic_registry.tool(
name="fact_check",
description="Check whether a claim can be verified.",
parameters=[
ToolParameter(name="claim", type="string", description="The claim to check", required=True),
],
)
async def fact_check(claim: str) -> dict[str, Any]:
return {"claim": claim, "verdict": "unverified", "confidence": 0.5}
Step 2 — Build the crew members¶
# continued from crew_agent.py
def make_client() -> AnthropicClient:
return AnthropicClient(api_key=os.environ["GRAMPUS_MODEL__ANTHROPIC_API_KEY"])
def make_researcher() -> CrewMember:
client = make_client()
executor = ToolExecutor(researcher_registry)
runner = AgentRunner(
model_client=client,
tool_executor=executor,
config=RunnerConfig(max_iterations=8, enable_memory=False),
)
agent_def = AgentDefinition(
name="researcher",
model="claude-sonnet-4-6",
system_prompt=(
"You are a thorough research analyst. Search for information on the given topic "
"and produce a detailed factual summary with key findings. "
"Use web_search to gather data. Include source URLs."
),
tools=["web_search"],
max_iterations=8,
)
return CrewMember(agent_def=agent_def, runner=runner, role="researcher")
def make_critic() -> CrewMember:
client = make_client()
executor = ToolExecutor(critic_registry)
runner = AgentRunner(
model_client=client,
tool_executor=executor,
config=RunnerConfig(max_iterations=5, enable_memory=False),
)
agent_def = AgentDefinition(
name="critic",
model="claude-sonnet-4-6",
system_prompt=(
"You are a rigorous fact-checker and editor. Review the research provided "
"and identify: (1) unsupported claims, (2) missing context, (3) logical gaps. "
"Use fact_check on specific claims. Output a structured critique."
),
tools=["fact_check"],
max_iterations=5,
)
return CrewMember(agent_def=agent_def, runner=runner, role="critic")
def make_writer() -> CrewMember:
client = make_client()
# Writer uses no tools — pure synthesis
executor = ToolExecutor(writer_registry)
runner = AgentRunner(
model_client=client,
tool_executor=executor,
config=RunnerConfig(max_iterations=3, enable_memory=False),
)
agent_def = AgentDefinition(
name="writer",
model="claude-sonnet-4-6",
system_prompt=(
"You are a professional technical writer. Given research findings and a critic's "
"notes, synthesize a polished, well-structured report. Use markdown headers. "
"Address all gaps the critic identified."
),
tools=[],
max_iterations=3,
)
return CrewMember(agent_def=agent_def, runner=runner, role="writer")
Step 3 — Assemble and run the crew¶
# continued from crew_agent.py
async def main() -> None:
crew = Crew(
members=[make_researcher(), make_critic(), make_writer()],
pattern=CrewPattern.SEQUENTIAL, # researcher → critic → writer
session_id="crew-report-001",
)
topic = "The current state of open-source agentic AI frameworks in 2025"
print(f"Starting crew run on: {topic}\n{'='*60}\n")
result = await crew.run(initial_input=topic)
print("=== RESEARCHER OUTPUT ===")
print(result.outputs.get("researcher", ""))
print("\n=== CRITIC OUTPUT ===")
print(result.outputs.get("critic", ""))
print("\n=== WRITER OUTPUT (FINAL REPORT) ===")
print(result.outputs.get("writer", ""))
print(f"\n{'='*60}")
print(f"Total cost: ${result.total_cost_usd:.4f}")
print(f"Duration: {result.duration_seconds:.1f}s")
print(f"Pattern: {result.pattern}")
if __name__ == "__main__":
asyncio.run(main())
Step 4 — Run it¶
How sequential context passing works¶
In CrewPattern.SEQUENTIAL, each agent receives a combined input:
Then the critic receives:
[original_topic]
--- researcher output ---
[researcher's findings]
--- critic input ---
Please critique the above research.
And the writer receives all prior outputs concatenated, giving it full context for synthesis.
Patterns¶
crew = Crew(
members=[researcher, critic, writer],
pattern=CrewPattern.SEQUENTIAL,
session_id="session-1",
)
Each agent runs after the previous one completes. Output accumulates.
crew = Crew(
members=[agent_a, agent_b, agent_c],
pattern=CrewPattern.PARALLEL,
session_id="session-2",
)
All agents run concurrently on the same input. Results are collected independently.
supervisor = CrewMember(agent_def=..., runner=..., role="supervisor")
worker_a = CrewMember(agent_def=..., runner=..., role="worker")
worker_b = CrewMember(agent_def=..., runner=..., role="worker")
crew = Crew(
members=[supervisor, worker_a, worker_b],
pattern=CrewPattern.HIERARCHICAL,
session_id="session-3",
)
The supervisor (first member with role="supervisor") orchestrates the workers.
Error handling¶
from grampus.core.errors import OrchestrationError
try:
result = await crew.run(initial_input=topic)
except OrchestrationError as e:
print(f"Crew failed: {e}")
print(f"Error code: {e.code}") # e.g. "CREW_MEMBER_FAILED"
print(f"Details: {e.details}")
Next steps¶
- Memory guide → — Add shared episodic memory across crew members
- Evaluation guide → — Write eval cases to test crew output quality
- Orchestration API → — Full
CrewandCrewMemberreference - Multi-Agent Debate → — When you need a confidence score instead of a pipeline: run the same question past a panel of LLMs and let them argue toward consensus
- Market-Based Allocation → — When your crew has a dynamic pool of workers with overlapping skills and you want the best-fit agent to win each task automatically — capability filtering, calibration-discounted bidding, and UCB reputation tracking via
MarketCrew