← Back to list

Design Patterns in Action: Inside AWS Strands Agents

Why Strands?

Gunjan · 2025-10-02 03:04 · 14 claps · 4.6 min read
#aws-strands #llm #aws #aws-machine-learning #aws-ml-specialty
Open on Medium ↗
Wiki topics: LLM · Large Language Models AGT · AI Agents ML · Machine Learning EDU · Education & Learning ☁️ · DevOps & Cloud

Design Patterns in Action: Inside AWS Strands Agents

Why Strands?

AI agents are complex systems, they think, call APIs, reason with memory, fail, retry, and sometimes collaborate with other agents.

AWS’s Strands Agents SDK is an open-source framework for building these agents in a production-ready way.

In a nutshell it has the following —

  • Multi-agent orchestration (agent graphs, swarms, peer-to-peer)
  • Model-agnostic adapters (Claude, Bedrock, Ollama, Llama, etc.)
  • MCP support for standardized tool servers
  • Observability and state management already baked in

Instead of inventing everything from scratch, Strands weaves these parts together using some easy to understand software design patterns.

Pattern 1: The Agent Event Loop (Reactor)

Every Strands agent runs inside a loop:

  1. Ask the model for its next step
  2. If it wants to use a tool → invoke it
  3. Feed results back to the model
  4. Repeat until the task ends

This is the Reactor pattern, familiar from GUIs and async frameworks.

while not done:
    step = model.step(context, tools)
    if step.wants_tool:
        result = run_tool(step.tool_name, step.args)
        context.append(result)
    else:
        done = True

Think of this as the “heartbeat” of every agent.

Pattern 2: Strategy for Tool Selection

When multiple tools exist, Strands lets you pick how to choose:

  • Relevance scoring
  • Cost-based priority
  • Custom heuristics

That’s the Strategy pattern in action: swap in different decision algorithms without changing the loop.

Pattern 3: Decorator for Tool Registration

Want to turn a Python function into a tool? Just annotate it:

from strands import tool  
@tool  
def web_search(query: str) -> list:  
    """Search the web for documents."""  
    ...

Here, the Decorator pattern attaches metadata (description, schema) so Strands can expose it as a tool.

Pattern 4: Adapter for Model Providers

Strands supports Claude via Bedrock, Ollama locally, Llama, GPTs, etc.

Under the hood, each is wrapped with an Adapter pattern: a common interface like model.invoke() no matter the provider.

Pattern 5: State & Session (Memento)

Agents need memory. Strands uses session managers to persist and restore state.

This is the Memento pattern: snapshot → save → resume. Critical for long-running or crash-resilient agents.

Pattern 6: Multi-Agent Graph (Composite)

Complex problems? Don’t build a single giant agent. Compose many.

graph = agent_graph({
    "planner": PlannerAgent(),
    "researcher": ResearchAgent(),
    "summarizer": SummarizerAgent()
})

That’s the Composite pattern: treat each agent as a node in a graph. Together they solve bigger problems.

Now let’s make Multi-Agent Composite Pattern concrete with Strands-style code. This pattern is about orchestrating multiple specialized agents (Planner, Researcher, Summarizer, etc.) that together solve a bigger task.

Here are two progressively richer examples:

6a. Simple Multi-Agent Graph

Each agent has its own role. We stitch them together with agent_graph.

from strands import Agent, agent_graph
# Planner agent - breaks problem into steps
planner = Agent(
    name="planner",
    system_prompt="You are a planner. Break down tasks into smaller steps."
)
# Research agent - fetches or reasons about knowledge
researcher = Agent(
    name="researcher",
    system_prompt="You are a researcher. Collect information and provide detailed findings."
)
# Summarizer agent - compiles the result
summarizer = Agent(
    name="summarizer",
    system_prompt="You are a summarizer. Condense research into a concise final answer."
)
# Compose into a graph
graph = agent_graph({
    "planner": planner,
    "researcher": researcher,
    "summarizer": summarizer
}, edges=[
    ("planner", "researcher"),
    ("researcher", "summarizer")
])
# Run the composite agent
response = graph.run("What are the top 3 applications of quantum computing in finance?")
print(response)

The flow: Planner → Researcher → Summarizer This is the Composite pattern: each agent is a node, but together they form a larger agent system.

6b. Composite with Tool-Enabled Agents

You can give different tools to different agents to reflect their specialties.

from strands import Agent, agent_graph, tool
# Example tool: web search
@tool
def web_search(query: str) -> str:
    """Search the web for information."""
    return f"Dummy search results for {query}"
# Planner
planner = Agent(
    name="planner",
    system_prompt="Create a plan of steps for answering the user's question."
)
# Researcher with a search tool
researcher = Agent(
    name="researcher",
    tools=[web_search],
    system_prompt="Use tools like web_search to gather relevant knowledge."
)
# Summarizer
summarizer = Agent(
    name="summarizer",
    system_prompt="Summarize the findings into a clear answer."
)
# Multi-agent workflow
graph = agent_graph(
    {
        "planner": planner,
        "researcher": researcher,
        "summarizer": summarizer
    },
    edges=[
        ("planner", "researcher"),
        ("researcher", "summarizer")
    ]
)
result = graph.run("Explain the latest breakthroughs in AI for drug discovery.")
print(result)

In the above —

  • The planner decomposes the request
  • The researcher uses a tool (e.g. web_search)
  • The summarizer produces a polished response

Pattern 7: Reflection (Self-Agent)

Strands even allows a “thinking tool” for deeper reflection. This resembles a Chain of Responsibility: before acting, the agent may delegate to a reflective sub-agent to double-check reasoning.

The idea is before acting (tool use, response), the agent calls a “reflection tool” (or a sub-agent) that double-checks or critiques the reasoning.

Here are two styles of implementation:

1. Reflection as a Tool

You can register a special tool (@tool) that the model itself can invoke when it wants to “think deeper” before acting.

from strands import Agent, tool
# Define a reflection tool
@tool
def reflect_on_plan(plan: str) -> str:
    """
    Reflect on the current plan and suggest improvements.
    """
    if "ambiguous" in plan or len(plan.split()) < 5:
        return "This plan is weak or ambiguous. Suggest clarifying steps."
    return "The plan looks solid. Proceed."
# Define a normal task tool
@tool
def execute_task(task: str) -> str:
    """
    Execute the given task.
    """
    return f"Task executed: {task}"
# Agent with reflection and execution tools
agent = Agent(tools=[reflect_on_plan, execute_task])
response = agent.run("Plan: search ambiguous topic and summarize results")
print(response)

Here the model may choose to call reflect_on_plan first, then adjust its strategy before calling execute_task. That’s the Chain of Responsibility feel — an intermediate check before action.

2. Reflection as a Sub-Agent (Critic + Actor Pattern)

You can explicitly wire a critic agent (reflection agent) that validates the planner/actor agent’s output before execution.

from strands import Agent, agent_graph
# Actor Agent (creates a plan)
actor = Agent(
    name="planner",
    system_prompt="You are a planner. Propose step-by-step actions to solve tasks."
)
# Reflection Agent (critiques plan before execution)
reflector = Agent(
    name="reflector",
    system_prompt="""
    You are a critic. Review the planner's proposed actions.
    If risky, incomplete, or unclear, suggest corrections.
    Otherwise approve.
    """
)
# Execution Agent (does the actual work)
executor = Agent(
    name="executor",
    system_prompt="Execute only validated plans. Do not improvise."
)
# Compose them in a mini-agent graph
graph = agent_graph({
    "planner": actor,
    "reflector": reflector,
    "executor": executor
}, edges=[
    ("planner", "reflector"),
    ("reflector", "executor")
])
response = graph.run("Research the top 3 trends in quantum computing and summarize.")
print(response)

The flow is Planner → Reflector → Executor The reflector acts as a gatekeeper, either passing the plan along or sending corrections back.

Why Patterns Matter

  • 🔹 Separation of concerns → clean code
  • 🔹 Swap-ability → change models/tools easily
  • 🔹 Resilience → robust in production
  • 🔹 Composability → multi-agent teamwork

Conclusion

AWS Strands Agents is a framework for sure but you could also think of it as a design pattern library in disguise.

Of course this can be done with other frameworks as well and we will explore them in posts in the future.

By embracing Reactor, Strategy, Decorator, Adapter, Memento, Composite, Reflection, and Resilience patterns, Strands can make agent design modular, extensible, and production-ready.

The takeaway if you understand these patterns, you can design agents with clarity, not just hack them together.


메타데이터
post_id
3af94208b2a5
slug
design-patterns-in-action-inside-aws-strands-agents-3af94208b2a5
url
https://medium.com/@gunjanvi/design-patterns-in-action-inside-aws-strands-agents-3af94208b2a5
canonical_url
https://medium.com/@gunjanvi/design-patterns-in-action-inside-aws-strands-agents-3af94208b2a5
author_url
https://medium.com/@gunjanvi
status
ok
fetched_at
2026-06-22 05:41:33