In my last post I covered the Swarm pattern and how multiple specialized agents can collaborate dynamically like an ant colony.ππΌ
In this post we'll dive deeper into another of those patterns, Graph. I'll share code snippets and a the repo with examples that you can follow along.
Strands Multi-Agent Systems: Graph
While Swarms are all about autonomous collaboration and dynamic handoffs, the Graph pattern brings structure and determinism to multi-agent orchestration. You can think of it as the difference between a jazz band improvising together versus a symphony orchestra following a conductor's score. While both can be powerful, they ultimately serve different purposes.
What Is a Graph Multi-Agent System?
A Graph in Strands is a deterministic directed graph based agent orchestration system. In this system, agents are nodes in a graph and each node is executed based on edge dependencies. In practice what this means is the output from one node is passed as input to connected and dependent nodes.
Unlike sequential workflows where agents simply pass results down a line, or swarms where agents collaborate dynamically, graphs provide structured coordination. Each agent knows exactly when to execute based on the completion of its dependencies.
Key characteristics of the Graph pattern:
- Deterministic execution order based on graph structure
- Output propagation along edges between nodes
- Clear dependency management between agents
- Conditional logic for dynamic workflows
- Support for cyclic patterns with proper safeguards
- Nested multi-agent systems (like another Graph as a node!)
The benefits of this pattern are its predictability. You define your agents, specify their relationships (dependencies), and let the graph handle the execution orchestration.
How Graphs Work
Every graph consists of three core components working together:
GraphNode represents a node on the graph with:
- A unique identifier
- The executor (your actual Agent instance)
- Dependencies on other nodes
- Execution status tracking
- Performance metrics
GraphEdge defines the connections between nodes, specifying:
- Source and target nodes
- Optional conditions that determine if the edge should be traversed
GraphBuilder provides a simple interface for constructing graphs, letting you:
- Add nodes to the graph
- Create edges between them
- Set entry points for execution
- Configure timeouts and max executions
- Validate the graph structure
Input Propagation and Context Management
One crucial aspect of graphs is how information flows between agents. The graph automatically builds input for each node based on its dependencies.
Entry point nodes receive the original task as input, while dependent nodes receive a combined input that includes:
- The original task description
- Results from all completed dependency nodes
This ensures each agent has full context while maintaining clean separation of concerns. In this sample use case with 4 nodes, the report agent, for instance, gets both the original research agent and the fact-checking agent outputs without needing to coordinate directly with those agents.
Here's what the formatted input for a dependent node looks like:
Original Task: [The original task text]
Inputs from previous nodes:
From [node_id]:
- [Agent name]: [Result text]
From [another_node_id]:
- [Agent name]: [Result text]
Practical Implementation
Creating a graph involves defining your agents, building the graph structure, and executing it:
Step 1: Define Your Agents
# Create specialized agents
researcher = Agent(name="researcher", system_prompt="You are a research specialist...")
analyst = Agent(name="analyst", system_prompt="You are a data analysis specialist...")
fact_checker = Agent(name="fact_checker", system_prompt="You are a fact checking specialist...")
report_writer = Agent(name="report_writer", system_prompt="You are a report writing specialist...")
Step 2: Build the Graph Structure
# Build the graph
builder = GraphBuilder()
# Add nodes
builder.add_node(researcher, "research")
builder.add_node(analyst, "analysis")
builder.add_node(fact_checker, "fact_check")
builder.add_node(report_writer, "report")
# Add edges (dependencies)
builder.add_edge("research", "analysis")
builder.add_edge("research", "fact_check")
builder.add_edge("analysis", "report")
builder.add_edge("fact_check", "report")
# Build the graph
graph = builder.build()
Step 3: Execute and Analyze Results
# Execute the graph on a task
result = graph("Research the impact of AI on healthcare and create a comprehensive report")
# Access the results
print(f"Execution order: {[node.node_id for node in result.execution_order]}")
# Get performance metrics
print(f"Total nodes: {result.total_nodes}")
print(f"Completed nodes: {result.completed_nodes}")
print(f"Failed nodes: {result.failed_nodes}")
print(f"Execution time: {result.execution_time}ms")
print(f"Token usage: {result.accumulated_usage}")
The graph returns detailed results including execution status, which agents participated, individual agent outputs, performance metrics, and token usage statistics.
Common Graph Topologies
There are three common graph patterns you might encounter:
Sequential Pipeline: Good for quality control workflows where each step builds on the previous one. Research β Analysis β Review β Report.
Parallel Processing: A coordinator distributes work to multiple specialists, then aggregates results. Great for comprehensive analysis from different perspectives/specialiazed agents.
Branching Logic: A classifier determines which specialized branch to take. Useful for routing different types of requests to appropriate specialists.
Conditional Edges
One powerful feature of graphs is conditional edges. Like you saw in the branching topology, they let you create dynamic workflows based on intermediate results:
def only_if_research_successful(state):
"""Only traverse if research was successful."""
research_node = state.results.get("research")
if not research_node:
return False
# Check if research result contains success indicator
result_text = str(research_node.result)
return "successful" in result_text.lower()
# Add conditional edge
builder.add_edge("research", "analysis", condition=only_if_research_successful)
When to Use Graph
A Graph is ideal when you need a structured process with conditional logic, branching, or loops with deterministic execution flow. It performs well for modeling business processes or any task where the next step is decided by the outcome of the current one.
Best Practices π‘
From exploring the graph pattern, here are some best practices:
- Leverage conditional edges - Create dynamic workflows based on intermediate results
- Handle failures gracefully - Think about how node failures affect the overall workflow
- Nest strategically - Use Swarms within Graphs for complex workflows combining structure and collaboration
- Set appropriate limits - Use timeouts and max executions, especially for cyclic graphs
Swarm vs. Graph: When to Choose Which?
Now that we've covered two patterns, you might be wondering when to use each:
Use Swarm when:
- The problem benefits from exploration and brainstorming
- You want agents to dynamically decide who should handle what
- Collaboration and handoffs are central to the solution
- The optimal agent sequence isn't known upfront
Use Graph when:
- You have a clear workflow structure with known dependencies
- Deterministic execution order is important
- You need conditional branching or feedback loops
- Different parts of the workflow can run in parallel
- You're modeling a business process
In the last post of this series we'll dive deeper into the Agents as Tools pattern, so stay tuned for that!
Don't forget to give me a π¦ if you got this far and let me know what pattern you're most excited to try in the comments!




Top comments (0)