Declarative multi-agent orchestration for strands-agents — wire entire agent systems with YAML
Important
Community project — not affiliated with AWS or the strands-agents team. Bugs here? Open an issue. Bugs in the underlying SDK? Head to strands-agents.
Think Docker Compose, but for AI agents
Strands is a powerful agent SDK. But once you have more than one agent, a few MCP servers, safety hooks, and shared models — you end up writing the same plumbing over and over. strands-compose kills that boilerplate.
You describe the shape of your agent system in YAML, and strands-compose resolves, validates, and starts everything — models, MCP servers & clients, hooks, tools, orchestration topology — as a live, fully wired multi-agent system.
models:
default:
provider: bedrock
model_id: us.anthropic.claude-sonnet-4-6-v1:0
agents:
researcher:
model: default
system_prompt: "You research topics."
tools: [strands_tools.http_request]
writer:
model: default
system_prompt: "You write reports."
coordinator:
model: default
system_prompt: "Coordinate research and writing."
orchestrations:
team_leader:
mode: delegate
entry_name: coordinator
connections:
- agent: researcher
description: "Research a topic."
- agent: writer
description: "Write the report."
entry: team_leaderfrom strands_compose import load
resolved = load("config.yaml")
result = resolved.entry("Write a report about quantum computing.")
print(result)Three agents, orchestration wiring, model sharing — zero plumbing code.
Already working with strands? Guess what — you already know strands-compose. After load() resolves your YAML, what you get back are plain strands objects. Every agent is a strands.Agent. Every MCP client is a strands.tools.mcp.MCPClient. Every orchestrator is a strands.multiagent.Swarm or Graph or just strands.Agent. No wrappers, no subclasses, no magic. Just the real deal, fully wired and ready to go.
Strands Compose is an ecosystem that includes the following packages:
| Layer | Package | Who uses it |
|---|---|---|
| Define the agents | strands-compose | Developers |
| Run / deploy the agents | strands-compose-agentcore | Developers, operations |
| *Put the agents in front of people | strands-compose-chat | End users |
Your entire agent network — models, prompts, tools, hooks, MCP servers, orchestration topology — captured in a single YAML file and maybe a few Python files for custom tools or hooks. That's it. That's your agent environment. Here's what that unlocks:
Push to Git. Tag it. Diff two versions and see exactly what changed. No more "I think someone changed the system prompt last Tuesday."
A folder of YAML configs — one per agent environment. production.yaml, staging.yaml, experiment-42.yaml. Each is a complete, self-contained snapshot of an agent system. That's your agent environments registry — no platform needed.
Your entire config is data, so you can generate it. Build 20 variations — different models, different prompts, different tool combinations — and run them all in CI. Point another strands-compose pipeline to analyze results compute metrics. You're benchmarking agent systems with agent systems.
A bug report comes in. You have the exact YAML config. Load it, replay it, debug it. No "works on my machine" — the config is the machine.
| Feature | What it does |
|---|---|
| YAML-first config | Models, agents, tools, hooks, MCP, orchestrations — all in one file |
| Full YAML power | Variables (${VAR:-default}), anchors (&ref / *ref), x- scratch pads, multi-file merge |
| Multi-model support | Bedrock, OpenAI, Ollama, Gemini — swap with one line |
| MCP servers & clients | Launch local servers from Python files, connect to remote HTTP endpoints, or spawn stdio subprocesses |
| MCP lifecycle management | Startup ordering, readiness polling, graceful shutdown — servers before clients, always |
| Orchestration modes | Delegate (agent-as-tool), Swarm (peer handoffs), Graph (DAG pipelines) — arbitrarily nestable |
| Event streaming | Unified async event queue across any orchestration depth — tokens, tool calls, handoffs, completions |
| Session persistence | File, S3, or Bedrock AgentCore Memory — agents remember across restarts |
| Custom agent factories | Plug your own Agent subclass or factory via type: |
| Deployment-agnostic | Pure core library — no HTTP server, no deployment opinions baked in |
Every example is a self-contained folder with a README.md, config.yaml, and main.py. Start from the top and work your way down — each one builds on concepts from the previous.
# Run any example
uv run python examples/01_minimal/main.py| # | Example | What it shows |
|---|---|---|
| 01 | Minimal | load() one-liner — the simplest possible agent |
| 02 | Vars & Anchors | ${VAR:-default} interpolation and YAML &anchor / *alias reuse |
| 03 | Tools | tools: — auto-load @tool functions from Python files |
| 04 | Session | session_manager: — persistent memory across turns and restarts |
| 05 | Hooks | hooks: — MaxToolCallsGuard, ToolNameSanitizer, and custom hooks |
| 06 | MCP | All three MCP modes: local server, remote URL, stdio subprocess |
| 07 | Delegate | mode: delegate — coordinator routes work to specialist agents |
| 08 | Swarm | mode: swarm — peer agents hand off to each other autonomously |
| 09 | Graph | mode: graph — deterministic DAG pipeline between agents |
| 10 | Nested | Nested orchestration — Swarm inside a Delegate |
| 11 | Multi-file | Split config across files — infra in one YAML, agents in another |
| 12 | Streaming | wire_event_queue() — stream every token, tool call, and handoff live |
| 13 | Graph conditions | Conditional edges — condition:, reset_on_revisit, max_node_executions |
| 14 | Agent factory | type: + agent_kwargs: — custom agent factory instead of Agent() |
Install with uv:
uv add strands-compose # Bedrock (default)Create a config.yaml:
models:
default:
provider: bedrock
model_id: us.anthropic.claude-sonnet-4-6-v1:0
agents:
assistant:
model: default
system_prompt: "You are a helpful assistant."
entry: assistantRun it:
from strands_compose import load
resolved = load("config.yaml")
with resolved.mcp_lifecycle:
result = resolved.entry("Hello!")
print(result)strands-compose gives you Docker Compose-style variable interpolation plus full YAML anchor/alias support. DRY configs that adapt to any environment:
vars:
MODEL: ${MODEL:-us.anthropic.claude-sonnet-4-6-v1:0}
TONE: ${TONE:-friendly}
x-base: &base_prompt |
You are a ${TONE} assistant.
Keep answers clear and concise.
x-hooks: &safety_hooks
- type: strands_compose.hooks:MaxToolCallsGuard
params: { max_calls: 15 }
- type: strands_compose.hooks:ToolNameSanitizer
models:
default:
provider: bedrock
model_id: ${MODEL}
agents:
assistant:
model: default
system_prompt: *base_prompt
hooks: *safety_hooks
entry: assistantOverride at runtime: TONE=formal MODEL=us.anthropic.claude-sonnet-4-6-v1:0 python main.py
Split large configs across files — models in one, agents in another, MCP in a third — and merge them with load(["base.yaml", "agents.yaml"]). Each file interpolates its own vars: independently, collections merge, and duplicates are caught.
strands-compose supports 3 orchestration modes from strands. They can be nested arbitrarily — a delegate target can be a swarm, a graph node can be a delegate.
The coordinator calls sub-agents like tool functions. Best for hub-and-spoke patterns:
orchestrations:
team_leader:
mode: delegate
entry_name: coordinator # Agent declared in `agents:`
connections:
- agent: researcher
description: "Research the topic."
- agent: writer
description: "Write the report."Peer agents pass control to each other. No central coordinator — agents decide when to hand off:
orchestrations:
review_team:
mode: swarm
entry_name: drafter # Swarm entry - agent name
agents: [drafter, reviewer, tech_lead] # Agents declared in `agents:`
max_handoffs: 10Agents execute in dependency order. Independent nodes run in parallel. Supports conditional edges:
orchestrations:
blog:
mode: graph
entry_name: writer # Graph entry - agent name
edges: # We use agent names to define edges
- from: writer
to: reviewer
- from: reviewer
to: writer
condition: ./conditions.py:needs_revision
- from: reviewer
to: publisher
condition: ./conditions.py:is_approvedNamed orchestrations reference each other. A swarm becomes a delegate tool, a delegate becomes a graph node — compose them however you want:
orchestrations:
content_team: # This swarm is plugged in as a tool for the team_leader
mode: swarm
entry_name: researcher
agents: [researcher, writer, reviewer]
team_leader:
mode: delegate
entry_name: coordinator
connections:
- agent: content_team # Nested swarm as a delegate tool
description: "Content creation team."
- agent: qa_bot # Nested agent as a delegate tool
description: "Quality assurance."
entry: team_leaderstrands-compose topologically sorts all orchestrations, builds inner ones first, then wires them as tools or nodes for outer ones. Circular dependencies are caught at load time.
One call — resolved.wire_event_queue() — silently injects an EventPublisher hook into every agent across your entire system, regardless of topology. Delegate, Swarm, Graph, nested three levels deep — all events funnel into one async queue. No per-agent wiring, no topology-specific plumbing.
For the best local dev experience — use the dev CLI from strands-compose-agentcore. Or see example 12 for pure strands-compose solution.
Every event carries {type, agent_name, timestamp, data} — uniform across all agents and orchestration modes:
Single-agent events
| Type | When |
|---|---|
agent_start |
Agent began processing |
token |
A chunk of generated text |
reasoning |
Model's thinking output |
tool_start |
Agent is calling a tool |
tool_end |
Tool returned a result |
agent_complete |
Agent finished processing |
error |
Something went wrong |
Multi-agent events (orchestrations)
| Type | When |
|---|---|
multiagent_start |
Orchestration started |
node_start |
A node in the graph/swarm started |
handoff |
Agent handing off to another |
node_stop |
A node finished |
multiagent_complete |
Orchestration finished |
Session-level events
| Type | When |
|---|---|
session_start |
First event of a turn — includes full wired topology manifest |
session_end |
Last event of a turn — includes final response text and full result |
This is a standard SSE design — uniform JSON events ready to pipe straight into any modern web service, log aggregator, or real-time frontend. The built-in AnsiRenderer gives you coloured terminal output immediately: agent names, tool calls, reasoning traces, handoffs — all streaming live.
git clone https://github.com/strands-compose/sdk-python
cd sdk-python
uv run just install # install deps + wire git hooks (run once after clone)
uv run just check # lint + type check + security scan
uv run just test # pytest with coverage
uv run just format # auto-format (Ruff)Re-install hooks after a fresh clone or if hooks stop running:
uv run just install-hooks
See CONTRIBUTING.md for the full contribution guide and CHANGELOG.md for release history.