Skip to content

Repository files navigation

strands-compose

Strands Compose

Declarative multi-agent orchestration for strands-agents — wire entire agent systems with YAML

Python 3.11+PyPI versionStrands AgentsLicense

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.

What is this?

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: bedrockmodel_id: us.anthropic.claude-sonnet-4-6-v1:0agents:
researcher:
model: defaultsystem_prompt: "You research topics."tools: [strands_tools.http_request]writer:
model: defaultsystem_prompt: "You write reports."coordinator:
model: defaultsystem_prompt: "Coordinate research and writing."orchestrations:
team_leader:
mode: delegateentry_name: coordinatorconnections:
- agent: researcherdescription: "Research a topic."
- agent: writerdescription: "Write the report."entry: team_leader
fromstrands_composeimportloadresolved=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.


See related projects

Strands Compose is an ecosystem that includes the following packages:

LayerPackageWho uses it
Define the agentsstrands-composeDevelopers
Run / deploy the agentsstrands-compose-agentcoreDevelopers, operations
*Put the agents in front of peoplestrands-compose-chatEnd users

Why this changes everything

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:

🔖 Version it

Push to Git. Tag it. Diff two versions and see exactly what changed. No more "I think someone changed the system prompt last Tuesday."

📦 Build a registry

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.

🧪 Automate experiments

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.

🔁 Reproduce anything

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.

CRAZY, right?!


What's in the box

FeatureWhat it does
YAML-first configModels, agents, tools, hooks, MCP, orchestrations — all in one file
Full YAML powerVariables (${VAR:-default}), anchors (&ref / *ref), x- scratch pads, multi-file merge
Multi-model supportBedrock, Anthropic, OpenAI, Ollama, Gemini — swap with one line
MCP servers & clientsLaunch local servers from Python files, connect to remote HTTP endpoints, or spawn stdio subprocesses
MCP lifecycle managementStartup ordering, readiness polling, graceful shutdown — servers before clients, always
Orchestration modesDelegate (agent-as-tool), Swarm (peer handoffs), Graph (DAG pipelines) — arbitrarily nestable
Event streamingUnified async event queue across any orchestration depth — tokens, tool calls, handoffs, completions
Session persistenceFile, S3, or Bedrock AgentCore Memory — agents remember across restarts
Custom agent factoriesPlug your own Agent subclass or factory via type:
Deployment-agnosticPure core library — no HTTP server, no deployment opinions baked in

Examples

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
#ExampleWhat it shows
01Minimalload() one-liner — the simplest possible agent
02Vars & Anchors${VAR:-default} interpolation and YAML &anchor / *alias reuse
03Toolstools: — auto-load @tool functions from Python files
04Sessionsession_manager: — persistent memory across turns and restarts
05Hookshooks:MaxToolCallsGuard, ToolNameSanitizer, and custom hooks
06MCPAll three MCP modes: local server, remote URL, stdio subprocess
07Delegatemode: delegate — coordinator routes work to specialist agents
08Swarmmode: swarm — peer agents hand off to each other autonomously
09Graphmode: graph — deterministic DAG pipeline between agents
10NestedNested orchestration — Swarm inside a Delegate
11Multi-fileSplit config across files — infra in one YAML, agents in another
12Streamingwire_event_queue() — stream every token, tool call, and handoff live
13Graph conditionsConditional edges — condition:, reset_on_revisit, max_node_executions
14Agent factorytype: + agent_kwargs: — custom agent factory instead of Agent()

Getting started

Install with uv:

uv add strands-compose # Bedrock (default)

Create a config.yaml:

models:
default:
provider: bedrockmodel_id: us.anthropic.claude-sonnet-4-6-v1:0agents:
assistant:
model: defaultsystem_prompt: "You are a helpful assistant."entry: assistant

Run it:

fromstrands_composeimportloadresolved=load("config.yaml")
withresolved.mcp_lifecycle:
result=resolved.entry("Hello!")
print(result)

YAML superpowers

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:MaxToolCallsGuardparams: { max_calls: 15 }
- type: strands_compose.hooks:ToolNameSanitizermodels:
default:
provider: bedrockmodel_id: ${MODEL}agents:
assistant:
model: defaultsystem_prompt: *base_prompthooks: *safety_hooksentry: assistant

Override 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.


Multi-agent orchestration

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.

Delegate — agent as a tool

The coordinator calls sub-agents like tool functions. Best for hub-and-spoke patterns:

orchestrations:
team_leader:
mode: delegateentry_name: coordinator # Agent declared in `agents:`connections:
- agent: researcherdescription: "Research the topic."
- agent: writerdescription: "Write the report."

Swarm — autonomous handoffs

Peer agents pass control to each other. No central coordinator — agents decide when to hand off:

orchestrations:
review_team:
mode: swarmentry_name: drafter # Swarm entry - agent nameagents: [drafter, reviewer, tech_lead] # Agents declared in `agents:`max_handoffs: 10

Graph — deterministic DAG pipeline

Agents execute in dependency order. Independent nodes run in parallel. Supports conditional edges:

orchestrations:
blog:
mode: graphentry_name: writer # Graph entry - agent nameedges: # We use agent names to define edges
- from: writerto: reviewer
- from: reviewerto: writercondition: ./conditions.py:needs_revision
- from: reviewerto: publishercondition: ./conditions.py:is_approved

Nested orchestrations

Named 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_leadermode: swarmentry_name: researcheragents: [researcher, writer, reviewer]team_leader:
mode: delegateentry_name: coordinatorconnections:
- agent: content_team # Nested swarm as a delegate tooldescription: "Content creation team."
- agent: qa_bot # Nested agent as a delegate tooldescription: "Quality assurance."entry: team_leader

strands-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.


Streaming-ready by design

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

TypeWhen
agent_startAgent began processing
tokenA chunk of generated text
reasoningModel's thinking output
tool_startAgent is calling a tool
tool_endTool returned a result
agent_completeAgent finished processing
errorSomething went wrong

Multi-agent events (orchestrations)

TypeWhen
multiagent_startOrchestration started
node_startA node in the graph/swarm started
handoffAgent handing off to another
node_stopA node finished
multiagent_completeOrchestration finished

Session-level events

TypeWhen
session_startFirst event of a turn — includes full wired topology manifest
session_endLast 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.


Developer setup

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.

About

Zero-code YAML-driven agent orchestration over strands-agents

Topics

Resources

Contributing

Security policy

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages