Akshay Parkhi's Weblog

Scaling Agents: The Definitive Open-Source Guide — From 1 Agent to 100 Agents, 1 Tool to 100 Tools, Managing Context

7th March 2026

The Scaling Problem Space: Three Walls

ScaleWall 1: ToolsWall 2: AgentsWall 3: Context
Trivial1 tool1 agent4K tokens
Fine5 tools3 agents32K tokens
Danger zone15 tools — model confused10 agents — coordination hard128K tokens — risky
Broken50 tools — wrong tool selection50 agents — chaos500K+ tokens — overflow
Impossible without architecture100 tools100 agentsMulti-agent × long context
SolutionTool routing & namespacingHierarchy & graphsSummarization & scoping

Tool Wall: Every tool’s JSON schema goes into the system prompt. At 15+ tools, models start selecting wrong tools. At 50+, token usage explodes and accuracy plummets.

Agent Wall: N agents with full connectivity have N×(N-1)/2 communication links. 10 agents have 45 links. 100 agents have 4,950 links. Without hierarchy, coordination becomes impossible.

Context Wall: LLMs have finite context windows. As conversations grow, the window loses older information. When context propagates across agents, the problem multiplies.


Single Agent: Getting Started

LangGraph — Single Agent

# LangGraph uses StateGraph for agent construction
# Source: langgraph/graph/state.py - StateGraph class

from langgraph.graph import StateGraph, MessagesState, START, END
from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-4o")

class AgentState(MessagesState):
    pass

def agent_node(state: AgentState):
    response = model.invoke(state["messages"])
    return {"messages": [response]}

def should_continue(state: AgentState):
    last_message = state["messages"][-1]
    if last_message.tool_calls:
        return "tools"
    return END

# Build graph
graph = StateGraph(AgentState)
graph.add_node("agent", agent_node)
graph.add_node("tools", tool_node)
graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END})
graph.add_edge("tools", "agent")
app = graph.compile()

LangGraph Architecture (from source):

  • StateGraph compiles to Pregel (Google’s graph processing framework concept)
  • Channels (LastValue, BinaryOperatorAggregate) manage state
  • Each node reads from and writes to channels
  • LangGraph supports checkpointing via BaseCheckpointSaver (memory, SQLite, Postgres)

AutoGen — Single Agent

# AutoGen uses ChatAgent protocol
# Source: autogen-agentchat/src/autogen_agentchat/base

from autogen_agentchat.agents import AssistantAgent
from autogen_ext.models.openai import OpenAIChatCompletionClient

model_client = OpenAIChatCompletionClient(model="gpt-4o")

agent = AssistantAgent(
    name="assistant",
    model_client=model_client,
    tools=[my_tool],
    system_message="You are a helpful assistant.",
)

result = await agent.run(task="What is 2+2?")

AutoGen Architecture (from source):

  • Two-layer design: autogen-core (message passing runtime) and autogen-agentchat (high-level API)
  • Core uses AgentRuntime with pub/sub messaging (SingleThreadedAgentRuntime, GrpcWorkerAgentRuntimeHost)
  • AgentChat provides ChatAgent protocol that wraps core agents

CrewAI — Single Agent

# CrewAI uses Agent + Task + Crew trinity
# Source: crewai/crew.py, crewai/task.py

from crewai import Agent, Task, Crew

agent = Agent(
    role="Researcher",
    goal="Find accurate information",
    backstory="Expert researcher with 10 years experience",
    tools=[search_tool],
    llm="gpt-4o",
)

task = Task(
    description="Research the latest AI trends",
    expected_output="A comprehensive report",
    agent=agent,
)

crew = Crew(
    agents=[agent],
    tasks=[task],
    process=Process.sequential,
)

result = crew.kickoff()

CrewAI Architecture (from source):

  • Crew orchestrates Agents that execute Tasks
  • Process enum: sequential or hierarchical
  • Flow class for complex DAG workflows with @start, @listen, @router decorators
  • Built-in evaluation via AgentEvaluator with metric categories: TOOL_SELECTION, REASONING, SEMANTIC_QUALITY, GOAL

Strands Agents — Single Agent

# Strands uses a simple Agent class
# Source: strands-agents/src/strands/agent/agent.py

from strands import Agent, tool

@tool
def calculate(expression: str) -> str:
    """Calculate a mathematical expression."""
    return str(eval(expression))

agent = Agent(
    model="us.amazon.nova-pro-v1:0",
    system_prompt="You are a math assistant.",
    tools=[calculate],
)

result = agent("What is 15 * 23?")

Strands Architecture (from source):

  • Minimal core: Agent with tools, model, and conversation manager
  • Tools via @tool decorator or MCP servers
  • Conversation management: SlidingWindowConversationManager, SummarizingConversationManager
  • Multi-agent: Swarm (dynamic handoffs) and Graph (deterministic DAG)
  • Session persistence via SessionManager hook system

Agno — Single Agent

# Agno uses Agent with rich configuration
# Source: agno/agent/agent.py

from agno.agent import Agent
from agno.models.openai import OpenAIChat
from agno.tools.duckduckgo import DuckDuckGoTools

agent = Agent(
    model=OpenAIChat(id="gpt-4o"),
    tools=[DuckDuckGoTools()],
    instructions=["Always verify facts"],
    markdown=True,
    show_tool_calls=True,
)

agent.print_response("Latest news on AI agents")

Agno Architecture (from source):

  • Agent dataclass with extensive configuration (200+ params)
  • Team class with TeamMode: coordinate, route, broadcast, tasks
  • Workflow for complex multi-step processes with Step definitions
  • Built-in memory via MemoryManager, storage via BaseDb, knowledge via KnowledgeProtocol

DSPy — Single Module/Agent

# DSPy uses Modules with Signatures
# Source: dspy/predict/react.py

import dspy

lm = dspy.LM("openai/gpt-4o")
dspy.configure(lm=lm)

def get_weather(city: str) -> str:
    """Get weather for a city."""
    return f"Sunny in {city}"

react = dspy.ReAct(
    signature="question -> answer",
    tools=[get_weather],
    max_iters=20,
)

result = react(question="What's the weather in Tokyo?")

DSPy Architecture (from source):

  • Module base class with composable Predict, ChainOfThought, ReAct
  • Signature defines typed input/output contracts
  • Tool wraps callables with name, description, args schema
  • Unique: Optimizers (MIPROv2, BootstrapFewShot) that automatically tune prompts
  • Evaluate module for systematic evaluation with metrics

Multi-Agent Patterns (From Source Code)

Pattern Taxonomy

┌─────────────────┬──────────────────┬────────────────┬──────────────────────┐
│ SEQUENTIAL      │ FAN-OUT/IN       │ HIERARCHICAL   │ DYNAMIC/SWARM        │
│ A → B → C       │    Aggregator    │   Manager      │   (self-organizing)  │
│                 │     ↗ ↑ ↖        │    / | \       │                      │
│ • LangGraph     │    A  B  C       │   A  B  C      │ • Strands Swarm      │
│ • CrewAI seq    │                  │                │ • AutoGen Swarm      │
│ • AgentScope    │ • AgentScope     │ • CrewAI hier  │ • Agno tasks mode    │
│   Sequential    │   Fanout         │ • Agno coord   │                      │
│   Pipeline      │ • LangGraph      │ • AutoGen      │                      │
│                 │   parallel       │   Selector     │                      │
├─────────────────┼──────────────────┼────────────────┼──────────────────────┤
│ GRAPH/DAG       │ ROUTER           │ NESTED TEAMS   │ DISTRIBUTED          │
│                 │                  │                │                      │
│ • LangGraph     │ • Agno route     │                │                      │
│ • Strands Graph │ • AutoGen        │ • AutoGen      │ • AutoGen gRPC       │
│ • AutoGen Graph │   Selector       │   nested teams │   Runtime            │
│                 │ • Strands        │ • Agno nested  │ • Mastra server      │
│                 │   (conditional)  │                │   adapters           │
└─────────────────┴──────────────────┴────────────────┴──────────────────────┘

LangGraph — Multi-Agent via Subgraphs

# LangGraph scales via subgraphs (graphs within graphs)
# Source: langgraph/graph/state.py - StateGraph supports nesting

# Each team is its own StateGraph
research_team = StateGraph(ResearchState)
research_team.add_node("searcher", search_agent)
research_team.add_node("analyst", analysis_agent)
research_compiled = research_team.compile()

writing_team = StateGraph(WritingState)
writing_team.add_node("writer", write_agent)
writing_compiled = writing_team.compile()

# Orchestrator graph uses teams as nodes
orchestrator = StateGraph(OrchestratorState)
orchestrator.add_node("research", research_compiled)  # Subgraph!
orchestrator.add_node("writing", writing_compiled)     # Subgraph!
orchestrator.add_edge("research", "writing")
app = orchestrator.compile(
    checkpointer=PostgresSaver(conn),  # Persistence
)

LangGraph Scaling Mechanisms (from source):

  • Subgraphs: StateGraph can contain other compiled StateGraphs
  • Namespaced State: Child graphs have isolated state via NS_SEP
  • Send API: Dynamically spawn parallel branches with Send(“node”, payload)
  • Checkpointing: PostgresSaver, SqliteSaver, MemorySaver for state persistence
  • Store: BaseStore (Postgres-backed) for cross-thread shared memory
  • RetryPolicy: Built-in retry with configurable backoff
  • CachePolicy: Cache node results to avoid recomputation
  • Command: Command(goto=..., update=...) for dynamic graph navigation

AutoGen — Multi-Agent Teams

# AutoGen provides multiple team patterns
# Source: autogen-agentchat/teams/_group_chat/

from autogen_agentchat.teams import (
    RoundRobinGroupChat,    # Agents take turns
    SelectorGroupChat,      # LLM selects next speaker
    Swarm,                  # Tool-based handoffs
    GraphFlow,              # DAG-based execution
)

# SelectorGroupChat - LLM picks next speaker
team = SelectorGroupChat(
    participants=[agent_a, agent_b, agent_c],
    model_client=selector_model,
    termination_condition=MaxMessageTermination(10),
    selector_prompt="Select the most appropriate agent...",
    allow_repeated_speaker=False,
)

# GraphFlow - DAG with conditional edges
builder = DiGraphBuilder()
builder.add_node(agent_a).add_node(agent_b).add_node(agent_c)
builder.add_edge(agent_a, agent_b)
builder.add_edge(agent_b, agent_a,
    condition=lambda msg: "loop" in msg.to_model_text())
builder.add_edge(agent_b, agent_c,
    condition=lambda msg: "done" in msg.to_model_text())

team = GraphFlow(
    participants=builder.get_participants(),
    graph=builder.build(),
    termination_condition=MaxMessageTermination(20),
)

# NESTED TEAMS - Teams as participants in other teams
inner_team = RoundRobinGroupChat(
    participants=[assistant, code_executor],
    termination_condition=TextMentionTermination("TERMINATE"),
)
outer_team = RoundRobinGroupChat(
    participants=[inner_team, reviewer],  # Team as participant!
    termination_condition=TextMentionTermination("TERMINATE"),
)

AutoGen Scaling Mechanisms (from source):

  • Team Nesting: Any Team implements ChatAgent, so teams compose into teams
  • AgentRuntime: Message-passing runtime (SingleThreadedAgentRuntime or GrpcWorkerAgentRuntimeHost)
  • Distributed Runtime: GrpcWorkerAgentRuntimeHost for cross-process agents
  • SelectorGroupChat: LLM-based dynamic routing with selector_func and candidate_func overrides
  • GraphFlow: DiGraphBuilder with conditional edges, activation groups, loop support
  • Termination Conditions: MaxMessageTermination, TextMentionTermination, composable with | and &
  • State Management: TeamState for pause/resume

CrewAI — Crews, Flows, and Hierarchical Process

# CrewAI multi-agent via Process and Flow
# Source: crewai/crew.py, crewai/flow/flow.py

# HIERARCHICAL PROCESS - Manager agent coordinates
crew = Crew(
    agents=[researcher, writer, editor],
    tasks=[research_task, write_task, edit_task],
    process=Process.hierarchical,
    manager_llm="gpt-4o",
    memory=True,
)

# FLOW - Complex DAG with @start, @listen, @router decorators
from crewai.flow.flow import Flow, start, listen, router

class ContentPipeline(Flow):
    @start()
    def research(self):
        crew = Crew(agents=[researcher], tasks=[research_task])
        return crew.kickoff()

    @listen(research)
    def write(self, research_result):
        crew = Crew(agents=[writer], tasks=[write_task])
        return crew.kickoff(inputs={"research": research_result})

    @router(write)
    def quality_check(self, write_result):
        if quality_score(write_result) > 0.8:
            return "publish"
        return "revise"

    @listen("publish")
    def publish(self, content):
        return publish_content(content)

    @listen("revise")
    def revise(self, content):
        return self.write(content)

pipeline = ContentPipeline()
result = pipeline.kickoff()

CrewAI Scaling Mechanisms (from source):

  • Process Types: sequential (chain) and hierarchical (manager delegates)
  • Flow Engine: Full DAG workflow with @start, @listen, @router decorators
  • Memory System: MemoryScope with path-based scoping (/agent/1, /crew/shared)
  • Evaluation: AgentEvaluator with ToolSelectionEvaluator, ReasoningMetrics, GoalMetrics, SemanticQualityMetrics
  • Knowledge: BaseKnowledgeSource with RAG integration

Strands Agents — Graph and Swarm

# Strands provides two multi-agent patterns
# Source: strands/multiagent/graph.py, strands/multiagent/swarm.py

from strands import Agent
from strands.multiagent.graph import GraphBuilder

# GRAPH PATTERN - Deterministic DAG
builder = GraphBuilder()
math_node = builder.add_node(math_agent)
analysis_node = builder.add_node(analysis_agent)
summary_node = builder.add_node(summary_agent)

builder.add_edge(math_node, analysis_node)
builder.add_edge(analysis_node, summary_node)
builder.set_entry_point(math_node.node_id)

# Safety controls
builder.set_max_node_executions(50)
builder.set_execution_timeout(300.0)
builder.set_node_timeout(60.0)
builder.reset_on_revisit(True)

graph = builder.build()
result = graph("Calculate 15 * 23, analyze the result, then summarize")

# SWARM PATTERN - Dynamic agent handoffs
from strands.multiagent.swarm import Swarm

swarm = Swarm(
    nodes=[sales_agent, support_agent, billing_agent],
    entry_point=sales_agent,
    max_handoffs=20,
    max_iterations=20,
    execution_timeout=900.0,
    node_timeout=300.0,
    repetitive_handoff_detection_window=5,
    repetitive_handoff_min_unique_agents=3,
)

result = swarm("I need help with my bill")

# NESTED: Graph can contain Swarm as a node (and vice versa)
builder = GraphBuilder()
builder.add_node(swarm)  # Swarm as a graph node!
builder.add_node(summary_agent)
builder.add_edge("default_swarm", summary_agent)

Strands Scaling Mechanisms (from source):

  • GraphBuilder: Fluent API for DAG construction with add_node, add_edge, set_entry_point
  • GraphState: Tracks completed_nodes, failed_nodes, execution_order, accumulated_usage
  • Conditional Edges: GraphEdge.condition is Callable[[GraphState], bool]
  • SwarmState: Tracks current_node, shared_context, node_history, handoff_node
  • SharedContext: JSON-serializable key-value store shared across swarm agents
  • Repetitive Handoff Detection: Monitors recent agent history for stuck loops
  • Composability: You can nest one MultiAgentBase in another (Graph in Graph, Swarm in Graph)
  • A2A Protocol: strands/multiagent/a2a/ for inter-service agent communication

Agno — Teams with Multiple Modes

# Agno provides Team with configurable execution modes
# Source: agno/team/team.py, agno/team/mode.py

from agno.agent import Agent
from agno.team import Team, TeamMode

# COORDINATE MODE - Leader picks members, crafts tasks, synthesizes
team = Team(
    name="Research Team",
    mode=TeamMode.coordinate,
    members=[researcher, writer, editor],
    model=OpenAIChat(id="gpt-4o"),
    instructions=["Produce high-quality research reports"],
    max_iterations=10,
)

# ROUTE MODE - Leader routes to single specialist
team = Team(
    name="Support Router",
    mode=TeamMode.route,
    members=[billing_agent, tech_agent, general_agent],
    respond_directly=True,
)

# BROADCAST MODE - Same task to all members simultaneously
team = Team(
    name="Consensus Team",
    mode=TeamMode.broadcast,
    members=[analyst_1, analyst_2, analyst_3],
    delegate_to_all_members=True,
)

# TASKS MODE - Autonomous task decomposition and execution
team = Team(
    name="Project Team",
    mode=TeamMode.tasks,
    members=[designer, developer, tester],
    max_iterations=20,
    share_member_interactions=True,
)

# NESTED TEAMS
inner_team = Team(members=[agent_a, agent_b], mode=TeamMode.coordinate)
outer_team = Team(
    members=[inner_team, agent_c],  # Team as member!
    mode=TeamMode.coordinate,
)

Tool Scaling: 1 → 100 Tools

The Tool Scaling Problem

Every framework sends tool schemas to the LLM. More tools mean more tokens consumed and worse selection accuracy.

ToolsToken CostSelection Accuracy
1-5~500 tokens~98%
5-15~2K tokens~90%
15-30~5K tokens~75% (danger zone)
30-50~10K tokens~50% (effectively broken)
50+~20K+ tokens~30% (random guessing)

Strategy 1: Agent Specialization (All Frameworks)

# Instead of 1 agent with 30 tools, use 3 agents with 10 each

finance_agent = Agent(tools=[stock_price, portfolio, dividends, earnings, ...])  # 8 tools
data_agent = Agent(tools=[sql_query, csv_read, json_parse, aggregate, ...])     # 7 tools
email_agent = Agent(tools=[send_email, read_inbox, search_mail, ...])           # 5 tools

# Router decides which specialist handles the request
swarm = Swarm(
    nodes=[finance_agent, data_agent, email_agent],
    entry_point=finance_agent,
)

Strategy 2: Dynamic Tool Loading

# Strands - MCP servers load tools on-demand
agent = Agent(
    tools=[
        "mcp://localhost:3000/finance-tools",
        "mcp://localhost:3001/data-tools",
    ]
)

# LangGraph - Tools bound per-node, not per-graph
def research_node(state):
    model_with_tools = model.bind_tools([search, wiki_lookup])  # Only these tools
    return {"messages": [model_with_tools.invoke(state["messages"])]}

def analysis_node(state):
    model_with_tools = model.bind_tools([calculator, chart])  # Different tools
    return {"messages": [model_with_tools.invoke(state["messages"])]}

Strategy 3: Tool Namespacing and Categorization

# Agno - Toolkit grouping
from agno.tools import Toolkit

class FinanceToolkit(Toolkit):
    def __init__(self):
        super().__init__(name="finance")
        self.register(self.get_stock_price)
        self.register(self.get_portfolio)
        self.register(self.get_dividends)

    def get_stock_price(self, symbol: str) -> str: ...
    def get_portfolio(self, user_id: str) -> str: ...
    def get_dividends(self, symbol: str) -> str: ...

agent = Agent(tools=[FinanceToolkit()])

Tool Scaling Architecture

┌────────────────────────────────────────────────────────────────────┐
│                    TOOL SCALING ARCHITECTURE                       │
├────────────────────────────────────────────────────────────────────┤
│                                                                    │
│  Layer 1: TOOL REGISTRY (per agent, max 10-15)                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐          │
│  │Finance   │  │Data      │  │Email     │  │File      │          │
│  │Agent     │  │Agent     │  │Agent     │  │Agent     │          │
│  │8 tools   │  │7 tools   │  │5 tools   │  │6 tools   │          │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘          │
│                                                                    │
│  Layer 2: ROUTING (agent selection)                               │
│  ┌─────────────────────────────────────────────────────┐          │
│  │  Router Agent (0 tools, just routes)                │          │
│  │  "I need stock data" → Finance Agent                │          │
│  │  "Send an email" → Email Agent                      │          │
│  └─────────────────────────────────────────────────────┘          │
│                                                                    │
│  Layer 3: MCP SERVERS (external tool providers)                   │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐                        │
│  │Finance   │  │Database  │  │Cloud     │                        │
│  │MCP Server│  │MCP Server│  │MCP Server│                        │
│  │20 tools  │  │15 tools  │  │25 tools  │                        │
│  └──────────┘  └──────────┘  └──────────┘                        │
└────────────────────────────────────────────────────────────────────┘

Agent Scaling: 1 → 100 Agents

The Scaling Progression

AgentsPattern & Framework Recommendation
1Single agent with tools — any framework works
2-5Strands: Swarm with handoff tools / AutoGen: SelectorGroupChat / LangGraph: Conditional edges / Agno: Team(mode=route) / CrewAI: Hierarchical process
10-25Nested teams/subgraphs with department structure — LangGraph: Subgraphs / AutoGen: Nested teams / Strands: Graph containing Swarms / Agno: Nested Teams / CrewAI: Flow with multiple Crews
25-50Multi-level subgraph nesting — AutoGen: Distributed runtime (GrpcWorkerAgentRuntime) / Strands: A2A protocol / CrewAI: Flow orchestrating multiple Crews
50-100Distributed microservices with A2A/MCP protocols — AutoGen: GrpcWorkerAgentRuntimeHost / Strands: A2A server/client / Agno: RemoteTeam / Mastra: Server adapters

Why Hierarchy Works

FLAT (100 agents, full connectivity):
  Links = 100 × 99 / 2 = 4,950  ← UNMANAGEABLE

HIERARCHICAL (100 agents in 5 departments of 20):
  Each agent only knows ~20 others max
  Total effective links per agent: ~20 (not 99)

3-LEVEL HIERARCHY (5 divisions × 4 teams × 5 agents):
  Each agent sees at most: 4 peers + 1 lead = 5 agents (not 99!)

Implementation: Nested Hierarchy

# AutoGen — Nested Teams (cleanest nesting support)
researcher = AssistantAgent("researcher", model_client=client)
writer = AssistantAgent("writer", model_client=client)
code_gen = AssistantAgent("code_gen", model_client=client)
code_review = AssistantAgent("code_review", model_client=client)
designer = AssistantAgent("designer", model_client=client)
qa_tester = AssistantAgent("qa_tester", model_client=client)

# Level 2: Department teams
content_team = SelectorGroupChat(
    participants=[researcher, writer],
    model_client=selector_client,
    termination_condition=MaxMessageTermination(6),
)

engineering_team = RoundRobinGroupChat(
    participants=[code_gen, code_review],
    termination_condition=MaxMessageTermination(6),
)

design_team = RoundRobinGroupChat(
    participants=[designer, qa_tester],
    termination_condition=MaxMessageTermination(4),
)

# Level 3: Organization (teams as participants)
organization = SelectorGroupChat(
    participants=[content_team, engineering_team, design_team],
    model_client=executive_client,
    termination_condition=MaxMessageTermination(20),
)

result = await organization.run(task="Build a landing page")
# Strands — Nested Graph + Swarm

# Department as Swarm
support_swarm = Swarm(
    nodes=[billing_agent, tech_agent, general_agent],
    entry_point=general_agent,
    max_handoffs=10,
)

# Organization as Graph containing Swarms
org_graph = GraphBuilder()
intake_node = org_graph.add_node(intake_agent)
support_node = org_graph.add_node(support_swarm)  # Swarm as node!
escalation_node = org_graph.add_node(escalation_agent)

org_graph.add_edge(intake_node, support_node)
org_graph.add_edge(support_node, escalation_node,
    condition=lambda state: "escalation" in str(state.results))
org_graph.set_entry_point(intake_node.node_id)
org = org_graph.build()

Distributed Agent Scaling

# AutoGen — Distributed via gRPC Runtime
# Host process
from autogen_ext.runtimes.grpc import GrpcWorkerAgentRuntimeHost

host = GrpcWorkerAgentRuntimeHost(address="localhost:50051")
host.start()

# Worker process 1
from autogen_ext.runtimes.grpc import GrpcWorkerAgentRuntime

runtime = GrpcWorkerAgentRuntime(host_address="localhost:50051")
runtime.start()
await Writer.register(runtime, "writer", lambda: Writer(...))

# Worker process 2
runtime = GrpcWorkerAgentRuntime(host_address="localhost:50051")
runtime.start()
await Editor.register(runtime, "editor", lambda: Editor(...))

Context Window Management

The Context Problem

┌──────────────────────────────────────────────────────────────┐
│              CONTEXT WINDOW CONSUMPTION                       │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  System prompt:           ~500-2000 tokens                   │
│  Tool schemas (10 tools): ~2000 tokens                       │
│  Conversation history:    GROWS UNBOUNDED                    │
│  Tool results:            500-10000 tokens EACH              │
│  Cross-agent context:     MULTIPLIED per agent               │
│                                                              │
│  Example: 5 agents × 10 tools × 20 turns                    │
│  = 34,000 × 5 = 170,000 tokens ← OVER most context windows  │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Strands: Conversation Managers

# 1. SLIDING WINDOW - Drop old messages
from strands.agent.conversation_manager import SlidingWindowConversationManager

agent = Agent(
    conversation_manager=SlidingWindowConversationManager(
        window_size=20,
    ),
)

# 2. SUMMARIZING - Compress old context into summary
from strands.agent.conversation_manager import SummarizingConversationManager

agent = Agent(
    conversation_manager=SummarizingConversationManager(
        summary_ratio=0.3,
        summary_agent=Agent(model="us.amazon.nova-lite-v1:0"),
    ),
)

# 3. NULL - No management (for short tasks)
from strands.agent.conversation_manager import NullConversationManager
agent = Agent(conversation_manager=NullConversationManager())

LangGraph: State Reducers and Message Trimming

from langgraph.graph import MessagesState
from langchain_core.messages import trim_messages

class AgentState(MessagesState):
    summary: str

def summarize_conversation(state):
    messages = state["messages"]
    if len(messages) > 20:
        summary = llm.invoke(f"Summarize: {messages[:10]}")
        return {
            "messages": messages[10:],
            "summary": summary.content,
        }
    return state

# Or use built-in trimming
trimmed = trim_messages(
    messages,
    max_tokens=4000,
    strategy="last",
    token_counter=ChatOpenAI(model="gpt-4o"),
)

AutoGen: Model Context

from autogen_core.model_context import (
    UnboundedChatCompletionContext,
    BufferedChatCompletionContext,
)

agent = AssistantAgent(
    "assistant",
    model_client=client,
    model_context=BufferedChatCompletionContext(buffer_size=20),
)

Cross-Agent Context Strategies

┌─────────────────────────────────────────────────────────────────────┐
│            CONTEXT ISOLATION vs SHARING STRATEGIES                   │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  FULL ISOLATION (Graph pattern)                                     │
│  Agent A ──output only──> Agent B ──output only──> Agent C          │
│  Each agent has own context. Only final output passed forward.      │
│                                                                     │
│  SHARED CONTEXT (Swarm pattern)                                     │
│  SharedContext: {"agent_a": {"key": "value"}}                       │
│  All agents read/write to shared JSON key-value store.              │
│                                                                     │
│  SCOPED SHARING (CrewAI Memory pattern)                             │
│  /global/          ← All agents can read                            │
│  /department/eng/  ← Only engineering agents                        │
│  /agent/coder/     ← Only the coder agent                          │
│                                                                     │
│  BROADCAST (AgentScope MsgHub)                                      │
│  All messages visible to all participants (like a chat room)        │
│  Simple but O(N²) context growth                                    │
└─────────────────────────────────────────────────────────────────────┘

Memory Systems Across Frameworks

LayerFramework Support
Layer 1: Working Memory (conversation history)All frameworks: messages list / Strands: SummarizingConversationManager / AutoGen: BufferedChatCompletionContext / Agno: CompressionManager / AgentScope: InMemoryMemory, RedisMemory / LangGraph: trim_messages
Layer 2: Shared Team MemoryStrands: SharedContext (JSON KV in Swarm) / LangGraph: Graph state (channels with reducers) / AutoGen: Shared message history in group chats / CrewAI: MemoryScope (/crew/shared/) / Agno: share_member_interactions
Layer 3: Session PersistenceLangGraph: PostgresSaver, SqliteSaver / Strands: SessionManager hook system / AutoGen: TeamState for pause/resume / AgentScope: SQLAlchemy-backed WorkingMemory
Layer 4: Long-Term Knowledge (Vector DB / RAG)Agno: 20+ vector DB integrations (pgvector, pinecone, qdrant, etc.) / CrewAI: Knowledge with BaseKnowledgeSource + RAG / LangGraph: BaseStore with embedding search / AgentScope: LongTermMemory via Mem0 / Strands: MCP-based knowledge tools

Verification & Testing Scaled Agents

CrewAI: Built-in Evaluation

from crewai.evaluation import AgentEvaluator
from crewai.evaluation.metrics import (
    ToolSelectionEvaluator,
    ReasoningMetrics,
    GoalMetrics,
    SemanticQualityMetrics,
)

evaluator = AgentEvaluator(
    agents=[agent1, agent2],
    evaluators=[
        ToolSelectionEvaluator(),
        ReasoningMetrics(),
        GoalMetrics(),
        SemanticQualityMetrics(),
    ],
)

# A/B testing with ExperimentRunner
from crewai.experimental.evaluation.experiment import ExperimentRunner

runner = ExperimentRunner(
    crew=crew,
    iterations=10,
    inputs_list=[{"topic": "AI"}, {"topic": "ML"}, ...],
)
results = runner.run()

DSPy: Systematic Optimization and Evaluation

import dspy

def accuracy_metric(example, prediction, trace=None):
    return example.answer == prediction.answer

# Evaluate module
evaluator = dspy.Evaluate(
    devset=dev_examples,
    metric=accuracy_metric,
    num_threads=4,
)
score = evaluator(my_module)

# Auto-tune prompts for better performance
from dspy.teleprompt import MIPROv2

optimizer = MIPROv2(
    metric=accuracy_metric,
    auto="medium",
)
optimized_module = optimizer.compile(
    my_module,
    trainset=train_examples,
)

Verification Checklist for Scaled Systems

┌─────────────────────────────────────────────────────────────────────┐
│              SCALED AGENT VERIFICATION CHECKLIST                    │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  1. TOOL SELECTION ACCURACY                                        │
│     □ Each agent selects correct tool >90% of the time             │
│     □ No agent has more than 15 tools                              │
│     □ Tool descriptions are unambiguous                            │
│                                                                     │
│  2. ROUTING ACCURACY                                               │
│     □ Correct agent selected >90% of the time                      │
│     □ No infinite handoff loops (repetitive_handoff_detection)     │
│                                                                     │
│  3. CONTEXT INTEGRITY                                              │
│     □ Critical information survives summarization                   │
│     □ Cross-agent handoffs don't lose key data                     │
│     □ Context window never exceeds model limit                     │
│                                                                     │
│  4. SAFETY & BOUNDS                                                │
│     □ max_handoffs prevents infinite loops                         │
│     □ execution_timeout prevents runaway                           │
│     □ max_turns prevents infinite chat                             │
│     □ max_iterations prevents infinite loops                       │
│                                                                     │
│  5. COST & LATENCY                                                 │
│     □ Total tokens per request within budget                       │
│     □ P95 latency acceptable for use case                          │
│     □ Cheap models used for routing/summarization                  │
│                                                                     │
│  6. STATE PERSISTENCE                                              │
│     □ Sessions survive process restarts                            │
│     □ Interrupted workflows resume correctly                       │
│     □ State doesn't grow unbounded                                 │
│                                                                     │
│  7. OUTPUT QUALITY                                                 │
│     □ Output meets stated goal (CrewAI GoalMetrics)                │
│     □ Output is semantically coherent (SemanticQualityMetrics)     │
│     □ Reasoning is logical (ReasoningMetrics)                      │
└─────────────────────────────────────────────────────────────────────┘

Testing Strategy by Scale

# LEVEL 1: Unit Test Individual Agents
def test_single_agent_tool_selection():
    agent = Agent(tools=[search, calculate])
    result = agent("What is 2+2?")
    assert "calculate" in str(result.tool_calls)

# LEVEL 2: Integration Test Agent Pairs
def test_handoff():
    swarm = Swarm(
        nodes=[router_agent, specialist_agent],
        max_handoffs=3,
    )
    result = swarm("billing question")
    assert "specialist" in str(result.node_history)

# LEVEL 3: System Test Full Pipeline
def test_full_pipeline_e2e():
    graph = build_full_pipeline()
    test_cases = [
        {"input": "Research AI trends", "expected_nodes": 3},
        {"input": "Debug this code", "expected_nodes": 2},
    ]
    for tc in test_cases:
        result = graph(tc["input"])
        assert len(result.execution_order) == tc["expected_nodes"]

# LEVEL 4: Load Test
async def test_concurrent_requests():
    graph = build_production_graph()
    tasks = [graph.invoke_async(f"Request {i}") for i in range(50)]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    success_rate = sum(1 for r in results if not isinstance(r, Exception)) / len(results)
    assert success_rate > 0.95

Observability & Monitoring

FrameworkObservability Features
LangGraphLangSmith integration (traces, runs) / Checkpoint inspection (get_state, get_state_history) / Stream events per node
AutoGenOpenTelemetry tracing / Agent registry / Message-level logging
CrewAIEvent bus (crewai_event_bus) with typed events / TraceCollectionListener for OpenTelemetry / AgentEvaluator with 4 metric categories / ExperimentRunner for A/B testing
StrandsHook system (Before/After events for all ops) / OpenTelemetry tracing (get_tracer) / GraphState/SwarmState with execution metrics
AgnoOpenTelemetry tracing / RunMetrics, SessionMetrics tracking / Registry for agent discovery
AgentScopeOpenTelemetry tracing module / Trace extractors and converters / Token counters per model provider
DSPydspy.inspect_history() for prompt debugging / Evaluate module with metrics / Cost tracking, latency monitoring
LangfuseUniversal observability layer / Prompt versioning, generations tracking / Works with ALL frameworks above

Key Metrics to Track

metrics = {
    # Per-request metrics
    "total_tokens": 0,
    "total_cost_usd": 0.0,
    "latency_ms": 0,
    "num_agents_invoked": 0,
    "num_tool_calls": 0,
    "num_handoffs": 0,

    # Quality metrics
    "goal_achieved": True,
    "tool_accuracy": 0.95,

    # Safety metrics
    "loop_detected": False,
    "context_overflow": False,
    "max_depth_reached": False,
}

Framework Comparison Matrix

FeatureLangGraphAutoGenCrewAIStrandsAgnoAgentScopeDSPy
Single AgentStateGraphAssistant AgentAgent+TaskAgentAgentAgentBaseModule
Multi-AgentSubgraphsTeamsCrew+FlowGraph+SwarmTeamPipeline+MsgHubCompose modules
DAG/GraphNativeGraphFlowFlowGraphBuilder-Seq+Fanout-
Dynamic RoutingConditional edgesSelectorGroupChatHierarchicalSwarmTeamMode.routeMsgHub-
DistributedLangGraph PlatformgRPC Runtime-A2A ProtocolRemoteTeam--
CheckpointingNative (Postgres/SQLite)TeamStateFlowPersistenceSessionManagerSessionManagerSQLAlchemy-
EvaluationManualManualBuilt-in 4 metricsHooksBaseEvalTunerNative Evaluate+Optimize
Safety GuardsRetry+CacheTermination CondGuardrailsMax handoffs+timeoutGuardrails-max_iters
LanguagesPython+JSPython+.NETPythonPythonPythonPythonPython
Best ForComplex stateful workflowsEnterprise distributedQuick teams w/evalAWS/prod agentsFull-stack agentsResearch+distribPrompt optim+eval

Step-by-Step Scaling Playbook

Phase 1: Single Agent (Week 1)

  1. Pick your framework based on needs
  2. Build a single agent with 3-5 tools
  3. Test tool selection accuracy (should be >95%)
  4. Add conversation management (sliding window or summarizing)
  5. Add session persistence

Phase 2: Agent Pair (Week 2-3)

  1. Identify a natural split (Research → Analysis, Generate → Review, Route → Specialist)
  2. Build the pair: Sequential (Graph/Pipeline) or Router + Specialist (Swarm/Selector)
  3. Define the handoff contract with structured output (Pydantic models)
  4. Test:
    • The correct agent is selected
    • Context transfers completely
    • No infinite loops occur

Phase 3: Small Team (Week 4-6)

  1. Organize: 3-4 specialists with 5-8 tools each, plus 1 router/coordinator
  2. Choose a pattern:
    • Known workflow → Graph
    • Dynamic routing → Swarm
    • Both → Swarm with conditional edges
  3. Add safety: max_handoffs=15, execution_timeout=300, repetitive_handoff_detection_window=5
  4. Add shared context (Strands SharedContext / LangGraph state channels / CrewAI Memory scopes)

Phase 4: Department Structure (Week 7-10)

Orchestrator (1 agent)
├── Research Team (5 agents)
│   ├── Web Researcher
│   ├── Paper Analyst
│   ├── Data Collector
│   ├── Fact Checker
│   └── Summarizer
├── Engineering Team (5 agents)
│   ├── Architect
│   ├── Frontend Dev
│   ├── Backend Dev
│   ├── Code Reviewer
│   └── DevOps
└── QA Team (3 agents)
    ├── Test Designer
    ├── Test Runner
    └── Bug Reporter

Each team is self-contained:

  • It has its own shared context.
  • It has its own conversation management.
  • It has its own safety limits.
  • It exposes a single interface to the orchestrator.

Teams communicate with each other through structured outputs only.

Phase 5: Organization Scale (Week 11+)

┌─────────────┐
│ Orchestrator │ (API Gateway)
│ Service      │
└──────┬───────┘
       │ A2A/gRPC/REST
┌──────┼──────┬───────────┐
│      │      │           │
▼      ▼      ▼           ▼
┌─────┐┌─────┐┌─────┐  ┌─────┐
│Team ││Team ││Team │  │Team │
│Svc 1││Svc 2││Svc 3│  │Svc 4│
└─────┘└─────┘└─────┘  └─────┘

Move to a distributed architecture (AutoGen GrpcWorkerAgentRuntimeHost, Strands A2A protocol, Agno RemoteTeam). Each team runs as an independent service. Add observability (Langfuse, OpenTelemetry). Add rate limiting and cost controls.


Production Architecture Patterns

The “Golden Path” Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                    PRODUCTION MULTI-AGENT SYSTEM                    │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌──────────────────────────┐                                      │
│  │     API Gateway          │ ← Rate limiting, auth                │
│  └────────────┬─────────────┘                                      │
│               │                                                     │
│  ┌────────────┼────────────┬────────────────┐                      │
│  │            │            │                │                      │
│  ▼            ▼            ▼                ▼                      │
│ ┌────────┐ ┌────────┐ ┌────────┐     ┌────────┐                  │
│ │Research│ │Execute │ │Support │     │Analysis│  ← Each is a      │
│ │Team    │ │Team    │ │Team    │ ... │Team    │    self-contained  │
│ │3-5 agt │ │3-5 agt │ │3-5 agt │     │3-5 agt │    Graph or Swarm │
│ │8-12    │ │8-12    │ │8-12    │     │8-12    │                   │
│ │tools   │ │tools   │ │tools   │     │tools   │                   │
│ └────────┘ └────────┘ └────────┘     └────────┘                   │
│                                                                     │
│  ┌──────────────────────────────────────────┐                      │
│  │  Shared Infrastructure                   │                      │
│  │  ┌─────────┐ ┌──────────┐ ┌───────────┐ │                      │
│  │  │Session  │ │Memory/   │ │Observabil-│ │                      │
│  │  │Store    │ │Knowledge │ │ity (OTel) │ │                      │
│  │  │(Postgres│ │(VectorDB)│ │(Langfuse) │ │                      │
│  │  └─────────┘ └──────────┘ └───────────┘ │                      │
│  └──────────────────────────────────────────┘                      │
└─────────────────────────────────────────────────────────────────────┘

Cost Optimization Rules

# RULE 1: Use cheap models for routing/summarization (GPT-4o-mini, Nova Lite)
# RULE 2: Cache tool results aggressively
# RULE 3: Don't run all agents — route and run the 3-5 that matter
# RULE 4: Structured output reduces token waste

from pydantic import BaseModel

class ResearchResult(BaseModel):
    summary: str
    key_findings: list[str]
    confidence: float

agent = Agent(output_type=ResearchResult)  # No rambling

The 8 Golden Rules of Agent Scaling

┌─────────────────────────────────────────────────────────────────────┐
│                    8 GOLDEN RULES                                   │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  1. NO AGENT SEES MORE THAN 15 TOOLS                               │
│     Split into specialists. Use routing.                           │
│                                                                     │
│  2. EVERY AGENT HAS CONVERSATION MANAGEMENT                        │
│     Sliding window or summarization. Never unbounded.              │
│                                                                     │
│  3. TOOL RESULTS SHOULD BE < 2K TOKENS                             │
│     Truncate or summarize large results.                           │
│                                                                     │
│  4. EVERY GRAPH HAS TIMEOUT + MAX NODE LIMIT                       │
│     Strands: execution_timeout + max_node_executions               │
│     AutoGen: termination_condition + max_turns                     │
│     CrewAI: max_iterations                                         │
│                                                                     │
│  5. EVERY SWARM HAS MAX_HANDOFFS + LOOP DETECTION                  │
│     Strands: max_handoffs + repetitive_handoff_detection_window    │
│     AutoGen: max_turns + termination_condition                     │
│                                                                     │
│  6. USE STRUCTURED OUTPUT FOR AGENT-TO-AGENT DATA                   │
│     Pydantic models, not free-text.                                │
│                                                                     │
│  7. USE CHEAP MODELS FOR INFRASTRUCTURE                             │
│     Routing, summarization, classification → GPT-4o-mini / Nova    │
│     Actual expert work → GPT-4o / Claude                           │
│                                                                     │
│  8. HIERARCHY BEATS FLAT NETWORKS — ALWAYS                          │
│     5 teams of 20 > 100 agents in a flat swarm.                    │
│     Each team: self-contained, own context, own safety limits.     │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Quick Reference: Which Framework for What

“I need...”Use This
Complex stateful workflows with checkpointingLangGraph
Quick team setup with built-in evaluationCrewAI
Enterprise distributed systemAutoGen
AWS-native production systemStrands
Full-stack Python with UI + DB + vectorsAgno
Prompt optimization at scaleDSPy
Research/prototyping multi-agentAgentScope
TypeScript/JavaScript agentsMastra
Observability for any frameworkLangfuse
Cross-framework agent communicationA2A Protocol

Generated from source code analysis of 8 open-source frameworks. Last updated: March 7, 2026.

More recent articles

This is Scaling Agents: The Definitive Open-Source Guide — From 1 Agent to 100 Agents, 1 Tool to 100 Tools, Managing Context by Akshay Parkhi, posted on 7th March 2026.

Next: How Pi Builds Its System Prompt at Runtime — And the Innovations That Make It Stand Out

Previous: Finding the Perfect Prompt: Combining DSPy's Optimization with Strands Agents for Cost-Effective Multi-Agent Systems