šŸ¤– Agents

Agents — 5 Documents

Agent Architectures, Multi-Agent, Frameworks, MCP/ACP, Tools

5
Documents
8,090
Total Lines

šŸ“„ AI Agents vs AI Orchestrators: A Comprehensive Guide — 1064 lines

ACPAgentDPOGPTGovernanceInferenceLLMMCPMulti-AgentMultimodal

1. What is an AI Agent

1.1 Definition

An AI Agent is an autonomous software entity that perceives its environment, processes information, makes decisions, and takes actions to achieve specific goals. Unlike traditional software programs that follow fixed, deterministic instruction paths, AI agents leverage large language models (LLMs) or other AI models to reason about their context, select appropriate actions, and adapt their behavior dynamically.

Formally, an AI agent can be described as a system that:

  • Senses its environment via inputs (text, API responses, sensor data, file contents)
  • Thinks by processing input through a reasoning engine (typically an LLM)
  • Acts by executing tool calls, generating outputs, or modifying its environment
  • Learns from feedback and past experiences to improve future performance

An agent operates within a perception-action cycle: it observes, reasons about what to do next, executes an action, observes the result, and repeats.

1.2 Core Characteristics

Autonomy

Autonomy is the defining characteristic of an AI agent. An autonomous agent operates without human intervention for extended periods. It makes independent decisions about:

  • Which tool to call next
  • When to ask for clarification versus proceed with available information
  • How to recover from errors or unexpected states
  • Whether a goal has been sufficiently achieved

The degree of autonomy varies: some agents require human-in-the-loop approval for critical actions, while others operate fully autonomously.

Tool-Use

Tool-use is what transforms an LLM from a static text generator into an active agent. Agents can call external tools and APIs to:

  • Read files and databases
  • Execute code in sandboxed environments
  • Search the web or internal knowledge bases
  • Send emails, messages, or API requests
  • Interact with SaaS platforms (Jira, GitHub, Slack, Asana, Salesforce)
  • Run shell commands and scripts
  • Query vector databases for semantic search

Tool-use is typically exposed through a function-calling interface where the LLM outputs structured JSON specifying which function to call and with what parameters.

Reasoning

Agents employ various reasoning strategies to decide what to do:

  • Chain-of-Thought (CoT): Step-by-step logical reasoning before answering
  • Tree-of-Thought (ToT): Exploring multiple reasoning branches simultaneously
  • ReAct: Interleaving reasoning traces with actions (see Section 1.4)
  • Reflection: Critiquing and revising past outputs
  • Self-Consistency: Generating multiple reasoning paths and selecting the most common answer

Memory

Memory enables agents to maintain context across interactions and learn from experience. Modern agents implement multiple memory layers:

  • Short-term / Context Memory: The current conversation or task session; stored in the LLM's context window
  • Long-term Memory: Persistent storage of facts, user preferences, and past learnings, often via vector databases (e.g., ChromaDB, Pinecone, Weaviate)
  • Episodic Memory: Logs of past actions, observations, and outcomes used for reflection
  • Working Memory: Temporary storage for intermediate computation results during a single task

Memory is typically managed through Retrieval-Augmented Generation (RAG) pipelines that fetch relevant past context before each reasoning step.

Planning

Planning allows agents to decompose complex goals into manageable sub-steps. Common planning strategies:

  • Flat Planning: Generate a complete step-by-step plan upfront, then execute
  • Hierarchical Planning: Decompose goals into sub-goals, each handled by sub-plans
  • Dynamic Replanning: Adjust the plan based on intermediate results or failures
  • Monte Carlo Tree Search (MCTS): Explore multiple possible action sequences and select the most promising path

1.3 Types of AI Agents

AI agents can be classified by their internal architecture and decision-making approach:

Reactive Agents

Reactive agents operate on a simple stimulus-response basis. They do not maintain internal state or memory of past actions.

  • How they work: Map current percept directly to action via condition-action rules
  • Strengths: Fast, computationally cheap, predictable
  • Weaknesses: No learning, no planning, no memory
  • Example: A simple chatbot that routes customer queries to the right department based on keyword matching; a reflex-based game-playing agent

Goal-Oriented Agents (Goal-Based Agents)

Goal-oriented agents maintain a representation of desired states (goals) and reason about how to achieve them.

  • How they work: The agent considers the current state, evaluates possible actions, and selects those that move it closer to the goal
  • Strengths: Can handle complex tasks with clear objectives, capable of planning
  • Weaknesses: Goal specification must be precise; may struggle with ambiguous objectives
  • Example: A travel booking agent that finds the cheapest flight meeting user constraints; a code-generation agent that writes tests to achieve code coverage goals

Utility-Based Agents

Utility-based agents go beyond binary goal satisfaction by assigning a utility score to each possible state. They choose actions that maximize expected utility.

  • How they work: For each possible action, the agent estimates the probability of various resulting states and their utilities, then selects the action with the highest expected utility
  • Strengths: Handles trade-offs naturally, can operate in environments with uncertainty
  • Weaknesses: Requires a well-defined utility function, which can be difficult to specify
  • Example: A trading agent that balances risk and return; a scheduling agent that optimizes for cost, time, and customer satisfaction simultaneously

Learning Agents

Learning agents improve their performance over time through experience. They typically have four components: a learning element, a critic, a performance element, and a problem generator.

  • How they work: The agent performs actions, receives feedback (rewards or penalties), and updates its internal model to improve future decisions
  • Strengths: Adapts to new environments, improves with data, handles changing conditions
  • Weaknesses: Requires training data or feedback loops, slower initial performance
  • Example: Reinforcement learning agents for game-playing (AlphaGo); a code-review agent that learns company-specific conventions from past reviews; a conversational agent that improves from user feedback

1.4 Agent Architectures

ReAct (Reasoning + Acting)

ReAct, proposed by Yao et al. (2022), interleaves reasoning traces with action execution. Instead of generating a full plan upfront, the agent alternates between:

  1. Thought: Reasoning about the current state and what information is needed
  2. Action: Calling a tool or performing an operation
  3. Observation: Processing the result of the action
  4. Repeat: Until the goal is achieved
Example ReAct Loop (Question: "What is the population of the capital of France?")

Thought: I need to find the capital of France first.
Action: search("capital of France")
Observation: Paris
Thought: Now I need to find the population of Paris.
Action: search("population of Paris")
Observation: 2.16 million
Thought: I have the answer.
Final Answer: The population of Paris, the capital of France, is approximately 2.16 million.

ReAct is the most widely used agent architecture, forming the basis of LangChain agents, OpenAI Assistants, and many custom implementations. Its advantage is that it naturally handles multi-step tasks while maintaining transparency in its reasoning process.

Plan-and-Execute

Plan-and-Execute architectures separate planning from execution into two distinct phases:

  1. Plan Phase: The agent analyzes the goal and generates a complete, ordered plan of subtasks
  2. Execute Phase: The agent executes each subtask, potentially with verification steps between them

This approach is particularly effective for tasks where the overall structure is known in advance (e.g., data pipelines, report generation). The plan can be re-generated mid-execution if unexpected results are encountered.

Variants include:
- Plan-and-Solve: The agent first creates a detailed plan, then executes it step by step, verifying each step before proceeding
- LLM Compiler: Treats the plan as a program where each step is a function call, with dependencies tracked via a DAG

Reflection

Reflection architectures add a self-critique loop to the agent's process:

  1. Act: The agent performs a task and produces output
  2. Reflect: The agent reviews its own output, identifies errors or improvements
  3. Revise: The agent produces a corrected or improved version
  4. Evaluate: A scoring function determines if the output meets quality thresholds

This architecture is commonly used in:
- Code generation: Generate code, run tests, reflect on failures, fix bugs
- Writing tasks: Write draft, critique, revise, repeat
- Multi-agent debate: Multiple agents critique each other's outputs to converge on a better answer

Hybrid Architectures

Most production agents combine elements from multiple architectures:

  • ReAct + Reflection: After each action-observation step, reflect on whether the action was effective
  • Plan-and-Execute + ReAct: Create a high-level plan, then use ReAct loops for each step
  • Plan-and-Execute + Reflection: Plan, execute, reflect on the overall result, and replan if needed

1.5 Popular Agent Frameworks

LangChain / LangGraph

LangChain (Python/JS) is the most widely adopted framework for building LLM-powered agents.

  • Key Concepts: Chains, Agents, Tools, Memory, Callbacks
  • LangGraph extension: Enables cyclic graphs (loops, branching) for multi-step agent workflows
  • Strengths: Huge ecosystem of integrations (300+ tools), extensive documentation, active community
  • Notable features: Streaming, human-in-the-loop, checkpointing, persistent state via LangGraph
# Minimal LangGraph ReAct agent (conceptual)
from langgraph.graph import StateGraph
from langchain_core.tools import tool

@tool
def search(query: str) -> str:
    """Search the web."""
    return web_search(query)

graph = StateGraph(AgentState)
graph.add_node("agent", call_agent)
graph.add_node("tools", call_tools)
graph.add_conditional_edges("agent", should_continue, ...)

CrewAI

CrewAI is a multi-agent orchestration framework that models agent teams as crews.

  • Key Concepts: Agents, Tasks, Crews, Processes
  • Strengths: Role-based agent design, built-in collaboration patterns, easy to set up multi-agent systems
  • Orchestration modes: Sequential (agents work one after another), Hierarchical (a manager agent coordinates worker agents)
# Conceptual CrewAI example
from crewai import Agent, Task, Crew

researcher = Agent(role="Researcher", goal="Find relevant data", ...)
writer = Agent(role="Writer", goal="Write compelling content", ...)

research_task = Task(description="Research AI trends", agent=researcher)
write_task = Task(description="Write report", agent=writer)

crew = Crew(agents=[researcher, writer], tasks=[research_task, write_task])
crew.kickoff()

AutoGen (Microsoft)

AutoGen is a multi-agent conversation framework from Microsoft Research.

  • Key Concepts: AssistantAgent, UserProxyAgent, GroupChat, Tool Registration
  • Strengths: Built-in code execution sandbox, flexible conversation patterns, group chat for multi-agent scenarios
  • Notable features: Nested chats, human-in-the-loop, function registration with type-safe schemas

2. What is an AI Orchestrator

2.1 Definition

An AI Orchestrator is an intelligent coordination layer that manages multiple AI agents, tools, sub-systems, and data sources to accomplish complex, multi-step objectives. While a single agent executes tasks directly, an orchestrator delegates tasks to specialized sub-agents, monitors their progress, aggregates their results, and makes high-level decisions about task prioritization, reallocation, and synthesis.

In essence, an orchestrator is an agent that manages other agents — a meta-agent whose primary function is coordination rather than direct task execution.

The orchestration layer is responsible for:

  • Decomposing a complex goal into manageable sub-tasks
  • Assigning sub-tasks to the most appropriate agents or tools
  • Monitoring the progress and health of all sub-processes
  • Coordinating state and data flow between components
  • Aggregating intermediate results into a coherent final output
  • Handling failures, timeouts, and unexpected states
  • Deciding when to re-plan, re-execute, or escalate

2.2 How It Differs from a Simple Agent

The distinction between an agent and an orchestrator is a matter of scope and responsibility:

Aspect Simple Agent Orchestrator
Primary role Execute tasks directly Delegate and coordinate tasks
Scope Single task or narrow domain Complex, multi-step workflows
Sub-components Uses tools (functions/APIs) Manages agents (which may use tools)
Decision-making What action to take next Which agent should act, and how results combine
State management Tracks own context Tracks global state across all sub-processes
Failure handling Retry or ask for help Re-route to another agent, re-plan, or escalate

A useful analogy: a simple agent is like a skilled individual contributor — they can use their tools to complete a task from start to finish. An orchestrator is like a team lead or conductor — they rarely do the work directly, but they ensure that the right people are working on the right things, that dependencies are managed, and that the pieces come together coherently.

2.3 Orchestrator Characteristics

Task Decomposition

The orchestrator breaks down a high-level goal into smaller, well-defined sub-tasks. This is typically done via:

  • Top-down decomposition: Analyze the goal and recursively break it down until each sub-task is atomic and assignable
  • Template-based decomposition: Use predefined workflow templates for common patterns (research-report, code-review-pipeline, data-analysis-pipeline)
  • Dynamic decomposition: Use the LLM to generate a task breakdown tailored to the specific request

Effective decomposition requires the orchestrator to understand:
- Dependencies between sub-tasks (which tasks must complete before others can start)
- Granularity (tasks should be small enough to be manageable but large enough to be meaningful)
- Resource requirements (what tools, data, or expertise each sub-task needs)

For example, given the goal "Create a market analysis report on electric vehicle adoption in Southeast Asia," the orchestrator might decompose this into:

  1. Gather macroeconomic data on SE Asian economies
  2. Analyze EV adoption rates by country
  3. Research government incentives and policies
  4. Interview industry analysts (simulated or via API)
  5. Compile competitive landscape of EV manufacturers
  6. Generate charts and data visualizations
  7. Write executive summary
  8. Format and combine into final report

Subagent Management

The orchestrator maintains a registry or pool of available sub-agents, each with known capabilities, specialties, and constraints. Responsibilities include:

  • Agent selection: Choosing the best agent for each sub-task based on capability, availability, and past performance
  • Agent instantiation: Creating and configuring agents with the right tools, context, and instructions
  • Agent communication: Managing the message flow between agents, including data passing and result forwarding
  • Agent lifecycle: Starting, monitoring, pausing, resuming, and terminating agents as needed
  • Load balancing: Distributing work across multiple agent instances to optimize throughput

Parallel Execution

One of the primary advantages of an orchestrator is the ability to execute independent sub-tasks concurrently. This requires:

  • Dependency graph analysis: Identifying which tasks can run in parallel based on their dependencies
  • Thread/process management: Orchestrating concurrent execution (via async functions, thread pools, or distributed computing)
  • Resource contention management: Ensuring parallel tasks don't conflict over shared resources (files, APIs with rate limits, database connections)
  • Timeout management: Setting and enforcing timeouts for each parallel branch

State Coordination

As multiple agents work concurrently, the orchestrator must maintain a coherent view of the overall state:

  • Global state store: A shared data structure (often a dictionary, database, or distributed store) where agents can read/write intermediate results
  • Version control: Tracking which version of a result is current, especially when multiple branches produce updates
  • Conflict resolution: Handling cases where two agents produce contradictory intermediate results
  • Consistency guarantees: Ensuring that agents see a consistent view of shared state (e.g., using transaction-like semantics)

Common implementations use:
- Shared memory / blackboard pattern: A central repository that all agents can read from and write to
- Message passing: Agents communicate results via a message bus (RabbitMQ, Kafka, Redis Pub/Sub)
- Database-backed state: Using SQLite or PostgreSQL to persist and share state across agent sessions

Result Aggregation

Once sub-tasks complete, the orchestrator must combine their outputs into a coherent whole:

  • Synthesis: Merging textual outputs from multiple agents into a single, well-structured document
  • Conflict resolution: Resolving contradictions between agent outputs (e.g., via majority voting or confidence scoring)
  • Quality filtering: Discarding low-quality outputs based on predefined quality criteria
  • Format conversion: Ensuring aggregated output follows the required format (JSON, Markdown, PDF, etc.)
  • Validation: Checking that the final output meets all original requirements

3. AI Agent vs AI Orchestrator

3.1 Detailed Comparison

Scope of Work

  • AI Agent: Best suited for tasks that can be completed within a few tool-calling steps. Typically handles a single well-defined objective such as "Answer this question," "Write this code function," or "Summarize this document."
  • AI Orchestrator: Designed for complex, multi-faceted objectives that require coordination across multiple domains. Handles workflows like "Research the market, interview stakeholders, analyze data, and produce a 50-page report."

Autonomy Level

  • AI Agent: Operates autonomously within a narrow scope. Makes independent decisions about tool selection and action sequencing but typically stays within its defined task boundaries.
  • AI Orchestrator: Exercises meta-level autonomy — it decides not just how to do a task but what tasks need to be done, who should do them, and how the pieces fit together. It can also decide to modify the overall plan mid-execution.

Complexity Handling

  • AI Agent: Handles linear or modestly branching task flows. A ReAct loop with 5-15 steps is typical. Beyond that, context window limits and error accumulation become problematic.
  • AI Orchestrator: Manages non-linear, branching, recursive, and parallel workflows. Can handle workflows with 50+ steps, multiple concurrent branches, and complex dependency graphs.

Delegation Capability

  • AI Agent: Delegates to tools (functions, APIs). The agent itself does the thinking — it calls tools as instruments.
  • AI Orchestrator: Delegates to agents. Each sub-agent can itself be a sophisticated AI system with its own tools, memory, and reasoning. The orchestrator manages agents as semi-autonomous units.

Error Handling

  • AI Agent: Retry on failure, simplify the approach, or ask the user for clarification. Recovery strategies are typically linear.
  • AI Orchestrator: Sophisticated error recovery: re-route failed tasks to alternative agents, adjust the decomposition strategy, add verification steps, initiate rollback, or escalate to a human supervisor.

State Management

  • AI Agent: Manages state within its own context window or memory store. State is private to the agent.
  • AI Orchestrator: Manages a global state space that encompasses the outputs and status of all sub-agents. State is visible and coordinated across the entire system.

Scalability

  • AI Agent: Vertical scaling (larger context, more capable model). Limited horizontal scaling since most tasks are sequential.
  • AI Orchestrator: Horizontal scaling — can spin up many agents in parallel, scale out across multiple API endpoints, and distribute work across a cluster.

3.2 Comparison Table

Criterion AI Agent AI Orchestrator
Primary function Direct task execution Coordination and delegation
Scope Single task or narrow goal Multi-step, multi-domain workflow
Sub-units managed Tools (functions, APIs) Sub-agents (which may use tools)
Decision horizon Next action (short-term) Whole workflow (long-term)
Task complexity Low to moderate Moderate to very high
Number of steps Typically 1-15 Potentially 10-100+
Parallelism Limited (sequential by default) Native parallel execution
State management Private context/memory Global, coordinated state
Dependency handling Implicit (within reasoning) Explicit (dependency graph)
Error recovery Retry, simplify, ask user Re-route, re-plan, escalate, rollback
Agent specialization General-purpose or narrow Meta-agent coordinating specialists
Human oversight Per-task approval optional Workflow-level governance
Result aggregation Single output from one agent Synthesized from multiple agents
Scalability approach Vertical (bigger model/context) Horizontal (more agents in parallel)
Typical frameworks LangChain, OpenAI Assistants LangGraph, CrewAI, AutoGen, Prefect
Analogy Individual contributor Team lead / conductor

3.3 When to Use Each

Use an AI Agent when:

  • The task is well-defined and contained: "Summarize this email thread," "Translate this document to French," "Extract the key metrics from this quarterly report."
  • You need a quick answer with minimal overhead: A single agent call is faster and cheaper than orchestrating a multi-agent system.
  • The task requires at most 5-15 tool calls: Beyond this range, context window limitations and compounding errors make single-agent approaches brittle.
  • You don't need specialized sub-tasks: The task fits within the capabilities of a single general-purpose model.
  • Latency is critical: Agent responses are typically faster because there's no orchestration overhead.

Use an AI Orchestrator when:

  • The task spans multiple domains or expertise areas: "Research the competitive landscape, analyze financial data, interview experts, and produce an investment memo."
  • Different parts of the task require different tools or data sources: A data-scraping agent, a code-execution agent, and a document-generation agent each specialize in their domain.
  • Parallel independent work can save time: Multiple research questions can be investigated simultaneously.
  • The workflow has complex dependencies: Task B depends on Task A, while Tasks C and D are independent of each other but both depend on Task B.
  • You need built-in quality control and verification: An orchestrator can add verification agents that review and validate outputs before they're included in the final result.
  • The task is long-running and may need human oversight at key decision points: Orchestrators naturally support checkpoints and human-in-the-loop gates.
  • You need to process large volumes of similar tasks: The orchestrator pattern enables batch processing with parallel execution and systematic error handling.

Common Anti-Patterns

  • Using an orchestrator for a simple Q&A: Adds unnecessary complexity, latency, and cost. A single agent is sufficient.
  • Using an agent when you need parallel research: A single agent working sequentially will be much slower than an orchestrator dispatching parallel sub-agents.
  • No error recovery in either: Both agents and orchestrators need robust error handling. For orchestrated systems, failure in one branch shouldn't collapse the whole workflow.
  • Over-decomposition: Breaking a task into too many tiny sub-tasks creates orchestration overhead that outweighs the benefits. Find the right granularity.

4. AI Orchestrator Workflow

4.1 Step-by-Step Process

A typical AI Orchestrator workflow follows these steps:

                          ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
                          │   User Request / Goal │
                          ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
                                     │
                          ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā–¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
                          │   1. Goal Analysis    │
                          │  Understand intent,   │
                          │  constraints, quality │
                          ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
                                     │
                          ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā–¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
                          │  2. Task Decomposit. │
                          │  Break goal into sub- │
                          │  tasks, identify deps │
                          ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
                                     │
                          ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā–¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
                          │  3. Subagent Assign. │
                          │  Match tasks to best │
                          │  agents, configure   │
                          ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
                                     │
                          ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā–¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
                          │  4. Parallel Exec.   │
                          │  Launch independent  │
                          │  sub-tasks concurrently│
                          ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
                                     │
                          ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā–¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
                          │  5. Result Collection │
                          │  Gather outputs, check│
                          │  for completeness     │
                          ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
                                     │
                          ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā–¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
                          │  6. Re-prioritization │
                          │  / Recursion          │
                          │  Adjust plan based on │
                          │  results, recurse if  │
                          │  needed               │
                          ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
                                     │
                          ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā–¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
                          │  7. Result Synthesis  │
                          │  Combine, validate,   │
                          │  format final output  │
                          ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
                                     │
                          ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā–¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
                          │  8. Final Output      │
                          │  Deliver to user      │
                          ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Step 1: Goal Analysis

The orchestrator receives the user's request and analyzes it to extract:

  • Primary objective: What does the user ultimately want?
  • Constraints: Budget, time, quality, format, privacy, compliance
  • Context: Relevant background information, previous conversations, user preferences
  • Success criteria: How will we know the goal has been achieved?

The output of this step is a structured goal specification that guides all subsequent decisions.

Step 2: Task Decomposition

The orchestrator breaks the high-level goal into a directed acyclic graph (DAG) of sub-tasks. Each sub-task should be:

  • Atomic: Cannot be meaningfully broken down further at this level
  • Assignable: Can be assigned to a specific agent or tool
  • Verifiable: Has clear success criteria
  • Dependency-clear: Its prerequisites are explicitly defined

The output is a task graph with nodes (tasks) and edges (dependencies).

Step 3: Subagent Assignment

For each sub-task, the orchestrator:

  1. Identifies required capabilities (e.g., web search, data analysis, code execution, creative writing)
  2. Selects the best agent from its registry based on capabilities, cost, speed, and past performance
  3. Configures the agent with necessary tools, context, and constraints
  4. Provides instructions including the task description, expected output format, and quality criteria

Step 4: Parallel Execution

Tasks with no interdependencies are executed concurrently. The orchestrator:

  • Launches agents in parallel (via async/await, thread pools, or distributed workers)
  • Monitors progress and resource usage
  • Handles timeouts: if an agent takes too long, the orchestrator may cancel and retry with a different agent

Step 5: Intermediate Result Collection

As agents complete their tasks, the orchestrator:

  • Validates outputs against success criteria
  • Stores results in the global state store
  • Propagates results to dependent tasks
  • Logs quality metrics for future agent selection

If a result fails validation, the orchestrator may:
- Request the agent to retry with feedback
- Re-assign to a different agent
- Skip optional sub-tasks
- Escalate if the result is critical

Step 6: Re-prioritization / Recursion

Based on intermediate results, the orchestrator may:

  • Re-prioritize: Change the order of remaining tasks based on new information
  • Re-plan: Decompose a task differently if the original approach isn't working
  • Recurse: If a sub-task turns out to be more complex than expected, recursively decompose it further
  • Add new tasks: If intermediate results reveal gaps in the original decomposition
  • Remove tasks: If certain sub-tasks become unnecessary

This step is what distinguishes an orchestrator from a simple workflow engine — it adapts dynamically rather than following a fixed script.

Step 7: Result Synthesis

Once all (or enough) sub-tasks are complete, the orchestrator synthesizes the final output:

  • Merges text from multiple sources
  • Resolves contradictions (e.g., via weighted voting or confidence scoring)
  • Applies formatting (Markdown, PDF, HTML, JSON)
  • Adds executive summaries or overviews
  • Runs quality checks against the original goal specification

Step 8: Final Output

The orchestrator delivers the final result to the user, optionally including:

  • The synthesized output
  • A summary of what was done (agents used, sources consulted)
  • Confidence level or quality assessment
  • Caveats or suggestions for follow-up

4.2 Real-World Example: Market Research Report

User Request: "Prepare a comprehensive market research report on the adoption of AI-powered coding assistants in enterprise software development teams, covering the current state, competitive landscape, and 3-year forecast."

Goal Analysis

  • Objective: Market research report on AI coding assistants in enterprise
  • Format: Comprehensive report (executive summary, analysis, forecast)
  • Constraints: Must cite sources, 3-year forecast, focus on enterprise (not individual developers)
  • Success: Report is data-driven, well-structured, and actionable

Task Decomposition

1. Research current market size (revenue, users, YoY growth)
2. Analyze competitive landscape (GitHub Copilot, Amazon CodeWhisperer, Tabnine, Cursor, etc.)
3. Survey enterprise adoption rates (by company size, industry, region)
4. Analyze key benefits and ROI metrics reported by enterprises
5. Research limitations, security concerns, and barriers to adoption
6. Analyze pricing models and enterprise licensing trends
7. Gather expert opinions from industry analysts (simulated)
8. Generate 3-year market forecast (with methodology)
9. Compile and format final report

Subagent Assignment

Task Agent Tools
Market size research Research Agent Web search, Statista API, Crunchbase
Competitive analysis Research ...

5. AI Orchestrator Patterns

5.1 Sequential Orchestration

In sequential orchestration, sub-tasks are executed one after another in a predefined order. Each task's output feeds into the next task as input.

Goal → Task A → Task B → Task C → Task D → Output

When to use:
- Tasks have strict dependencies (each step requires the previous step's output)
- The workflow is linear and predictable
- Simpler debugging and error tracing is desired

Example:

Write Specification → Generate Code → Run Tests → Fix Issues → Deploy

Implementation approach:

async def sequential_orchestrator(tasks, agents):
    results = {}
    for task in tasks:
        agent = select_agent(task, agents)
        result = await agent.run(task, context=results)
        results[task.id] = result
    return results

Advantages: Simple to implement, easy to debug, predictable state
Disadvantages: Slow (no parallelism), underutilizes available resources

5.2 Parallel Orchestration

In parallel orchestration, independent tasks are executed concurrently. The orchestrator waits for all tasks to complete (or a sufficient subset) before moving to the synthesis phase.

Goal → Task A ─┐
       Task B ──┤→ Synthesis → Output
       Task C ā”€ā”˜

When to use:
- Multiple independent research questions
- Data collection from different sources
- Any workflow where tasks don't depend on each other

Example:

Market Research Request:
  ā”œā”€ Find competitor prices     ─┐
  ā”œā”€ Analyze customer reviews   ─┤→ Combine → Report
  ā”œā”€ Search industry news       ā”€ā”˜
  └─ Gather financial data      ā”€ā”˜

Implementation approach:

async def parallel_orchestrator(tasks, agents):
    async def run_task(task):
        agent = select_agent(task, agents)
        return task.id, await agent.run(task)

    results = await asyncio.gather(*[run_task(t) for t in tasks])
    return dict(results)

Advantages: Faster execution, better resource utilization
Disadvantages: Requires careful dependency analysis, more complex error handling, potential for resource contention

5.3 Hierarchical Orchestration

Hierarchical orchestration introduces a tree structure where a manager agent oversees multiple worker agents, which may themselves have sub-workers.

                    Orchestrator
                   /     |      \
          Manager A   Manager B  Manager C
          /    \         |           |
      Worker1  Worker2  Worker3    Worker4

When to use:
- The problem has natural hierarchical structure (e.g., organization reporting, project management)
- Different levels of abstraction require different reasoning approaches
- You need delegation chains with escalation paths

Example — Enterprise Report Generation:

Report Orchestrator
ā”œā”€ā”€ Research Manager
│   ā”œā”€ā”€ Web Researcher Agent
│   ā”œā”€ā”€ Database Analyst Agent
│   └── Interview Agent
ā”œā”€ā”€ Writing Manager
│   ā”œā”€ā”€ Executive Summary Writer
│   ā”œā”€ā”€ Technical Writer
│   └── Editor/Reviewer Agent
└── QA Manager
    ā”œā”€ā”€ Fact-Checker Agent
    ā”œā”€ā”€ Compliance Reviewer Agent
    └── Format Validator Agent

Implementation approach:

class HierarchicalOrchestrator:
    async def execute(self, goal):
        plan = await self.master_planner.create_plan(goal)
        for phase in plan.phases:
            manager = self.get_manager(phase.manager_type)
            phase_result = await manager.execute(phase, self.worker_registry)
            self.state.store(phase_result)
        return self.synthesizer.synthesize(self.state)

Advantages: Scalable to very large workflows, clear responsibility boundaries, natural delegation
Disadvantages: Higher latency due to multi-layer coordination, more complex to debug and monitor

5.4 Recursive Orchestration

Recursive orchestration is used when a sub-task is discovered to be too complex for a single agent. The orchestrator recursively decomposes the sub-task into smaller pieces, treating it as a new mini-goal.

Goal → Task A → Task B → B is too complex → Decompose B →
  ā”œā”€ B1 → B2 → B3 → Reassemble B → Continue → Output

When to use:
- The complexity of sub-tasks is unknown upfront
- Tasks have variable granularity (some are simple, some turn out to be complex)
- Self-improving systems where the orchestrator learns to decompose over time

Example — Complex Code Generation:

Goal: "Build a recommendation engine"
  → Design architecture (easy, one shot)
  → Implement collaborative filtering (medium, one shot)
  → Implement real-time personalization (too complex!)
    → Recursively decompose:
      → Implement user session tracking
      → Implement feature extraction pipeline
      → Implement model inference endpoint
      → Implement A/B testing framework
      → Implement caching layer
    → Reassemble into personalization module
  → Write tests
  → Integration and final output

Implementation approach:

async def recursive_orchestrator(goal, depth=0, max_depth=5):
    if depth > max_depth:
        raise ValueError("Max recursion depth exceeded")

    complexity = await estimate_complexity(goal)
    if complexity <= THRESHOLD:
        agent = select_best_agent(goal)
        return await agent.run(goal)

    sub_tasks = await decompose(goal)
    results = await asyncio.gather(
        *[recursive_orchestrator(st, depth+1) for st in sub_tasks]
    )
    return await synthesize(sub_tasks, results)

Advantages: Handles unknown complexity gracefully, adapts to task difficulty, no wasted overhead on simple tasks
Disadvantages: Risk of infinite recursion, unpredictable execution time, harder to estimate cost

5.5 Hybrid Orchestration

Hybrid orchestration combines multiple patterns based on the characteristics of each sub-task. Most real-world orchestrators are hybrid.

Goal Analysis → Task Decomposition → 
  ā”œā”€ Branch 1: Sequential (linear dependency chain)
  │   ā”œā”€ A → B → C → D  
  ā”œā”€ Branch 2: Parallel (independent tasks)
  │   ā”œā”€ E ─┐
  │   ā”œā”€ F ─┤→ G
  │   └─ H ā”€ā”˜
  ā”œā”€ Branch 3: Hierarchical (sub-manager)
  │   └─ Manager → Workers
  └─ Branch 4: Recursive (if complex)
      └─ Decompose on demand

When to use:
- Virtually all production systems. Pure patterns are rare in practice.
- The workflow has a mix of sequential, parallel, and conditional elements

Example — Multi-Article Research System:

async def hybrid_orchestrator(goal):
    # Phase 1: Goal Analysis (sequential — simple)
    goal_spec = await analyze_goal(goal)

    # Phase 2: Research (parallel — independent topics)
    research_tasks = decompose_into_topics(goal_spec)
    research_results = await asyncio.gather(
        *[research_agent.run(t) for t in research_tasks]
    )

    # Phase 3: Drafting (sequential — builds on research)
    draft = await writer_agent.run("Write draft", context=research_results)

    # Phase 4: Review (parallel — independent reviewers)
    reviews = await asyncio.gather(
        fact_checker.run(draft),
        style_editor.

šŸ“„ Multi-Agent Systems — 2723 lines

Comprehensive reference on multi-agent architectures, communication patterns, coordination mechanisms, consensus algorithms, task decomposition, error handling, memory sharing, topology patterns, and real-world implementations.

ACPAgentAlignmentBenchmarkDeploymentEnsembleEvaluationGPTInferenceLLM

Multi-Agent Architectures

Multi-agent systems (MAS) organize multiple AI agents to collaborate on complex tasks. The architecture defines how agents are structured, how they communicate, and how decisions are made. Below are the primary architectural patterns, each suited to different problem domains.

Hierarchical: Manager-Worker Architecture

The hierarchical architecture introduces a tiered structure where a manager agent supervises and delegates tasks to worker agents. This pattern mirrors organizational hierarchies in human teams.

Structure:

Manager Agent
ā”œā”€ā”€ Worker Agent 1 (specialist)
ā”œā”€ā”€ Worker Agent 2 (specialist)
ā”œā”€ā”€ Worker Agent 3 (specialist)
└── Worker Agent N (specialist)

Key Characteristics:
- Manager Agent: Responsible for task decomposition, assignment, result aggregation, and quality control. The manager maintains the global context and decides which worker handles which subtask.
- Worker Agents: Execute specific subtasks. Workers may be generalists or specialists with domain expertise. They report results back to the manager.
- Escalation Path: When a worker fails or encounters ambiguity, the manager intervenes or reassigns the task.
- Scalability: Adding more workers scales horizontally, but the manager can become a bottleneck.

Implementation Variants:
- Strict Hierarchy: Workers cannot communicate directly; all coordination flows through the manager.
- Relaxed Hierarchy: Workers can communicate peer-to-peer for subtask coordination, but final decisions rest with the manager.
- Dynamic Hierarchy: The manager role can be reassigned based on task requirements or agent capabilities.

Use Cases:
- Complex document generation (manager outlines, workers write sections)
- Code generation (manager designs architecture, workers implement modules)
- Multi-step research tasks (manager formulates questions, workers search and analyze)
- Customer support escalation (triage agent → specialist agents → supervisor)

Implementation Example (Pseudocode):

class ManagerAgent:
    def orchestrate(self, task):
        subtasks = self.decompose(task)
        results = []
        for subtask in subtasks:
            worker = self.select_worker(subtask)
            result = worker.execute(subtask)
            results.append(result)
        return self.aggregate(results)

class WorkerAgent:
    def execute(self, subtask):
        # Domain-specific execution logic
        return self.process(subtask)

Advantages:
- Clear responsibility boundaries
- Natural fit for hierarchical organizations
- Simplified debugging and monitoring
- Modular specialization

Disadvantages:
- Single point of failure (manager)
- Communication overhead
- Potential for bottleneck at manager level
- Less flexible for dynamic task re-prioritization

Code Example: CrewAI Hierarchical:

from crewai import Agent, Task, Crew, Process

manager = Agent(
    role="Project Manager",
    goal="Coordinate the team to build a complete application",
    backstory="Experienced engineering manager",
    allow_delegation=True
)

developer = Agent(
    role="Senior Developer",
    goal="Write high-quality code",
    backstory="Expert Python developer"
)

tester = Agent(
    role="QA Engineer",
    goal="Ensure code quality and test coverage",
    backstory="Detail-oriented tester"
)

crew = Crew(
    agents=[developer, tester],
    manager_agent=manager,
    process=Process.hierarchical
)

Sequential: Pipeline Architecture

The pipeline architecture chains agents in a sequence where each agent's output becomes the next agent's input. This is analogous to Unix pipes or assembly lines.

Structure:

Agent 1 → Agent 2 → Agent 3 → ... → Agent N

Key Characteristics:
- Linear Flow: Data passes through agents in a predetermined order.
- Specialization: Each agent performs a specific transformation on the data.
- Deterministic: The execution order is fixed and predictable.
- Bottleneck Awareness: The slowest agent determines overall throughput.

Variants:
- Simple Pipeline: Fixed order, all agents execute in sequence.
- Conditional Pipeline: Some agents may be skipped based on conditions.
- Dynamic Pipeline: The pipeline structure can be modified at runtime based on intermediate results.

Use Cases:
- Data processing ETL pipelines
- Document generation (research → outline → draft → review → finalize)
- Code review pipelines (lint → test → build → deploy)
- Content moderation pipeline

Implementation Example:

class PipelineAgent:
    def __init__(self, name, process_fn):
        self.name = name
        self.process_fn = process_fn

    def run(self, input_data):
        print(f"{self.name}: processing")
        return self.process_fn(input_data)

class Pipeline:
    def __init__(self, agents):
        self.agents = agents

    def execute(self, initial_input):
        data = initial_input
        for agent in self.agents:
            data = agent.run(data)
        return data

Advantages:
- Simple to design and debug
- Predictable execution order
- Easy to add, remove, or replace stages
- Natural parallelism between stages (if buffered)

Disadvantages:
- Inflexible for dynamic task structures
- Error in one stage breaks the entire chain
- Limited to linear workflows
- No feedback loops without additional complexity

Parallel: Fan-Out Architecture

The fan-out architecture dispatches tasks to multiple agents simultaneously, then aggregates results. This pattern is essential for throughput-sensitive applications.

Structure:

Task Dispatcher
ā”œā”€ā”€ Agent 1 (parallel)
ā”œā”€ā”€ Agent 2 (parallel)
ā”œā”€ā”€ Agent 3 (parallel)
└── Agent N (parallel)
      ↓
  Result Aggregator

Key Characteristics:
- Concurrent Execution: Agents run in parallel, potentially on different threads/processes/machines.
- Data Independence: Each agent works on a subset of data or an independent aspect of the task.
- Synchronization Barrier: Results are collected and merged at a synchronization point.
- Scaling: Near-linear speedup for embarrassingly parallel workloads.

Variants:
- Pure Fan-Out: All agents execute the same function on different data shards.
- Specialized Fan-Out: Different agents with different capabilities work on different aspects.
- Competitive Fan-Out: Multiple agents tackle the same task; best result wins (see voting/ensemble).

Use Cases:
- Parallel document chunk processing
- Multi-source research and aggregation
- Ensemble model inference
- Batch data validation
- Web scraping across multiple sources

Implementation Example:

import asyncio

class FanOutOrchestrator:
    def __init__(self, agents):
        self.agents = agents

    async def fan_out(self, task_data):
        tasks = [agent.process(task_data) for agent in self.agents]
        results = await asyncio.gather(*tasks)
        return self.aggregate(results)

    def aggregate(self, results):
        # Merge or select best result
        return results

Advantages:
- High throughput for independent subtasks
- Resilient to individual agent failures (if designed properly)
- Efficient resource utilization
- Scales horizontally with available compute

Disadvantages:
- Requires merge/aggregation logic
- Increased complexity for shared-state coordination
- Diminishing returns with too many parallel agents
- Debugging parallel execution is harder

Swarm: Homogeneous Architecture

Swarm architecture involves many identical or similar agents that collectively solve problems through emergent behavior, inspired by biological swarms (ants, bees, birds).

Structure:

Agent Pool (homogeneous)
ā”œā”€ā”€ Agent (identical)
ā”œā”€ā”€ Agent (identical)
ā”œā”€ā”€ Agent (identical)
ā”œā”€ā”€ Agent (identical)
└── Agent (identical)
Coordinator (lightweight)

Key Characteristics:
- Homogeneous Agents: All agents have the same capabilities and prompts.
- Emergent Behavior: Complex outcomes arise from simple individual rules and local interactions.
- Decentralized: No single point of control; coordination is implicit.
- Scalability: Can scale to hundreds or thousands of agents.

Variants:
- Stochastic Swarm: Agents introduce randomness in their behavior for exploration.
- Greedy Swarm: Agents always take the locally optimal action.
- Role-Switching Swarm: Agents can adopt different roles dynamically.

Use Cases:
- Idea generation and brainstorming
- Diverse solution exploration
- Content variation generation
- Data augmentation
- Parallel hypothesis testing

Implementation Patterns:

class SwarmAgent:
    def __init__(self, agent_id, system_prompt):
        self.id = agent_id
        self.system_prompt = 

Communication Patterns

Communication is the backbone of multi-agent systems. The choice of communication pattern affects scalability, latency, reliability, and the types of coordination possible.

Direct Messaging

Point-to-point communication between agents, where one agent sends a message directly to another.

Characteristics:
- Addressed: Messages have a specific recipient.
- Synchronous or Asynchronous: Can be blocking or non-blocking.
- Private: Only the sender and recipient see the message.
- Direct: No intermediary or routing logic needed.

Implementation Approaches:

class Agent:
    def __init__(self, name):
        self.name = name
        self.mailbox = []

    def send_message(self, recipient, message):
        recipient.receive_message(self.name, message)

    def receive_message(self, sender, message):
        self.mailbox.append((sender, message))

Use Cases:
- Task assignment from manager to worker
- Peer-to-peer coordination between specialized agents
- Query-response patterns between agents

Broadcast

One agent sends a message to all other agents in the system.

Characteristics:
- One-to-Many: A single sender reaches all recipients.
- Efficient Dissemination: Useful for global updates.
- No Targeting: All agents receive the message regardless of relevance.
- Potential for Information Overload: Agents must filter relevant messages.

Implementation Approaches:

class MessageBus:
    def __init__(self):
        self.subscribers = []

    def subscribe(self, agent):
        self.subscribers.append(agent)

    def broadcast(self, sender, message):
        for agent in self.subscribers:
            if agent != sender:
                agent.receive_broadcast(sender, message)

class Agent:
    def receive_broadcast(self, sender, message):
        if self.is_interested(message):
            self.process_message(sender, message)

Use Cases:
- System-wide announcements (shutdown, configuration changes)
- Global state updates
- Emergency or alert messages
- Discovery and registration events

Blackboard / Shared Memory

A shared repository where agents read and write information, enabling indirect communication and collaboration.

Structure:

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│        Blackboard            │
│  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  │
│  │ Problem Description   │  │
│  ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤  │
│  │ Partial Solutions     │  │
│  ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤  │
│  │ Constraints           │  │
│  ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤  │
│  │ Shared Knowledge      │  │
│  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
         ↕        ↕        ↕
       Agent 1  Agent 2  Agent 3

Characteristics:
- Decoupled: Agents don't need to know about each other.
- Shared State: All agents contribute to and read from a common repository.
- Opportunistic Contribution: Agents can contribute when they have relevant knowledge.
- Iterative Refinement: Solutions improve as agents add information.

Implementation:

class Blackboard:
    def __init__(self):
        self.data = {}
        self.lock = asyncio.Lock()

    async def write(self, key, value, agent_name):
        async with self.lock:
            self.data[key] = {"value": value, "author": agent_name, "timestamp": time.time()}

    async def read(self, key):
        async with self.lock:
            return self.data.get(key)

    async def query(self, filter_fn):
        async with self.lock:
            return {k: v for k, v in self.data.items() if filter_fn(k, v)}

Use Cases:
- Collaborative problem solving
- Incremental document building
- Shared research findings
- Multi-agent planning

Tool-Mediated Communication

Agents communicate indirectly through shared tools and their outputs.

Characteristics:
- Artifact-Based: Communication happens through tool outputs.
- Asynchronous: Agents can work at different times.
- Persistent: Tool outputs persist beyond the conversation.
- Auditable: All tool usage is logged.

Examples:
- Agent A writes to a shared database; Agent B reads from it.
- Agent A creates a file; Agent B processes that file.
- Agent A issues a API call; Agent B reads the API response.
- Agent A updates a shared document; Agent B reviews the changes.

Advantages:
- Natural integration with existing tools and APIs
- Persistent communication artifacts
- Parallel execution possible
- Interoperable with non-agent systems

Structured ACP (Agent Communication Protocol)

ACP defines formal message formats, ontologies, and interaction protocols for agent communication.

Characteristics:
- Standardized Format: Messages follow a defined schema (e.g., FIPA ACL, JSON-RPC).
- Speech Acts: Messages have performative types (request, inform, query, propose, accept, reject).
- Conversation Protocols: Predefined interaction patterns (FIPA Contract Net, English Auction).
- Content Languages: Shared ontology for unambiguous meaning.

FIPA ACL Message Structure:

class ACLMessage:
    def __init__(self, performative, sender, receivers, content, ontology, language):
        self.performative = performative  # request, inform, query, propose, etc.
        self.sender = sender
        self.receivers = receivers
        self.content = content
        self.ontology = ontology
        self.language = language
        self.conversation_id = str(uuid.uuid4())
        self.reply_with = None
        self.in_reply_to = None

Common Performatives:
- INFORM: Agent shares information.
- REQUEST: Agent asks another to perform an action.
- QUERY-REF: Agent asks for information.
- PROPOSE: Agent proposes a course of action.
- ACCEPT-PROPOSAL: Agent accepts a proposal.
- REJECT-PROPOSAL: Agent rejects a proposal.
- CANCEL: Agent cancels a previous request.
- CONFIRM: Agent confirms receipt or agreement.

Use Cases:
- Enterprise multi-agent systems requiring standardization
- Interoperable agent platforms
- Formal verification of agent interactions


Coordination Mechanisms

Coordination mechanisms govern how agents align their actions to achieve coherent outcomes.

Consensus Building

Agents iteratively negotiate to reach agreement on a course of action or answer.

Process:
1. Agents express initial positions
2. Agents share reasoning and evidence
3. Agents update positions based on new information
4. Iterate until convergence or timeout
5. Final position is adopted as consensus

Implementation:

class ConsensusBuilder:
    def __init__(self, agents, max_rounds=5, threshold=0.8):
        self.agents = agents
        self.max_rounds = max_rounds
        self.threshold = threshold

    def build_consensus(self, question):
        positions = [agent.initial_position(question) for agent in self.agents]
        for round in range(self.max_rounds):
            agreement = self.measure_agreement(positions)
            if agreement >= self.threshold:
                return self.aggregate_positions(positions), "consensus"
            # Share reasoning
            for i, agent in enumerate(self.agents):
                positions[i] = agent.update_position(question, positions)
        return self.aggregate_positions(positions), "majority"

    def measure_agreement(self, positions):
        # Measure similarity or overlap in positions
        pass

Voting

Agents cast votes on options, and the aggregated vote determines the outcome.

See detailed coverage in Consensus Algorithms section below.

MoE / Mixture of Agents

MoE architecture routes tasks to specialized sub-agents (experts) based on the input, using a gating/routing mechanism.

Architecture:

Input
  │
Router/Gate
  │
  ā”œā”€ā”€ Expert 1
  ā”œā”€ā”€ Expert 2
  ā”œā”€ā”€ Expert 3
  └── Expert N
      │
  Top-k Aggregation
      │
   Output

Key Concepts:
- Gating Network: Learns to route inputs to the most relevant experts.
- Sparse Activation: Only a subset of experts (top-k) are activated per input.
- Load Balancing: Ensures all experts receive sufficient training/usage.
- Shared Context: Experts may share some layers or parameters.

In Multi-Agent Context:
- Each "expert" is a full agent with specialized knowledge
- The router is typically an LLM or classification model
- Top-1 or top-2 routing is common
- Experts can be fine-tuned on domain-specific data

Code Example:

class MoERouter:
    def __init__(self, agents, routing_fn=None):
        self.agents = agents  # List of specialized agents
        self.routing_fn = routing_fn or self.default_router

    def route(self, task):
        # Get routing weights for all agents
        weights = self.routing_fn(task)
        # Select top-k agents
        top_k = sorted(
            enumerate(weights), key=lambda x: x[1], reverse=True
        )[:self.k]
        results = []
        for idx, weight in top_k:
            result = self.agents[idx].process(task)
            results.append((result, weight))
        return self.aggregate(results)

Debate

Structured argumentation where agents argue for different positions and a resolution mechanism determines the outcome. See Debate Architecture for details.

Referee / Arbitrator

A neutral agent that mediates disputes, ensures fairness, and makes final decisions when agents disagree.

Role of the Referee:
- Rule Enforcement: Ensures agents follow established protocols
- Conflict Resolution: Makes binding decisions when agents disagree
- Quality Assessment: Evaluates agent outputs for correctness
- Process Management: Controls turn-taking and speaking order

Implementation:

class RefereeAgent:
    def __init__(self, rules):
        self.rules = rules

    def evaluate_submissions(self, agents, task):
        submissions = [agent.produce_output(task) for agent in agents]
        scores = []
        for submission in submissions:
            score = self.evaluate_quality(submission, task)
            scores.append(score)
        winner_idx = scores.index(max(scores))
        return agents[winner_idx], submissions[winner_idx], scores

    def resolve_dispute(self, agent_a, agent_b, issue):
        # Make binding decision
        return self.llm_judge(issue, agent_a.argument, agent_b.argument)

Use Cases:
- Tournament-style agent evaluation
- Quality-controlled generation
- Fair resource allocation
- Multi-agent learning with rewards

Auction-Based Coordination

Agents bid on tasks or resources, and an auctioneer allocates based on bids.

Types of Auctions:
- English Auction: Ascending bid, highest wins.
- Dutch Auction: Descending bid, first to accept wins.
- Vickrey Auction: Sealed bid, highest wins but pays second-highest price.
- Combinatorial Auction: Bids on bundles of items.
- Reverse Auction: Buyers bid for services (lowest wins).

Implementation Pattern:

class Auctioneer:
    def __init__(self, auction_type="english"):
        self.auction_type = auction_type
        self.bids = {}

    def announce_task(self, task, min_bid=0):
        self.current_task = task
        self.min_bid = min_bid
        self.bids = {}
        # Notify agents of new task

    def receive_bid(self, agent, amount, capabilities):
        self.bids[agent] = {"amount": amount, "capabilities": capabilities}

    def resolve(self):
        if self.auction_type == "english":
            winner = max(self.bids, key=lambda a: self.bids[a]["amount"])
        elif self.auction_type == "reverse":
            winner = min(self.bids, key=lambda a: self.bids[a]["amount"])
        return winner, self.current_task

Role-Based Coordination

Agents adopt specific roles with associated responsibilities and interaction protocols.

Common Roles in Multi-Agent Systems:
- Orchestrator: Coordinates agent activities and workflow
- Executor: Performs specific tasks
- Monitor: Observes system behavior and raises alerts
- Gatekeeper: Controls access to resources
- Validator: Verifies outputs and enfor...

Agent Specialization Patterns

Agent specialization enables efficient task allocation and high-quality outputs by matching tasks to agents with appropriate capabilities.

Role Assignment

Agents are assigned specific roles that define their responsibilities, authority, and interaction patterns.

Role Definition Structure:

Role:
  name: "Senior Python Developer"
  responsibilities:
    - Write production-ready Python code
    - Implement unit and integration tests
    - Code review and refactoring
  authorities:
    - Modify source code in /src
    - Approve pull requests
  capabilities:
    - Python, FastAPI, SQLAlchemy
    - Docker, Kubernetes
    - pytest, unittest
  constraints:
    - Must not modify database schema without review
    - Must follow coding standards

Role Assignment Strategies:
- Hard-Coded: Roles are predefined in the system configuration.
- Capability-Based: Agents are assigned roles matching their demonstrated capabilities.
- Preference-Based: Agents express role preferences.
- Load-Balanced: Roles are assigned to balance workload across agents.

Skill-Based Routing

Tasks are routed to agents based on their demonstrated or declared skills.

Skill Taxonomy:
- Hard Skills: Technical abilities (coding, data analysis, API integration)
- Soft Skills: Communication, negotiation, empathy
- Domain Skills: Legal, medical, financial expertise
- Tool Skills: Proficiency with specific tools or APIs

Routing Strategies:

class SkillBasedRouter:
    def __init__(self, agent_registry):
        self.agent_registry = agent_registry

    def find_best_agent(self, task_requirements):
        candidates = []
        for agent in self.agent_registry:
            match_score = self.calculate_match(
                task_requirements, agent.skills
            )
            candidates.append((agent, match_score))
        candidates.sort(key=lambda x: x[1], reverse=True)
        return candidates[0][0] if candidates else None

    def calculate_match(self, requirements, skills):
        # Weighted Jaccard similarity or learned embedding similarity
        score = 0
        for req, weight in requirements.items():
            if req in skills:
                score += weight * skills[req]
        return score

Domain Expertise

Agents are specialized in specific knowledge domains with tailored system prompts, knowledge bases, and tools.

Domain Expert Design:

class DomainExpert(Agent):
    def __init__(self, name, domain, knowledge_base=None, tools=None):
        super().__init__(name)
        self.domain = domain
        self.knowledge_base = knowledge_base  # Domain-specific vector store
        self.tools = tools or []  # Domain-specific tools
        self.system_prompt = self.build_system_prompt()

    def build_system_prompt(self):
        return f"""You are a {self.domain} expert.
        You have access to domain-specific knowledge and tools.
        Always ground your responses in domain expertise.
        {self.get_domain_guidelines()}"""

    def process(self, task):
        context = self.knowledge_base.query(task.query)
        return self.llm.generate(task, context, tools=self.tools)

Domain Boundaries:
- Clear definition of what each domain covers
- Confidence thresholds for domain relevance
- Escalation procedures for cross-domain queries
- Fallback to generalist when domain match is uncertain

Context Specialization

Agents specialize based on the context of the interaction, such as conversation history, user profile, or environmental conditions.

Context Dimensions:
- User Context: User preferences, history, language
- Temporal Context: Time of day, season, urgency
- Task Context: Complexity, domain, required output format
- System Context: Available resources, load, constraints

Implementation:

class ContextSpecializedAgent:
    def __init__(self, context_profile, base_agent):
        self.context_profile = context_profile
        self.base_agent = base_agent

    def matches_context(self, current_context):
        return all(
            current_context.get(k) == v
            for k, v in self.context_profile.items()
        )

    def process(self, task, context):
        if self.matches_context(context):
            # Use specialized behavior
            return self.specialized_process(task, context)
        else:
            # Fall back to base behavior
            return self.base_agent.process(task)

Consensus Algorithms

Consensus algorithms enable multiple agents to agree on answers, decisions, or courses of action.

Majority Voting

The simplest consensus method: each agent produces an answer, and the answer with the most votes wins.

Characteristics:
- Simple: Easy to implement and understand.
- Fair: Each agent has equal weight.
- Requires N ≄ 3: At least 3 agents needed for meaningful majority.
- Tie-Breaking: Random selection or fallback for ties.

Implementation:

def majority_vote(responses):
    """Standard majority voting."""
    from collections import Counter
    vote_counts = Counter(responses)
    max_votes = max(vote_counts.values())
    winners = [ans for ans, count in vote_counts.items() if count == max_votes]
    if len(winners) == 1:
        return winners[0]
    return random.choice(winners)  # Tie-breaking

Variants:
- Plurality Voting: Most votes wins even without absolute majority.
- Super-Majority: Requires > 66% or > 75% agreement.
- Unanimous: All agents must agree.

Weighted Voting

Agents have different voting weights based on expertise, historical accuracy, or confidence.

Weight Assignment:
- Confidence-Based: Agent's self-reported confidence score
- Accuracy-Based: Historical correctness rate
- Expertise-Based: Domain relevance weight
- Seniority-Based: Experience level or authority

Implementation:

def weighted_vote(responses, weights):
    """Weighted voting where each agent has a weight."""
    score_map = {}
    for response, weight in zip(responses, weights):
        score_map[response] = score_map.get(response, 0) + weight
    winner = max(score_map, key=score_map.get)
    return winner, score_map[winner]

def confidence_weighted_vote(responses, confidences):
    """Vote weighted by agent confidence scores."""
    return weighted_vote(responses, confidences)

Use Cases:
- Expert panels with varying expertise levels
- Systems where some agents are known to be more reliable
- When agents report confidence in their outputs

Borda Count

A ranked voting method where agents rank options, and points are assigned based on rank position.

Method:
1. Each agent ranks N options from best to worst.
2. The top-ranked option receives N-1 points, second receives N-2, etc.
3. Points are summed across all agents.
4. The option with the highest total wins.

Implementation:

def borda_count(rankings):
    """Borda count consensus.
    rankings: list of lists, each sublist is an agent's ranking [best, ..., worst]
    """
    N = len(rankings[0])  # Number of options
    scores = {option: 0 for option in rankings[0]}

    for agent_ranking in rankings:
        for position, option in enumerate(agent_ranking):
            scores[option] += (N - 1 - position)  # N-1 for 1st, 0 for last

    return max(scores, key=scores.get), scores

Advantages:
- Considers full preference ordering, not just top choice
- Less susceptible to strategic voting
- Produces more representative consensus

Sequential Voting

Multiple rounds of voting where options are eliminated or refined between rounds.

Variants:
- Runoff Voting: Bottom option eliminated each round until majority is achieved.
- Instant-Runoff: Voters rank options; lowest-ranked is eliminated; votes redistributed.
- Approval Voting: Multiple rounds approve/disapprove options.

Implementation:

def instant_runoff_voting(rankings):
    """Instant-runoff (ranked-choice) voting."""
    active_options = list(rankings[0])  # All options initially active
    while len(active_options) > 1:
        # Count first-choice votes among active options
        votes = {opt: 0 for opt in active_options}
        for ranking in rankings:
            for opt in ranking:
                if opt in active_options:
                    votes[opt] += 1
                    break

        # Eliminate lowest vote-getter
        min_votes = min(votes.values())
        eliminated = [opt for opt, v in votes.items() if v == min_votes]
        if len(eliminated) == len(active_options):
            break  # All tied
        active_options = [opt for opt in active_options if opt not in eliminated]

    return active_options[0]

Pairwise Comparison

Options are compared in head-to-head matchups to determine the overall winner.

Methods:
- Condorcet Method: An option that beats all others in pairwise comparisons wins.
- Copeland's Method: Score based on number of pairwise wins minus losses.
- Schulze Method: Beatpaths to resolve cycles in pairwise preferences.

Characteristics:
- Condorcet Winner: May not always exist (voting cycles).
- Complete Ranking: Produces a full ordered ranking.
- Computationally Intensive: O(N²) comparisons for N options.

Debate with Referee

Agents argue for their positions, and a referee/judge agent evaluates the arguments to determine the winner.

Process:
1. Agents present initial arguments.
2. Agents rebut opposing arguments (multiple rounds).
3. Referee evaluates arguments based on evidence, reasoning, and relevance.
4. Referee declares winner or produces synthesized answer.

Implementation:

class DebateWithReferee:
    def __init__(self, agents, referee, rounds=3):
        self.agents = agents
        self.referee = referee
        self.rounds = rounds

    def resolve(self, question):
        arguments = {agent: agent.initial_argument(question) for agent in self.agents}

        for r in range(self.rounds):
            for agent in self.agents:
                # Respond to other agents' arguments
                others = [a for a in self.agents if a != agent]
                for other in others:
                    arguments[agent] = agent.rebut(
                        question, arguments[other], arguments[agent]
                    )

        # Referee makes final judgment
        return self.referee.judge(question, arguments)

Mixture of Agents (TogetherAI)

TogetherAI's MoA architecture uses multiple LLMs as "proposers" and "aggregators" in a layered architecture.

Architecture:

Layer 1 (Proposers):
  GPT-4o, Claude 3.5, Llama 3.1, Mixtral, Qwen 2
  Each produces an initial response

Layer 2 (Aggregators):
  GPT-4o (aggregator) reviews and synthesizes layer 1 outputs
  May...

Task Decomposition

Task decomposition breaks complex tasks into smaller, manageable subtasks that can be distributed among agents.

Hierarchical Decomposition

Tasks are broken down recursively into a tree of subtasks, where each level represents increasing granularity.

Structure:

Main Task
ā”œā”€ā”€ Subtask 1
│   ā”œā”€ā”€ Subtask 1.1
│   ā”œā”€ā”€ Subtask 1.2
│   └── Subtask 1.3
ā”œā”€ā”€ Subtask 2
│   ā”œā”€ā”€ Subtask 2.1
│   └── Subtask 2.2
└── Subtask 3
    ā”œā”€ā”€ Subtask 3.1
    ā”œā”€ā”€ Subtask 3.2
    └── Subtask 3.3

Decomposition Strategies:
- Top-Down: Start with the main task and recursively decompose.
- Bottom-Up: Identify atomic tasks and group them.
- Pattern-Based: Use known decomposition patterns for common task types.

Implementation:

class HierarchicalDecomposer:
    def __init__(self, llm):
        self.llm = llm

    def decompose(self, task, depth=0, max_depth=3):
        if depth >= max_depth or self.is_atomic(task):
            return [task]

        subtasks = self.llm.decompose_task(task)
        result = []
        for subtask in subtasks:
            result.extend(self.decompose(subtask, depth + 1, max_depth))
        return result

    def is_atomic(self, task):
        # Check if task is simple enough for single agent
        return task.complexity_score < self.threshold

DAG Decomposition

Tasks are decomposed into a directed acyclic graph (DAG) where nodes are subtasks and edges represent dependencies.

Characteristics:
- Dependency-Aware: Captures which subtasks must complete before others.
- Parallelism: Independent subtasks can execute in parallel.
- Flexible Ordering: Any topological ordering is valid.
- Comprehensive: Captures complex relationships beyond sequential or tree structures.

DAG Representation:

class DAGTask:
    def __init__(self, task_id, description):
        self.id = task_id
        self.description = description
        self.dependencies = []  # List of DAGTask IDs
        self.subtasks = []

class DAGDecomposer:
    def decompose_to_dag(self, task):
        # Use LLM to identify subtasks and their dependencies
        subtasks = self.llm.identify_subtasks(task)
        dependencies = self.llm.identify_dependencies(subtasks)

        dag = {}
        for st in subtasks:
            dag[st.id] = DAGTask(st.id, st.description)
            dag[st.id].dependencies = dependencies.get(st.id, [])

        return dag

    def get_execution_order(self, dag):
        """Return topological ordering of tasks."""
        visited = set()
        order = []

        def dfs(node_id):
            if node_id in visited:
                return
            visited.add(node_id)
            node = dag[node_id]
            for dep_id in node.dependencies:
                dfs(dep_id)
            order.append(node_id)

        for node_id in dag:
            dfs(node_id)

        return order

Sequential Decomposition

Tasks are decomposed into a linear sequence of steps.

Characteristics:
- Linear Order: Each step depends on the previous.
- Simple: Easy to understand and implement.
- Predictable: Fixed execution order.

Use Cases:
- Recipe following
- Step-by-step procedures
- Document template filling

Parallel Decomposition

Tasks are decomposed into independent subtasks that can execute simultaneously.

Characteristics:
- No Dependencies: Subtasks are independent of each other.
- Maximum Parallelism: All subtasks can run concurrently.
- Merge Required: Results must be combined at the end.

Use Cases:
- Processing independent data shards
- Multi-source fact-checking
- Multiple solution generation

Recursive Decomposition

Tasks are decomposed using the same decomposition strategy applied recursively.

Characteristics:
- Self-Similar: Decomposition pattern repeats at each level.
- Natural Fit: Works well for hierarchical problems.
- Potential for Deep Nesting: Needs depth limits.

Feedback-Driven Decomposition

Decomposition is refined based on intermediate results and feedback.

Process:
1. Initial decomposition of the main task.
2. Execute subtasks and collect results.
3. Analyze intermediate results for issues or gaps.
4. Refine decomposition based on feedback.
5. Re-execute modified subtasks.
6. Iterate until satisfactory completion.

Implementation:

class FeedbackDrivenDecomposer:
    def __init__(self, llm, max_iterations=5):
        self.llm = llm
        self.max_iterations = max_iterations

    def execute_with_feedback(self, main_task):
        plan = self.llm.create_initial_plan(main_task)

        for iteration in range(self.max_iterations):
            results = self.execute_plan(plan)
            feedback = self.analyze_results(results, main_task)

            if feedback.is_complete:
                return self.synthesize(results)

            plan = self.llm.refine_plan(plan, results, feedback)

        return self.synthesize(results)

Error Handling in Multi-Agent Systems

Multi-agent systems introduce unique failure modes beyond single-agent errors. Robust error handling is essential for production deployments.

Retry with Different Agent

When an agent fails or produces low-quality output, the task is retried with a different agent.

Strategy:

class RetryWithDifferentAgent:
    def __init__(self, agents, fallback_order=None):
        self.agents = agents
        self.fallback_order = fallback_order or list(agents)

    def execute_with_retry(self, task, max_retries=3):
        errors = []
        for i in range(min(max_retries, len(self.fallback_order))):
            agent = self.fallback_order[i]
            try:
                result = agent.process(task)
                if self.validate_result(result):
                    return result
                else:
                    errors.append((agent.name, "Validation failed"))
            except Exception as e:
                errors.append((agent.name, str(e)))
                continue

        raise MultiAgentError(f"All agents failed: {errors}")

    def validate_result(self, result):
        # Quality checks, format validation, etc.
        return result is not None and result.quality_score > 0.7

Considerations:
- Agent ordering: Try specialized agents first, fall back to generalists
- Timeout management: Set per-agent timeouts to avoid cascading delays
- Error context: Pass error information to fallback agents

Fallback Chain

A predefined chain of fallback strategies or agents, tried in order until one succeeds.

Fallback Chain Structure:

Primary Strategy
  → Fallback 1 (different approach)
    → Fallback 2 (simplified approach)
      → Fallback 3 (default response)

Example Chain:

class FallbackChain:
    def __init__(self, strategies):
        self.strategies = strategies  # Ordered list of (name, handler_fn)

    def execute(self, task):
        errors = []
        for name, handler in self.strategies:
            try:
                result = handler(task)
                if result is not None:
                    return result
            except Exception as e:
                errors.append(f"{name}: {e}")
                continue

        return self.default_response(task)

Fallback Strategies (Ordered):
1. Primary agent with full context
2. Different agent with simplified context
3. Single-step generation (no decomposition)
4. Rule-based template response
5. Graceful error message to user

Circuit Breaker

Prevents cascading failures by detecting when a component is failing and temporarily stopping requests to it.

States:
- CLOSED: Normal operation, requests pass through.
- OPEN: Failures detected, requests are rejected immediately.
- HALF-OPEN: Testing if service has recovered.

Implementation:

class CircuitBreaker:
    def __init__(self, failure_threshold=5, recovery_timeout=30):
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.failure_count = 0
        self.last_failure_time = None
        self.state = "CLOSED"

    def call(self, agent_fn, task):
        if self.state == "OPEN":
            if time.time() - self.last_failure_time > self.recovery_timeout:
                self.state = "HALF-OPEN"
            else:
                raise CircuitBreakerOpen("Agent temporarily unavailable")

        try:
            result = agent_fn(task)
            self.on_success()
            return result
        except Exception as e:
            self.on_failure()
            raise

    def on_success(self):
        if self.state == "HALF-OPEN":
            self.state = "CLOSED"
        self.failure_count = 0

    def on_failure(self):
        self.failure_count += 1
        self.last_failure_time = time.time()
        if self.failure_count >= self.failure_threshold:
            self.state = "OPEN"

Consensus Verification

Multiple agents independently verify outputs to catch errors.

Process:
1. Primary agent produces output.
2. Verification agents independently check the output.
3. Consensus among verifiers determines if output is accepted.
4. If verification fails, output is rejected and re-generated.

Implementation:

class ConsensusVerifier:
    def __init__(self, verifiers, required_approvals=2):
        self.verifiers = verifiers
        self.required_approvals = required_approvals

    def verify(self, output, context):
        approvals = 0
        rejections = []
        for verifier in self.verifiers:
            result = verifier.check(output, context)
            if result.approved:
                approvals += 1
            else:
                rejections.append(result.reason)
            if approvals >= self.required_approvals:
                return True, []
        return False, rejections

Verification Strategies:
- Factual Verification: Check claims against known facts
- Logical Verification: Check reasoning consistency
- Format Verification: Check output format compliance
- Style Verification: Check tone and style guidelines

Human-in-the-Loop Escalation

When agents cannot resolve an issue, the system escalates to a human operator.

Escalation Criteria:
- Low confidence scores across all agents
- Repeated failures or timeouts
- Novel or ambiguous queries
- Sensitive topics requiring human judgment
- Edge cases outside agent training

Escalation Flow:

class HITLEScalation:
    def __init__(self, auto_resolve_threshold=0.9):
        self.auto_resolve_threshold = auto_resolve_threshold

    def process_with_escalation(self, task):
        # Try automatic processing
        result = self.auto_process(task)

        if result.confidence >= self.

Agent Topology Patterns

The topology defines how agents are connected and interact within the system.

Linear Chain

Agents are arranged in a line, with each agent communicating only with its immediate neighbors.

Structure:

A1 → A2 → A3 → ... → An

Characteristics:
- Simple communication paths
- Clear data flow direction
- Limited connectivity
- Single path for information

Use Cases:
- Sequential processing pipelines
- Transformation chains
- Simple workflows

Tree Topology

Agents are organized hierarchically in a tree structure.

Structure:

        Root
       /    \
    A2       A3
   /  \     /  \
  A4  A5   A6  A7

Characteristics:
- Hierarchical control flow
- Parent-child relationships
- Broadcast from root to leaves
- Aggregation from leaves to root

Use Cases:
- Hierarchical task decomposition
- Organizational hierarchies
- Decision trees

Star / Hub-and-Spoke Topology

A central hub agent communicates with all peripheral agents.

Structure:

       A1
        |
    A2--Hub--A3
        |
       A4

Characteristics:
- Centralized coordination
- Hub is single point of communication
- Spokes only communicate through hub
- Easy to monitor and control

Use Cases:
- Manager-worker patterns
- Centralized information routing
- Load balancing

Mesh / Fully Connected Topology

Every agent can communicate directly with every other agent.

Structure:

A1 ---- A2
 | \   / |
 |  X   |
 | /   \ |
A3 ---- A4

Characteristics:
- Maximum connectivity
- Redundant communication paths
- High flexibility
- High complexity for N agents (NƗ(N-1)/2 connections)

Use Cases:
- Small teams requiring full collaboration
- Debate and discussion systems
- Peer review networks

DAG Topology

Agents are arranged in a directed acyclic graph, allowing parallel paths and dependencies.

Structure:

    A1
   /  \
  A2  A3
  |    |
  A4  A5
   \  /
    A6

Characteristics:
- Supports parallelism and dependencies
- No cycles (enforces acyclic flow)
- Flexible routing
- Complex to validate

Use Cases:
- Complex workflows with branching
- Multi-stage processing
- Dependency-aware task execution

Modular Topology

The topology is composed of reusable modules, each containing multiple agents with internal topology.

Structure:

Module1        Module2
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”      ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”
│ A1-A2│──────│ A3-A4│
│  │   │      │  │   │
│ A5   │      │ A6   │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”˜      ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Characteristics:
- Encapsulated agent groups
- Well-defined interfaces between modules
- Reusable and composable
- Scalable through module addition

Use Cases:
- Large-scale enterprise systems
- Microservice-based agent architectures
- Domain-separated systems

Dynamic Topology

The topology can change at runtime based on task requirements, agent availability, or performance.

Characteristics:
- Self-Organizing: Agents form and break connections as needed.
- Adaptive: Topology adjusts to changing conditions.
- Resilient: Can route around failed agents.
- Complex: Requires monitoring and reconfiguration logic.

Implementation Considerations:
- Topology discovery protocol
- Health monitoring and heartbeat
- Reconfiguration triggers and strategies
- Consistency guarantees during transitions


Memory Sharing

Memory sharing enables agents to access and contribute to shared knowledge, enabling coherent collaboration.

Shared Vector Store

A shared vector database where agents store and retrieve embeddings of knowledge, context, and intermediate results.

Architecture:

Agents → Shared Vector Store (Chroma/Pinecone/Qdrant)
         ā”œā”€ā”€ Knowledge embeddings
         ā”œā”€ā”€ Conversation history
         ā”œā”€ā”€ Task results
         └── Shared context

Implementation:

class SharedVectorStore:
    def __init__(self, collection_name, embedding_fn):
        self.collection = chroma_client.get_or_create_collection(
            name=collection_name,
            embedding_function=embedding_fn
        )

    def store_agent_output(self, agent_name, content, metadata=None):
        self.collection.add(
            documents=[content],
            metadatas=[{"agent": agent_name, **(metadata or {})}],
            ids=[f"{agent_name}_{uuid.uuid4()}"]
        )

    def query_shared_knowledge(self, query, n_results=5):
        return self.collection.query(
            query_texts=[query],
            n_results=n_results
        )

Use Cases:
- Cross-agent retrieval augmented generation
- Long-term memory for agent conversations
- Shared knowledge base
- Task result caching

Shared Message Bus

A publish-subscribe message bus that agents use to exchange messages asynchronously.

Components:
- Topics/Channels: Named channels for message categories.
- Publishers: Agents that produce messages.
- Subscribers: Agents that consume messages.
- Broker: Routes messages from publishers to subscribers.

Implementation Pattern:

class MessageBus:
    def __init__(self):
        self.topics = defaultdict(list)
        self.history = defaultdict(list)

    def publish(self, topic, message, sender):
        self.history[topic].append({"sender": sender, "message": message, "timestamp": time.time()})
        for subscriber in self.topics[topic]:
            subscriber.receive(topic, message, sender)

    def subscribe(self, topic, agent):
        self.topics[topic].append(agent)

    def get_history(self, topic, since=None):
        if since:
            return [m for m in self.history[topic] if m["timestamp"] > since]
        return self.history[topic]

Shared State

A central state store that agents can read from and write to, enabling coordinated state management.

Characteristics:
- Consistent: All agents see the same state (subject to consistency guarantees).
- Transactional: Updates can be atomic and isolated.
- Observable: Agents can watch for state changes.

Implementation:

class SharedState:
    def __init__(self, backend="redis"):
        self.store = RedisClient() if backend == "redis" else InMemoryStore()
        self.watchers = defaultdict(list)

    def set(self, key, value, agent_name):
        old_value = self.store.get(key)
        self.store.set(key, {"value": value, "updated_by": agent_name, "timestamp": time.time()})
        self.notify_watchers(key, value, old_value)

    def get(self, key):
        return self.store.get(key)

    def watch(self, key, callback):
        self.watchers[key].append(callback)

    def transaction(self, updates):
        """Atomic batch update."""
        with self.lock:
            for key, value in updates:
                self.set(key, value)

Blackboard Pattern

A shared knowledge repository where agents contribute partial solutions and retrieve needed information. See Blackboard / Shared Memory section.

Advanced Blackboard Features:
- Structured Blackboard: Organized into panels/levels for different types of information.
- Source Tracking: Each entry records which agent contributed it.
- Confidence Scoring: Entries have associated confidence levels.
- Lifecycle Management: Entries can be proposed, verified, accepted, or rejected.

Cross-Agent RAG

Retrieval-Augmented Generation where agents query each other's knowledge stores or a shared knowledge base.

Architecture:

Agent A (query)
  │
  ā”œā”€ā”€ā†’ Shared Vector Store (documents)
  ā”œā”€ā”€ā†’ Agent B's Knowledge (via tool call)
  ā”œā”€ā”€ā†’ Agent C's Knowledge (via tool call)
  │
  └──→ Aggregate retrieved context → Generate response

Implementation:

class CrossAgentRAG:
    def __init__(self, shared_store, peer_agents):
        self.shared_store = shared_store
        self.peer_agents = peer_agents  # Other agents that can provide knowledge

    def retrieve(self, query):
        # Retrieve from shared store
        shared_results = self.shared_store.query(query)

        # Retrieve from peer agents
        peer_results = []
        for agent in self.peer_agents:
            try:
                result = agent.provide_knowledge(query)
                peer_results.append(result)
            except Exception:
                continue

        # Aggregate and deduplicate
        all_results = self.merge_results(shared_results, peer_results)
        return all_results

Tool Sharing Models

Tools enable agents to interact with external systems. Tool sharing models determine how agents discover and use tools.

Global Tool Registry

A centralized registry where all tools are registered and any agent can discover and use any tool.

Architecture:

Global Tool Registry
ā”œā”€ā”€ Tool: WebSearch
│   ā”œā”€ā”€ Agent A can use
│   ā”œā”€ā”€ Agent B can use
│   └── Agent C can use
ā”œā”€ā”€ Tool: CodeInterpreter
│   ā”œā”€ā”€ Agent A can use
│   └── Agent B can use
└── Tool: DatabaseQuery
    ā”œā”€ā”€ Agent A can use
    └── Agent C can use

Implementation:

class GlobalToolRegistry:
    def __init__(self):
        self.tools = {}

    def register_tool(self, name, tool_fn, description, schema):
        self.tools[name] = {
            "fn": tool_fn,
            "description": description,
            "schema": schema,
            "usage_count": 0
        }

    def get_tool(self, agent, tool_name):
        if tool_name in self.tools:
            self.tools[tool_name]["usage_count"] += 1
            return self.tools[tool_name]["fn"]
        return None

    def list_tools(self, agent=None):
        return {name: info["description"] for name, info in self.tools.items()}

Advantages:
- Single source of truth for tools
- Easy to add, update, or remove tools
- Usage tracking and monitoring
- Access control at registry level

Disadvantages:
- Centralized point of failure
- Registry can become a bottleneck
- All agents see all tools (potential confusion)

Per-Agent Tools

Each agent has its own set of tools, configured specifically for its role and capabilities.

Configuration:

class PerAgentToolConfig:
    def __init__(self):
        self.agent_tools = {}

    def configure_agent(self, agent_name, tools):
        self.agent_tools[agent_name] = [
            {"name": name, "config": config}
            for name, config in tools.items()
        ]

    def get_agent_tools(self, agent_name):
        return self.agent_tools.get(agent_name, [])

# Example configuration
config = PerAgentToolConfig()
config.configure_agent("research_agent", {
    "web_search": {"engine": "bing", "max_results": 10},
    "fetch_page": {"timeout": 5}
})
config.configure_agent("code_agent", {
    "python_repl": {},
    "file_ops": {"allowed_paths": ["/workspace"]},
    "git_ops": {}
})

Advantages:
- Clean separation of concerns
- Agents only see relevant tools
- Reduced prompt complexity
- Security isolation between agents

Disadvantages:
- Tool duplication across agents
- Configuration overhead
- Harder to share tools dynamically

Capability-Based Routing

Tools are matched to tasks based on the capabilities they provide, independent of which agent uses them.

Capability Model:

Tools:
  WebSearchTool:
    capabilities:
      - web_search
      - information_retrieval
    input: query string
    output: search results

  CodeInterpreter:
    capabilities:
      - code_execution
      - data_analysis
      - visualization
    input: code string
    output: execution result

  DatabaseQuery:
    capabilities:
      - data_query
      - database_access
    input: SQL query
    output: query results

Routing Logic:

class CapabilityRouter:
    def __init__(self, tool_registry):
        self.tool_registry = tool_registry

    def find_tools_for_capability(self, required_capabilities):
        matching_tools = []
        for tool_name, tool_info in self.tool_registry.items():
            if any(cap in tool_info["capabilities"] for cap in required_capabilities):
                matching_tools.append(tool_name)
        return matching_tools

    def route_task(self, task, agent):
        required_caps = task.required_capabilities
        available_tools = self.find_tools_for_capability(required_caps)
        # Filter to tools agent is allowed to use
        agent_tools = [t for t in available_tools if agent.can_use(t)]
        return agent_tools

Tool Output Caching

Results from tool executions are cached so that repeated calls with the same or similar inputs return cached results.

Caching Strategies:

  1. Exact Match Cache: Identical inputs return cached result.
class ExactMatchCache:
    def __init__(self, ttl=300):
        self.cache = {}
        self.ttl = ttl

    def get_or_execute(self, tool_fn, tool_args):
        cache_key = self.make_key(tool_fn.__name__, tool_args)
        if cache_key in self.cache:
            entry = self.cache[cache_key]
            if time.time() - entry["timestamp"] < self.ttl:
                return entry["result"]
        result = tool_fn(**tool_args)
        self.cache[cache_key] = {"result": result, "timestamp": time.time()}
        return result

    def make_key(self, fn_name, args):
        return f"{fn_name}:{hash(frozenset(args.items()))}"
  1. Semantic Cache: Similar inputs return cached result based on embedding similarity.
class SemanticCache:
    def __init__(self, embedding_fn, similarity_threshold=0.95):
        self.entries = []
        self

Real-World Patterns and Frameworks

AutoGen Group Chat

Microsoft's AutoGen provides a flexible group chat pattern for multi-agent conversations.

Architecture:

GroupChatManager
ā”œā”€ā”€ Agent A (UserProxy)
ā”œā”€ā”€ Agent B (Assistant)
ā”œā”€ā”€ Agent C (Critic)
ā”œā”€ā”€ Agent D (Executor)
└── Agent E (Custom)

Key Concepts:
- GroupChat: Manages a conversation among multiple agents.
- GroupChatManager: Orchestrates the group chat, selecting the next speaker.
- ConversableAgent: Base class for all agents in AutoGen.
- Speaker Selection: Configurable strategies (round-robin, auto, manual, custom).

Speaker Selection Methods:
- Round Robin: Agents speak in fixed order.
- Auto: LLM decides who speaks next based on conversation context.
- Random: Random selection for exploratory conversations.
- Manual: Human selects the next speaker.
- Custom: User-defined selection logic.

Implementation Pattern:

import autogen

# Define agents
planner = autogen.AssistantAgent(
    name="Planner",
    system_message="You are a planner. Break down tasks into steps.",
    llm_config={"config_list": [{"model": "gpt-4", "api_key": "..."}]}
)

executor = autogen.AssistantAgent(
    name="Executor",
    system_message="You execute tasks and produce results.",
    llm_config={"config_list": [{"model": "gpt-4", "api_key": "..."}]}
)

critic = autogen.AssistantAgent(
    name="Critic",
    system_message="You review and critique work.",
    llm_config={"config_list": [{"model": "gpt-4", "api_key": "..."}]}
)

# Create group chat
groupchat = autogen.GroupChat(
    agents=[planner, executor, critic],
    messages=[],
    max_round=10,
    speaker_selection_method="auto"
)

manager = autogen.GroupChatManager(
    groupchat=groupchat,
    llm_config={"config_list": [{"model": "gpt-4", "api_key": "..."}]}
)

CrewAI Hierarchical / Sequential

CrewAI provides both hierarchical and sequential process patterns.

Sequential Process:

from crewai import Crew, Process

crew = Crew(
    agents=[agent1, agent2, agent3],
    tasks=[task1, task2, task3],
    process=Process.sequential
)
# Tasks execute in order: task1 → task2 → task3

Hierarchical Process:

crew = Crew(
    agents=[developer, tester, writer],
    tasks=[main_task],
    manager_agent=manager,
    process=Process.hierarchical
)
# Manager decomposes main_task, assigns to workers

Key Features:
- Role Assignment: Each agent has a role, goal, and backstory.
- Task Delegation: Manager can delegate subtasks to workers.
- Tool Sharing: Agents can share tools defined at crew level.
- Memory: Crew-level memory for context across tasks.
- Callbacks: Hooks for logging, monitoring, and custom actions.

LangGraph Graphs

LangGraph provides a graph-based approach to building agent workflows.

Key Concepts:
- StateGraph: Graph where nodes modify shared state.
- MessageGraph: Specialized for chat message passing.
- Nodes: Functions or agents that process state.
- Edges: Define transitions between nodes.
- Conditional Edges: Dynamic routing based on state.
- Checkpointing: Save and restore state for persistence.
- Human-in-the-Loop: Interrupt execution for human input.

Implementation Pattern:

from langgraph.graph import StateGraph, END
from typing import TypedDict, List
import operator

class AgentState(TypedDict):
    messages: List
    next_agent: str

def agent_a(state):
    # Process and return updated state
    return {"messages": state["messages"] + ["A processed"]}

def agent_b(state):
    return {"messages": state["messages"] + ["B processed"]}

def router(state):
    # Conditional routing logic
    if len(state["messages"]) < 3:
        return "agent_b"
    return END

# Build graph
graph = StateGraph(AgentState)
graph.add_node("agent_a", agent_a)
graph.add_node("agent_b", agent_b)
graph.set_entry_point("agent_a")
graph.add_edge("agent_a", "agent_b")
graph.add_conditional_edges("agent_b", router)
graph.set_finish_point(END)

# Compile and run
app = graph.compile()
result = app.invoke({"messages": []})

Semantic Kernel Planner

Microsoft's Semantic Kernel provides planning capabilities for multi-step tasks.

Planner Types:
- HandlebarsPlanner: Uses Handlebars templates for prompt-based planning.
- FunctionCallingStepwisePlanner: Step-by-step planning with function calling.
- StepwisePlanner: Creates sequential plans from available functions.

Architecture:

Kernel
ā”œā”€ā”€ Plugins (collections of functions)
│   ā”œā”€ā”€ Native Functions (C#/Python code)
│   └── Semantic Functions (AI prompts)
ā”œā”€ā”€ Planner (creates execution plan)
ā”œā”€ā”€ Memory (vector store integration)
└── Connectors (OpenAI, HuggingFace, etc.)

smolagents Code Agents

HuggingFace's smolagents framework provides code-first agent architecture.

Key Concepts:
- CodeAgent: Agent that writes and executes Python code.
- ToolCallingAgent: Agent that calls tools via function calling.
- ManagedAgent: Sub-agent managed by a main agent.
- Tool Sharing: Tools can be shared across agents.

Architecture:

from smolagents import CodeAgent, HfApiModel, ManagedAgent

# Create tools
def search_web(query: str) -> str:
    """Search the web for information."""
    return f"Results for {query}"

# Create agent
agent = CodeAgent(
    tools=[search_web],
    model=HfApiModel(),
    add_base_tools=True
)

# Multi-agent setup
managed_agent = ManagedAgent(
    agent=agent,
    name="researcher",
    description="Handles research tasks"
)

main_agent = CodeAgent(
    tools=[],
    model=HfApiModel(),
    managed_agents=[managed_agent]
)

OpenAI Swarm

OpenAI's Swarm framework provides lightweight multi-agent orchestration.

Key Concepts:
- Routines: Agents with specific instructions and functions.
- Handoffs: Agents can transfer control to other agents.
- Context Variables: Shared state across agents.

Implementation:

from swarm import Swarm, Agent

client = Swarm()

def transfer_to_spanish_agent():
    return spanish_agent

english_agent = Agent(
    name="English Agent",
    instructions="You speak English.",
    functions=[transfer_to_spanish_agent]
)

spanish_agent = Agent(
    name="Spanish Agent",
    instructions="You speak Spanish."
)

# Run with handoff
response = client.run(
    agent=

Performance Metrics for Multi-Agent Systems

Measuring multi-agent system performance requires metrics at multiple levels: system-level, task-level, and agent-level.

System-Level Metrics

Metric Description Measurement
Throughput Tasks completed per unit time tasks/second
Latency (p50/p95/p99) Time from task submission to completion milliseconds
Scalability Performance change with additional agents Δthroughput / Δagents
Availability System uptime / total time percentage
Cost Efficiency Quality per unit cost score / $
Resource Utilization Agent idle vs active time percentage
Fault Tolerance Performance degradation under failures graceful degradation

Task-Level Metrics

Metric Description
Task Completion Rate Percentage of tasks completed successfully
Task Accuracy Correctness of outputs
Task Quality Score Human or automated quality rating
Task Decomposition Efficiency Overhead of task decomposition
First-Time Success Rate Tasks completed without retry
Time to Resolution End-to-end task completion time

Agent-Level Metrics

Metric Description
Individual Accuracy Per-agent output correctness
Contribution Value Impact on final outcome
Response Time Per-agent processing time
Tool Utilization Frequency and efficiency of tool use
Error Rate Frequency of agent failures
Collaboration Score Effectiveness in multi-agent context

Communication Metrics

Metric Description
Message Volume Total messages exchanged
Messages per Task Communication overhead
Average Message Size Information density
Coordination Overhead Time spent in coordination vs execution
Convergence Time Time to reach consensus
Agreement Rate How often agents agree

Quality Metrics

Metric Description Method
Consistency Output consistency across similar inputs Variance analysis
Robustness Performance under noisy inputs Stress testing
Diversity Variance in agent outputs Entropy measurement
Novelty Unique insights produced Human evaluation
Coherence Logical consistency of multi-agent output Automated checks

Cost Metrics

Metric Description
Token Cost per Task Total LLM tokens consumed
API Call Volume Number of external API calls
Compute Cost Infrastructure cost per task
Human Oversight Cost Time spent by humans reviewing outputs
Storage Cost Memory and state storage costs

Monitoring Dashboard Example

class MASMonitor:
    def __init__(self):
        self.metrics = defaultdict(list)

    def record_task_completion(self, task_id, duration, success, cost):
        self.metrics["task_duration"].append(duration)
        self.metrics["task_success"].append(success)
        self.metrics["task_cost"].append(cost)

    def record_message(self, sender, receiver, size):
        self.metrics["messages"].append({
            "sender": sender,
            "receiver": receiver,
            "size": size,
            "timestamp": time.time()
        })

    def summary(self):
        return {
            "total_tasks": len(self.metrics["task_duration"]),
            "avg_duration": mean(self.metrics["task_duration"]),
            "p95_duration": percentile(self.metrics["task_duration"], 95),
            "success_rate": mean(self.metrics["task_success"]),
            "total_cost": sum(self.metrics["task_cost"]),
            "total_messages": len(self.metrics["messages"]),
            "agents_active": len(set(m["sender"] for m in self.metrics["messages"]))
        }

šŸ“„ Agentic Frameworks — 2514 lines

Comprehensive reference and comparison of major agentic frameworks: LangGraph, CrewAI, AutoGen, Semantic Kernel, smolagents, DSPy, OpenAI Agents SDK, Anthropic MCP, Google ADK, Amazon Bedrock Agents, Agno/Phidata, and Pydantic AI.

AgentDPODeploymentEvaluationGPTLLMLoRAMCPMulti-AgentOrchestrator

LangGraph

LangGraph is a framework developed by LangChain for building stateful, multi-agent applications using graph-based state machines. It enables developers to define agent workflows as directed graphs where nodes are computation steps and edges define control flow.

Core Architecture

LangGraph models agent workflows as a state machine where each node in the graph represents a step that transforms the shared state.

StateGraph

StateGraph is the primary graph type. It operates on a shared state object that persists across nodes.

Key Components:
- State Schema: A TypedDict or Pydantic model defining the structure of the shared state.
- Nodes: Functions or runnable objects that take state as input and return state updates.
- Edges: Define the flow between nodes (directed connections).
- Conditional Edges: Dynamic routing based on state content.
- Entry Point: The first node to execute.
- Finish Points: Nodes where execution terminates.

Basic StateGraph Example:

from langgraph.graph import StateGraph, END
from typing import TypedDict, List
import operator

class GraphState(TypedDict):
    input: str
    messages: List[str]
    next_step: str

def node_a(state: GraphState) -> GraphState:
    # Process and return updates (only returning changes)
    return {"messages": state["messages"] + ["Node A processed"]}

def node_b(state: GraphState) -> GraphState:
    return {"messages": state["messages"] + ["Node B processed"]}

def router(state: GraphState) -> str:
    """Conditional edge: decide which node to go to next."""
    if "error" in state:
        return "error_handler"
    elif state["next_step"] == "b":
        return "node_b"
    return END

# Build the graph
builder = StateGraph(GraphState)
builder.add_node("node_a", node_a)
builder.add_node("node_b", node_b)
builder.add_node("error_handler", error_handler)

builder.set_entry_point("node_a")
builder.add_edge("node_a", "node_b")
builder.add_conditional_edges("node_b", router, {
    "node_a": "node_a",
    "error_handler": "error_handler",
    END: END
})

# Compile into an executable app
app = builder.compile()

MessageGraph

MessageGraph is a specialized graph for chat-based applications. It operates on lists of messages rather than arbitrary state.

from langgraph.graph import MessageGraph, END
from langchain_core.messages import HumanMessage, AIMessage

def assistant_node(state):
    messages = state["messages"]
    response = llm.invoke(messages)
    return {"messages": messages + [response]}

def human_review_node(state):
    # Interrupt for human review
    return {"messages": state["messages"]}

# MessageGraph is simpler - just message passing
graph = MessageGraph()
graph.add_node("assistant", assistant_node)
graph.add_node("human_review", human_review_node)

graph.set_entry_point("assistant")
graph.add_edge("assistant", "human_review")
graph.add_conditional_edges("human_review", 
    lambda state: "assistant" if state["approved"] else END)

Node Types and Patterns

Nodes in LangGraph are the fundamental building blocks. They can be:

  1. Function Nodes: Simple Python functions that take state and return state updates.
def my_node(state: dict) -> dict:
    # Process state
    return {"key": "value"}
  1. Runnable Nodes: LangChain Runnable objects (chains, LCEL pipelines).
from langchain_core.runnables import RunnableLambda

chain = prompt | llm | parser
builder.add_node("llm_call", chain)
  1. Subgraph Nodes: Entire graphs used as nodes within a larger graph.
subgraph = create_subgraph().compile()
builder.add_node("sub_task", subgraph)
  1. Agent Nodes: Full agent loops that can make multiple tool calls.
from langgraph.prebuilt import create_react_agent

agent = create_react_agent(model, tools)
builder.add_node("agent_step", agent)

Edges and Control Flow

LangGraph provides several types of edges for different control flow patterns:

Direct Edges

Simple, unconditional transitions between nodes.

graph.add_edge("node_a", "node_b")

Conditional Edges

Edges that route based on a function evaluating the current state.

def route_decision(state: dict) -> str:
    if state["quality"] > 0.8:
        return "finalize"
    elif state["attempts"] < 3:
        return "retry"
    return "fallback"

graph.add_conditional_edges(
    "evaluator",
    route_decision,
    {
        "finalize": "output_node",
        "retry": "generator",
        "fallback": "human_review"
    }
)

Entry and Finish Points

graph.set_entry_point("start_node")
graph.set_finish_point("end_node")
# Can also set conditional entry:
graph.set_conditional_entry_point(entry_router)

Checkpointing and Persistent State

LangGraph provides built-in checkpointing for state persistence, enabling pause/resume, human-in-the-loop, and fault recovery.

from langgraph.checkpoint import MemorySaver

# In-memory checkpointing
memory = MemorySaver()
app = graph.compile(checkpointer=memory)

# SQLite checkpointing (persistent)
from langgraph.checkpoint import SqliteSaver
with SqliteSaver.from_conn_string("checkpoints.db") as saver:
    app = graph.compile(checkpointer=saver)

    # Run with thread ID for conversation continuity
    config = {"configurable": {"thread_id": "conversation_1"}}
    result = app.invoke({"input": "Hello"}, config)

Checkpointing Features:
- Automatic State Saving: State is saved after each node execution.
- Thread-Based Isolation: Each conversation/run gets a unique thread_id.
- Pause/Resume: Execution can be paused and resumed later.
- State Inspection: View state at any checkpoint.
- Branching: Fork from any checkpoint for exploration.

Streaming

LangGraph supports multiple streaming modes for real-time output:

CrewAI

CrewAI is a framework for orchestrating role-based AI agents that work together on complex tasks. It provides a high-level abstraction for multi-agent systems.

Core Architecture

CrewAI is built around four core entities: Agents, Tasks, Crews, and Processes.

Crew
ā”œā”€ā”€ Process (sequential / hierarchical)
ā”œā”€ā”€ Agents
│   ā”œā”€ā”€ Agent 1 (role, goal, backstory, tools)
│   ā”œā”€ā”€ Agent 2 (role, goal, backstory, tools)
│   └── Agent N (...)
ā”œā”€ā”€ Tasks
│   ā”œā”€ā”€ Task 1 (description, agent, tools)
│   ā”œā”€ā”€ Task 2 (description, agent, tools)
│   └── Task N (...)
└── Memory (optional)
    ā”œā”€ā”€ Short-term
    ā”œā”€ā”€ Long-term
    └── Entity memory

Agents

Agents in CrewAI are defined by their role, goal, backstory, and available tools.

from crewai import Agent

researcher = Agent(
    role="Senior Research Analyst",
    goal="Uncover cutting-edge developments in AI and technology",
    backstory="""You are a seasoned research analyst with over 15 years 
    of experience in technology analysis. Known for deep dives into 
    complex topics and identifying emerging trends.""",
    allow_delegation=False,
    verbose=True,
    tools=[search_tool, scrape_tool],
    llm="gpt-4-turbo",
    max_iter=15,
    max_rpm=10,
    memory=True
)

Agent Parameters:
| Parameter | Description |
|-----------|-------------|
| role | Agent's function within the crew |
| goal | Individual objective the agent is working towards |
| backstory | Context and personality for the agent |
| allow_delegation | Whether agent can delegate tasks to other agents |
| tools | List of tools available to the agent |
| llm | Language model to use (default: gpt-4) |
| max_iter | Maximum iterations before forced answer |
| max_rpm | Maximum requests per minute (rate limiting) |
| verbose | Enable detailed logging |
| memory | Enable memory for this agent |

Tasks

Tasks define what needs to be done and can be assigned to specific agents.

from crewai import Task

research_task = Task(
    description="""Research the latest developments in {topic}. 
    Focus on major breakthroughs in the last 6 months.""",
    expected_output="""A comprehensive 3-page report covering:
    - Key breakthroughs and their implications
    - Companies and researchers leading the work
    - Timeline of major developments""",
    agent=researcher,
    tools=[search_tool],  # Override tools for this specific task
    context=[],  # Context from other tasks
    async_execution=False,  # Whether to run asynchronously
    callback=task_callback_function,
    human_input=False
)

Task Features:
- Context Passing: Tasks can receive context from previous tasks.
- Async Execution: Tasks can run in parallel.
- Callbacks: Hooks for task lifecycle events.
- Human Input: Option to request human input during task execution.
- Output Customization: Define expected output format.

Process Types

CrewAI supports two primary process types:

Sequential Process

Tasks are executed in order, with each task receiving context from the previous.

from crewai import Crew, Process

crew = Crew(
    agents=[researcher, writer, editor],
    tasks=[research_task, writing_task, editing_task],
    process=Process.sequential,
    verbose=True
)

result = crew.kickoff()

Execution Flow:

Task 1 (Research) → Task 2 (Writing) → Task 3 (Editing)
    ↓                    ↓                    ↓
   Output             Output               Final Output

When to Use:
- Simple linear workflows
- Tasks with clear dependencies
- When order of execution matters
- Prototyping and simple automation

Hierarchical Process

A manager agent orchestrates worker agents, decomposing tasks and assigning them dynamically.

manager = Agent(
    role="Project Manager",
    goal="Efficiently manage the team to achieve the project goals",
    backstory="Experienced project manager with a track record of delivering complex projects",
    allow_delegation=True,
    llm="gpt-4-turbo"
)

crew = Crew(
    agents=[researcher, writer, editor, designer],
    tasks=[project_task],
    manager_agent=manager,
    process=Process.hierarchical,
    verbose=True
)

Execution Flow:

Manager Agent
ā”œā”€ā”€ Decomposes project into subtasks
ā”œā”€ā”€ Assigns subtasks to appropriate agents
ā”œā”€ā”€ Monitors progress and quality
└── Synthesizes final output

Worker Agents
ā”œā”€ā”€ Receive assignments from manager
ā”œā”€ā”€ Execute their specific tasks
└── Report results back to manager

When to Use:
- Complex projects requiring planning
- When agent specialization is important
- Dynamic task allocation needed
- Quality control through manager oversight

Role Assignment

CrewAI's role system provides structured agent definition:

# Predefined Roles
from crewai.roles import Role

custom_role = Role(
    name="Data Scientist",
    goal="Extract insights from data",
    backstory="Expert in statistical analysis and machine learning",
    tools=[python_repl,ę•°ę®åˆ†ęž_tools]
)

Role Assignment Strategies:
- Single Role: Each agent has one fixed role.
- Context-Enhanced Roles: Roles are augmented with task-specific context.
- Dynamic Role Adjustment: Agent behavior adjusts based on conversation.

Task Delegation

Agents can delegate subtasks to other agents when allow_delegation=True.

researcher = Agent(
    role="Lead Researcher",
    goal="Coordinate research efforts",
    allow_delegation=True,
    tools=[delegation_tool]
)

# The researcher agent can request help from other agents
# during task execution

Delegation Flow:
1. Agent receives a task.
2. Agent identifies sub-parts that require other expertise.
3. Agent delegates sub-tasks to appropriate agents.
4. Delegated agents return results.
5. Original agent synthesizes all results.

Tool Sharing

Tools can be shared across agents at the crew level or agent level.

from crewai_tools import tool

@tool("WebSearch")
def web_search(query: str) -> str:
    """Search the web for information."""
    return search_results

@tool("Calculator")
def calculator(expression: str) -> str:
    """Evaluate mathematical expressions."""
    return str(eval(expression))

# Agent-level tools

AutoGen / AutoGen Studio

AutoGen is a multi-agent conversation framework developed by Microsoft Research. AutoGen Studio provides a visual interface for designing and debugging agent workflows.

Core Concepts

ConversableAgent

The base class for all agents in AutoGen. It defines the fundamental communication interface.

import autogen

class ConversableAgent:
    """
    Base class for agents that can converse with each other.
    """
    def __init__(self, name, system_message, llm_config, human_input_mode="NEVER"):
        self.name = name
        self.system_message = system_message
        self.llm_config = llm_config
        self.human_input_mode = human_input_mode

Key Methods:
- send(message, recipient, request_reply): Send a message to another agent.
- receive(message, sender, request_reply): Receive a message from another agent.
- generate_reply(messages, sender): Generate a reply based on conversation history.
- initiate_chat(recipient, message): Start a conversation with another agent.

AssistantAgent

An agent that uses an LLM to generate responses.

assistant = autogen.AssistantAgent(
    name="assistant",
    system_message="You are a helpful assistant.",
    llm_config={
        "config_list": [
            {
                "model": "gpt-4",
                "api_key": os.environ["OPENAI_API_KEY"]
            }
        ],
        "temperature": 0.7
    }
)

UserProxyAgent

An agent that can act on behalf of a human user, including executing code and providing feedback.

user_proxy = autogen.UserProxyAgent(
    name="user_proxy",
    human_input_mode="TERMINATE",
    max_consecutive_auto_reply=10,
    code_execution_config={
        "work_dir": "coding",
        "use_docker": False,  # Set to True for sandboxed execution
    }
)

Two-Agent Chat

The simplest pattern: two agents converse to solve a task.

# Create agents
assistant = autogen.AssistantAgent(
    name="assistant",
    llm_config=llm_config
)

user_proxy = autogen.UserProxyAgent(
    name="user_proxy",
    code_execution_config={"work_dir": "coding"}
)

# Initiate conversation
user_proxy.initiate_chat(
    assistant,
    message="Write a Python script to calculate Fibonacci numbers"
)

Chat Flow:

UserProxy → Assistant: "Write a Python script..."
Assistant → UserProxy: "Here's a script..."
UserProxy → Assistant: Executes script, returns output
Assistant → UserProxy: "The result is..."
... continues until termination condition

Group Chat

Multiple agents converse in a group chat managed by a GroupChatManager.

from autogen import GroupChat, GroupChatManager

# Define multiple agents
planner = autogen.AssistantAgent(
    name="Planner",
    system_message="You are a planner. Break down tasks.",
    llm_config=llm_config
)

coder = autogen.AssistantAgent(
    name="Coder",
    system_message="You are a Python programmer.",
    llm_config=llm_config
)

critic = autogen.AssistantAgent(
    name="Critic",
    system_message="Review and critique code.",
    llm_config=llm_config
)

# Create group chat
groupchat = GroupChat(
    agents=[planner, coder, critic, user_proxy],
    messages=[],
    max_round=12,
    speaker_selection_method="auto",  # Options: auto, round_robin, random, manual
    allow_repeat_speaker=False,  # Prevent same speaker twice in a row
)

# Create manager
manager = GroupChatManager(
    groupchat=groupchat,
    llm_config=llm_config
)

# Start conversation
user_proxy.initiate_chat(
    manager,
    message="Create a web scraper for news articles"
)

Speaker Selection Methods:
| Method | Description |
|--------|-------------|
| auto | LLM decides next speaker based on context |
| round_robin | Speakers take turns in fixed order |
| random | Random selection |
| manual | Human selects next speaker |
| Custom | User-defined selection function |

Function Calling and Tool Registration

AutoGen supports function calling for tool integration.

# Define a function/tool
def search_web(query: str) -> str:
    """Search the web for information."""
    import requests
    response = requests.get(f"https://api.search.com?q={query}")
    return response.text

# Register with assistant
assistant = autogen.AssistantAgent(
    name="assistant",
    llm_config={
        "config_list": [...],
        "functions": [{
            "name": "search_web",
            "description": "Search the web",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "Search query"
                    }
                },
                "required": ["query"]
            }
        }]
    }
)

# Register function with user proxy
user_proxy.register_function(
    function_map={
        "search_web": search_web
    }
)

Function Registration for Tool Calling:

# Alternative: function registration with tool calling
@user_proxy.register_for_execution()
@assistant.register_for_llm(description="Search the web")
def web_search(query: str) -> str:
    return search_api(query)

Code Execution Sandbox

AutoGen provides configurable code execution environments:

# Local execution
user_proxy = autogen.UserProxyAgent(
    name="user_proxy",
    code_execution_config={
        "work_dir": "coding",
        "use_docker": False,
        "timeout": 60
    }
)

# Docker sandbox (secure)
user_proxy = autogen.UserProxyAgent(
    name="user_proxy",
    code_execution_config={
        "work_dir": "coding",
        "use_docker": True,
        "docker_container_name": "autogen-sandbox",
        "timeout": 120
    }
)

# Custom executor
user_proxy = autogen.UserProxyAgent(
    name="user_proxy",
    code_execution_config={
        "executor": custom_executor
    }
)

Sequential, Parallel, and Debate Patterns

Sequential Pattern:

Semantic Kernel

Semantic Kernel (SK) is an SDK by Microsoft that integrates LLMs with conventional programming languages.

Core Architecture

Semantic Kernel
ā”œā”€ā”€ Kernel (central orchestrator)
ā”œā”€ā”€ Plugins (collections of functions)
│   ā”œā”€ā”€ Native Functions (code)
│   └── Semantic Functions (prompts)
ā”œā”€ā”€ Planners (auto function chaining)
ā”œā”€ā”€ Memory (semantic memory / vector store)
ā”œā”€ā”€ Connectors (AI services, memory stores)
└── Filters (content moderation, logging)

Kernel

The kernel is the central orchestrator that manages plugins, memory, and AI service connections.

from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion

# Create kernel
kernel = Kernel()

# Add AI service
kernel.add_service(
    AzureChatCompletion(
        deployment_name="gpt-4",
        endpoint="https://your-endpoint.openai.azure.com",
        api_key="your-api-key"
    )
)

# The kernel connects all components
result = await kernel.invoke_prompt_async(
    "What is the capital of France?",
    stream=True
)

Plugins

Plugins are collections of functions that the AI can use.

Native Functions (Code)

from semantic_kernel.plugin import kernel_function

class EmailPlugin:
    @kernel_function(
        description="Send an email to a recipient",
        name="send_email"
    )
    def send_email(self, to: str, subject: str, body: str) -> str:
        """Send an email using the SMTP server."""
        # Email sending logic
        return f"Email sent to {to}"

    @kernel_function(
        description="Get unread email count",
        name="get_unread_count"
    )
    def get_unread_count(self) -> int:
        """Get the number of unread emails."""
        return 42

# Add plugin to kernel
email_plugin = EmailPlugin()
kernel.add_plugin(email_plugin, "Email")

Semantic Functions (Prompts)

from semantic_kernel.functions import KernelFunctionFromPrompt

# Define prompt-based function
summarize_function = kernel.create_function_from_prompt(
    function_name="Summarize",
    plugin_name="TextPlugin",
    prompt="""Summarize the following text in 3 sentences:

    {{$input}}

    Summary:"""
)

# Or use YAML/Prompt template files
# Save as TextPlugin/Summarize/skprompt.txt
kernel.add_plugin_from_directory("./plugins/TextPlugin")

Planners

Planners automatically chain functions together to accomplish complex goals.

HandlebarsPlanner

Uses Handlebars templates for plan generation.

from semantic_kernel.planners import HandlebarsPlanner

planner = HandlebarsPlanner(
    kernel=kernel,
    prompt="""Create a plan to accomplish: {{$goal}}
    Available functions: {{$functions}}"""
)

plan = await planner.create_plan_async(
    goal="Send a summary email to the team"
)

FunctionCallingStepwisePlanner

Iterative planner that uses function calling to build plans step by step.

from semantic_kernel.planners import FunctionCallingStepwisePlanner

planner = FunctionCallingStepwisePlanner(
    kernel=kernel,
    max_tokens=2000,
    max_iterations=10
)

result = await planner.execute_plan_async(
    goal="Research latest AI news and create a report"
)

Planning Process:
1. Decompose the goal into steps.
2. Match steps to available functions.
3. Execute functions in order.
4. Adapt plan based on intermediate results.
5. Return final output.

StepwisePlanner

The original planner that creates step-by-step execution plans.

from semantic_kernel.planners import StepwisePlanner

planner = StepwisePlanner(kernel)

plan = planner.create_plan(goal="Compare stock prices of Apple and Microsoft")

Connectors

Connectors bridge Semantic Kernel with external services.

from semantic_kernel.connectors.ai.open_ai import (
    AzureChatCompletion,
    OpenAIChatCompletion
)
from semantic_kernel.connectors.ai.anthropic import AnthropicChatCompletion
from semantic_kernel.connectors.ai.google import GooglePalmChatCompletion
from semantic_kernel.connectors.memory.chroma import ChromaMemoryStore
from semantic_kernel.connectors.memory.qdrant import QdrantMemoryStore

# Multiple AI connectors
kernel.add_service(AzureChatCompletion(...))
kernel.add_service(OpenAIChatCompletion(...))
kernel.add_service(AnthropicChatCompletion(...))

# Memory connectors
kernel.add_memory_store(ChromaMemoryStore(persist_directory="./chroma"))
kernel.add_memory_store(QdrantMemoryStore(host="localhost", port=6333))

Filters

Filters provide middleware-like capabilities for the kernel pipeline.

from semantic_kernel.filters import (
    FunctionFilter,
    PromptFilter,
    KernelFilter
)

class LoggingFilter(FunctionFilter):
    async def on_function_invoking(self, context):
        print(f"Invoking function: {context.function.name}")

    async def on_function_invoked(self, context):
        print(f"Function completed: {context.function.name}")
        print(f"Duration: {context.duration}")

class SafetyFilter(PromptFilter):
    async def on_prompt_rendering(self, context):
        if contains_harmful_content(context.prompt):
            context.cancel = True

# Add filters to kernel
kernel.add_filter(LoggingFilter())
kernel.add_filter(SafetyFilter())

Memory and Vector Store Integration

from semantic_kernel.memory import SemanticTextMemory
from semantic_kernel.memory.memory_store import MemoryStore

# Configure memory
memory = SemanticTextMemory(
    storage=ChromaMemoryStore(),
    embeddings_generator=kernel.get_service("text_embedding")
)

# Store information
await memory.save_information_async(
    collection="facts",
    text="The Eiffel Tower is in Paris",
    id="fact_1"
)

# Retrieve information
result = await memory.search_async(
    collection="facts",
    query="Where is the Eiffel Tower?",
    limit=3
)

OpenAI / Anthropic / Google Integration

# OpenAI
kernel.add_service(
    OpenAIChatCompletion(
        model_id="gpt-4",
        api_key="...",
        org_id="..."
    )
)

# Azure OpenAI
kernel.add_service(
    

smolagents

smolagents is HuggingFace's framework for building AI agents that can write and execute code.

Core Philosophy

smolagents distinguishes between two types of agents:
- CodeAgent: Writes and executes Python code to accomplish tasks.
- ToolCallingAgent: Uses tool calling APIs (function calling).

Both are built on a shared base architecture.

CodeAgent

The CodeAgent writes Python code to solve tasks, then executes it.

from smolagents import CodeAgent, HfApiModel

# Create a CodeAgent
agent = CodeAgent(
    model=HfApiModel(),
    tools=[],  # Custom tools
    add_base_tools=True,  # Include base tools like Python interpreter
    max_steps=10,
    verbosity_level=1
)

# Run the agent
result = agent.run(
    "Calculate the 50th Fibonacci number and check if it's prime."
)

How CodeAgent Works:
1. Receives a task.
2. Generates Python code to solve it.
3. Executes the code in a sandboxed Python environment.
4. If execution fails, the agent can fix the code and retry.
5. Returns the final result.

ToolCallingAgent

The ToolCallingAgent uses function calling APIs to invoke tools.

from smolagents import ToolCallingAgent, tool

@tool
def search_web(query: str) -> str:
    """Search the web for information."""
    return f"Search results for: {query}"

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

agent = ToolCallingAgent(
    model=HfApiModel(),
    tools=[search_web, calculate],
    max_steps=5
)

result = agent.run("What is 2^10? And search for 'AI news'")

Multi-Agent Hierarchy

smolagents supports hierarchical multi-agent setups through ManagedAgent.

from smolagents import CodeAgent, ManagedAgent, HfApiModel

# Create specialized sub-agents
research_agent = CodeAgent(
    model=HfApiModel(),
    tools=[search_tool],
    add_base_tools=True
)

code_agent = CodeAgent(
    model=HfApiModel(),
    tools=[python_repl],
    add_base_tools=True
)

# Wrap as managed agents
managed_researcher = ManagedAgent(
    agent=research_agent,
    name="researcher",
    description="Researchs topics online and returns findings"
)

managed_coder = ManagedAgent(
    agent=code_agent,
    name="coder",
    description="Writes and executes Python code"
)

# Main agent with sub-agents
main_agent = CodeAgent(
    model=HfApiModel(),
    managed_agents=[managed_researcher, managed_coder],
    tools=[],
    add_base_tools=True
)

# Run
result = main_agent.run(
    "Research quantum computing and write a simulation"
)

Agent Hierarchy:

Main Agent
ā”œā”€ā”€ Managed Agent: Researcher
│   └── Tools: web_search, fetch_page
└── Managed Agent: Coder
    └── Tools: python_repl, file_ops

Tool Sharing

Tools can be defined and shared across agents:

from smolagents import tool, CodeAgent, ToolCollection

# Define tools
@tool
def get_weather(location: str) -> str:
    """Get current weather for a location."""
    return f"Weather in {location}: Sunny, 72°F"

@tool
def get_time(timezone: str) -> str:
    """Get current time in a timezone."""
    return f"Time in {timezone}: 14:30"

# Create a tool collection
tools = ToolCollection([get_weather, get_time])

# Share across agents
agent1 = CodeAgent(tools=tools, ...)
agent2 = CodeAgent(tools=tools, ...)

Hub Integration

smolagents integrates with the HuggingFace Hub for model and tool sharing:

from smolagents import HfApiModel, CodeAgent, ToolCollection

# Use models from HuggingFace Hub
model = HfApiModel(
    model_id="Qwen/Qwen2.5-72B-Instruct",
    token="hf_..."
)

# Load tools from Hub
tools = ToolCollection.from_hub(
    "huggingface/tools-web-search",
    token="hf_..."
)

agent = CodeAgent(model=model, tools=tools)

Gradio UI

smolagents includes a Gradio-based UI for interactive agent usage:

from smolagents import CodeAgent, HfApiModel
from smolagents.gradio_ui import GradioUI

agent = CodeAgent(
    model=HfApiModel(),
    tools=[...],
    add_base_tools=True
)

# Launch UI
GradioUI(agent).launch()

DSPy

DSPy is a framework for programmatically composing and optimizing language model programs.

Core Philosophy

DSPy treats LMs as programmable components. Instead of hand-crafting prompts, you define signatures and modules, then use optimizers to automatically improve performance.

Signatures

Signatures define the input/output behavior of an LM call.

import dspy

# Define a signature
class GenerateAnswer(dspy.Signature):
    """Answer questions based on context."""

    context = dspy.InputField(desc="Contains relevant facts")
    question = dspy.InputField(desc="The question to answer")
    answer = dspy.OutputField(desc="The answer")

# Use the signature
module = dspy.Predict(GenerateAnswer)
result = module(context="Paris is the capital of France.", question="What is the capital of France?")
print(result.answer)  # "Paris"

Signature Fields:
- InputField: Marks input parameters with optional descriptions.
- OutputField: Marks output parameters.
- desc: Natural language description of the field.

Modules

DSPy modules encapsulate LM calls with signatures.

# Basic Predict module
predict = dspy.Predict(GenerateAnswer)

# Chain of Thought
cot = dspy.ChainOfThought(GenerateAnswer)

# ReAct (Reasoning + Acting)
react = dspy.ReAct(GenerateAnswer, tools=[search_tool])

# Multi-Chain Comparison
multi = dspy.MultiChainComparison(GenerateAnswer, M=3)  # Compare 3 chains

# Retrieve then Read (RAG)
rag = dspy.RetrieveThenRead(GenerateAnswer, retriever=retriever)

# Program of Thought (PoT)
pot = dspy.ProgramOfThought(GenerateAnswer)

Optimizers (Teleprompters)

DSPy optimizers automatically tune prompts and few-shot examples.

BootstrapFewShot

from dspy.teleprompt import BootstrapFewShot

# Configure optimizer
optimizer = BootstrapFewShot(
    metric=answer_accuracy,
    max_bootstrapped_demos=8,
    max_labeled_demos=16,
    max_rounds=3
)

# Compile (optimize) the program
compiled_program = optimizer.compile(
    student=program,
    teacher=program,
    trainset=training_data,
    valset=validation_data
)

MIPRO (Multi-Instruction Proposal Optimizer)

from dspy.teleprompt import MIPRO

optimizer = MIPRO(
    metric=answer_accuracy,
    num_bayes_trials=10,
    num_candidates=5,
    verbose=True
)

compiled = optimizer.compile(
    program,
    trainset=training_data,
    num_trials=30
)

MIPRO Features:
- Proposes multiple instruction variations.
- Uses Bayesian optimization to find best combinations.
- Tunes both instructions and few-shot examples.
- More thorough optimization than BootstrapFewShot.

BayesianSignatureOptimizer

from dspy.teleprompt import BayesianSignatureOptimizer

optimizer = BayesianSignatureOptimizer(
    metric=answer_accuracy,
    n_trials=20
)

compiled = optimizer.compile(
    program,
    trainset=training_data
)

Compiled Programs

Once optimized, DSPy programs can be saved and loaded:

# Save compiled program
compiled_program.save("optimized_rag_program.dspy")

# Load and use
loaded_program = dspy.load("optimized_rag_program.dspy")
result = loaded_program(context="...", question="...")

Multi-Stage Pipelines

DSPy can compose multiple modules into pipelines:

class RAGPipeline(dspy.Module):
    def __init__(self, retriever):
        super().__init__()
        self.retriever = retriever
        self.generate_answer = dspy.ChainOfThought(GenerateAnswer)
        self.check_facts = dspy.Predict(VerifyFacts)

    def forward(self, question):
        # Stage 1: Retrieve
        context = self.retriever(question)

        # Stage 2: Generate answer
        answer = self.generate_answer(context=context, question=question)

        # Stage 3: Verify facts
        verification = self.check_facts(answer=answer.answer, context=context)

        return dspy.Prediction(
            answer=answer.answer,
            confidence=verification.confidence
        )

# Create and optimize
pipeline = RAGPipeline(retriever=retriever)
optimized = optimizer.compile(pipeline, trainset=trainset)

Evaluation

DSPy provides built-in evaluation tools:

from dspy.evaluate import Evaluate

# Create evaluator
evaluator = Evaluate(
    devset=test_data,
    metric=answer_accuracy,
    num_threads=4,
    display_progress=True
)

# Evaluate
score = evaluator(compiled_program)
print(f"Accuracy: {score}")

# Custom metrics
def exact_match(example, prediction, trace=None):
    return example.answer.strip() == prediction.answer.strip()

evaluator = Evaluate(devset=test_data, metric=exact_match)

OpenAI Agents SDK

The OpenAI Agents SDK is a lightweight framework for building agent-based applications using OpenAI models.

Core Architecture

OpenAI Agents SDK
ā”œā”€ā”€ Agent (runnable agent instance)
ā”œā”€ā”€ Handoffs (agent-to-agent transfer)
ā”œā”€ā”€ Guardrails (input/output validation)
ā”œā”€ā”€ Tracing (observability)
└── Agent Loop (execution engine)

Agent Loop

The core execution loop:

from agents import Agent, Runner

agent = Agent(
    name="Assistant",
    instructions="You are a helpful assistant.",
    model="gpt-4o"
)

result = Runner.run_sync(agent, "What is the capital of France?")
print(result.final_output)

Agent Loop Process:
1. Receive user input.
2. Process through model.
3. Execute any tool calls.
4. Continue until final output.
5. Return result.

Handoffs

Agents can transfer control to specialized sub-agents:

from agents import Agent, Runner

# Specialized agents
spanish_agent = Agent(
    name="Spanish Agent",
    instructions="You speak Spanish. Respond in Spanish.",
)

french_agent = Agent(
    name="French Agent",
    instructions="You speak French. Respond in French.",
)

# Main agent with handoff functions
def transfer_to_spanish():
    return spanish_agent

def transfer_to_french():
    return french_agent

main_agent = Agent(
    name="Main Agent",
    instructions="Route users to the right language specialist.",
    functions=[transfer_to_spanish, transfer_to_french]
)

result = Runner.run_sync(main_agent, "Parlez-vous franƧais?")

Guardrails

Guardrails validate inputs and outputs:

from agents import Agent, Runner, Guardrail

# Input guardrail
class TopicGuardrail(Guardrail):
    async def check_input(self, agent, input_text):
        if "illegal" in input_text.lower():
            return GuardrailResult(blocked=True, message="Cannot process this request")
        return GuardrailResult(blocked=False)

# Output guardrail
class SafetyGuardrail(Guardrail):
    async def check_output(self, agent, output):
        if contains_harmful_content(output):
            return GuardrailResult(blocked=True, message="Output blocked")
        return GuardrailResult(blocked=False)

agent = Agent(
    name="Safe Agent",
    instructions="Be helpful and safe.",
    input_guardrails=[TopicGuardrail()],
    output_guardrails=[SafetyGuardrail()]
)

Tracing

Built-in tracing for observability:

from agents import Agent, Runner, trace

# Automatic tracing
with trace("my_app"):
    result = Runner.run_sync(agent, "Question 1")
    result2 = Runner.run_sync(agent, "Question 2")

# Manual tracing
trace.add_event("custom_event", {"key": "value"})

Comparison with Older Function Calling

Aspect Agents SDK Older Function Calling
Abstraction Full agent lifecycle Raw function calling
Handoffs Built-in Manual implementation
Guardrails First-class Custom validation
Tracing Built-in None
Multi-turn Managed loop Manual conversation management
State Context management Manual state tracking

Anthropic MCP Client

Anthropic's Model Context Protocol (MCP) client enables standardized tool and resource access.

Architecture

MCP Client
ā”œā”€ā”€ MCP Server Connection
ā”œā”€ā”€ Tool Discovery
ā”œā”€ā”€ Resource Access
ā”œā”€ā”€ Prompt Management
└── Sampling (optional)

Key Concepts

  • MCP Server: Provides tools and resources.
  • Client: Connects to servers and exposes capabilities to LLMs.
  • Tools: Functions the model can call.
  • Resources: Data sources the model can read.
  • Prompts: Reusable prompt templates.

Client Implementation

import asyncio
from anthropic import Anthropic
from mcp import ClientSession, StdioServerParameters

async def main():
    # Connect to MCP server
    server_params = StdioServerParameters(
        command="python",
        args=["mcp_server.py"]
    )

    async with ClientSession(server_params) as session:
        # Initialize connection
        await session.initialize()

        # List available tools
        tools = await session.list_tools()

        # Call a tool
        result = await session.call_tool(
            "search_web",
            {"query": "latest AI news"}
        )

        # Access resources
        resource = await session.read_resource("database://users")

Google Agent Development Kit (ADK)

Google's ADK is a framework for building agents using Google's AI models and tools.

Architecture

Google ADK
ā”œā”€ā”€ Agent (base agent class)
ā”œā”€ā”€ Tools (function, API, code execution)
ā”œā”€ā”€ Memory (conversation, session)
ā”œā”€ā”€ Auth (Google OAuth, service accounts)
└── Deployment (Cloud Run, Cloud Functions)

Key Features

  • Gemini Integration: First-class support for Gemini models.
  • Google Workspace Tools: Gmail, Calendar, Drive integration.
  • Vertex AI Agent Builder: Visual agent builder.
  • Enterprise Security: IAM, VPC-SC, CMEK.
  • Streaming Support: Real-time response streaming.

Agent Definition

from google.adk import Agent
from google.adk.tools import FunctionTool

def search_flights(origin, destination, date):
    """Search for flights."""
    return flight_api.search(origin, destination, date)

agent = Agent(
    model="gemini-2.0-flash",
    tools=[
        FunctionTool(search_flights),
        GoogleWorkspaceToolkit()
    ],
    system_instruction="You are a travel assistant."
)

Amazon Bedrock Agents

Amazon Bedrock Agents enable building agents using foundation models on AWS.

Architecture

Amazon Bedrock Agent
ā”œā”€ā”€ Foundation Model (Claude, Llama, Mistral, etc.)
ā”œā”€ā”€ Action Groups (Lambda functions)
ā”œā”€ā”€ Knowledge Base (RAG with vector store)
ā”œā”€ā”€ Guardrails (content filtering)
└── Orchestration (step-by-step planning)

Key Features

  • Managed Infrastructure: Fully managed by AWS.
  • Multi-Model Support: Choose from various foundation models.
  • Knowledge Base: Built-in RAG with Aurora/OpenSearch.
  • Guardrails: Content filtering and safety controls.
  • CloudWatch Integration: Monitoring and logging.
  • IAM Security: AWS IAM integration.
  • Cost Management: Pay-per-use pricing.

Agent Configuration

import boto3

bedrock_agent = boto3.client("bedrock-agent")

# Create agent
response = bedrock_agent.create_agent(
    agentName="customer-support-agent",
    foundationModel="anthropic.claude-3-sonnet-20240229",
    instruction="You are a helpful customer support agent.",
    actionGroups=[{
        "actionGroupName": "ticket-actions",
        "description": "Actions for managing support tickets",
        "actionGroupExecutor": {
            "lambda": "arn:aws:lambda:...:function:ticket-handler"
        },
        "functionSchema": {
            "functions": [
                {
                    "name": "create_ticket",
                    "description": "Create a support ticket",
                    "parameters": {
                        "customer_id": {"type": "string", "description": "Customer ID"},
                        "issue": {"type": "string", "description": "Issue description"}
                    }
                }
            ]
        }
    }],
    knowledgeBases=[{
        "knowledgeBaseId": "KB123",
        "description": "Product documentation"
    }]
)

Knowledge Base Integration

# Create knowledge base
bedrock_agent.create_knowledge_base(
    name="product-docs",
    roleArn="arn:aws:iam::...:role/bedrock-knowledge-base",
    knowledgeBaseConfiguration={
        "type": "VECTOR",
        "vectorKnowledgeBaseConfiguration": {
            "embeddingModelArn": "arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v2"
        }
    },
    storageConfiguration={
        "type": "OPENSEARCH_SERVERLESS",
        "opensearchServerlessConfiguration": {
            "collectionArn": "arn:aws:aoss:...:collection/docs",
            "vectorIndexName": "bedrock-knowledge-base-index",
            "fieldMapping": {
                "metadataField": "metadata",
                "textField": "text"
            }
        }
    }
)

Agno / Phidata

Agno (formerly Phidata) is a framework for building agent-as-function applications.

Core Architecture

Agno Agent
ā”œā”€ā”€ Model (LLM backend)
ā”œā”€ā”€ Tools (function registry)
ā”œā”€ā”€ Knowledge Base (vector store)
ā”œā”€ā”€ Memory (conversation history)
ā”œā”€ā”€ Storage (persistent state)
└── Monitor (observability)

Agent-as-Function

Agno treats agents as composable functions:

from agno import Agent

# Define agent
agent = Agent(
    name="research-agent",
    model="gpt-4o",
    description="Research assistant that searches the web",
    instructions=[
        "Search the web for relevant information",
        "Cite your sources",
        "Provide comprehensive answers"
    ],
    tools=[web_search, fetch_page],
    show_tool_calls=True,
    markdown=True
)

# Call like a function
result = agent.run("What are the latest developments in quantum computing?")
print(result.content)

Knowledge Base Integration

from agno.knowledge import KnowledgeBase
from agno.vectordb import QdrantDb

# Create knowledge base
knowledge_base = KnowledgeBase(
    vector_db=QdrantDb(
        collection="documents",
        url="localhost:6333"
    ),
    num_documents=5
)

# Add documents
knowledge_base.load_directory("./documents/", formats=[".pdf", ".txt", ".md"])

# Create agent with knowledge base
agent = Agent(
    knowledge_base=knowledge_base,
    search_knowledge=True,
    add_references=True
)

Tool Registry

from agno.tools import tool

@tool(show_result=True)
def get_stock_price(symbol: str) -> dict:
    """Get current stock price for a symbol."""
    return {"symbol": symbol, "price": 150.25, "currency": "USD"}

@tool
def send_email(to: str, subject: str, body: str) -> bool:
    """Send an email."""
    email_client.send(to, subject, body)
    return True

# Tools are automatically registered and available to the agent
agent = Agent(tools=[get_stock_price, send_email])

Pydantic AI

Pydantic AI is a framework for building typed, production-ready AI agents with validation.

Core Architecture

Pydantic AI Agent
ā”œā”€ā”€ Model (OpenAI, Anthropic, Gemini, etc.)
ā”œā”€ā”€ Tools (typed function tools)
ā”œā”€ā”€ Result Types (Pydantic models)
ā”œā”€ā”€ Dependency Injection
ā”œā”€ā”€ Run Context
ā”œā”€ā”€ OpenTelemetry Tracing
└── Logfire Monitoring

Typed Agents

Agents are strongly typed using Pydantic models:

from pydantic_ai import Agent
from pydantic import BaseModel

# Define output type
class CityLocation(BaseModel):
    city: str
    country: str
    latitude: float
    longitude: float

# Agent with typed output
agent = Agent(
    'openai:gpt-4o',
    result_type=CityLocation,
    system_prompt="Extract location information from queries."
)

# Run returns typed result
result = agent.run_sync("What is the capital of France?")
print(result.data)  # CityLocation(city='Paris', country='France', ...)
print(result.data.latitude)  # 48.8566

Dependency Injection

Pydantic AI supports dependency injection for clean code:

from dataclasses import dataclass
from pydantic_ai import Agent, RunContext

@dataclass
class Database:
    connection: str

    async def query(self, sql: str) -> list:
        return [{"name": "Alice"}, {"name": "Bob"}]

agent = Agent(
    'openai:gpt-4o',
    deps_type=Database,
    system_prompt="Query the database to answer questions."
)

@agent.tool
async def get_users(ctx: RunContext[Database]) -> str:
    """Get all users from the database."""
    users = await ctx.deps.query("SELECT * FROM users")
    return f"Found {len(users)} users"

# Inject dependencies at runtime
db = Database(connection="postgresql://...")
result = agent.run_sync("How many users do we have?", deps=db)

Result Validation

from pydantic import BaseModel, Field, validator

class SearchResult(BaseModel):
    title: str
    url: str = Field(..., pattern=r'^https?://')
    relevance_score: float = Field(..., ge=0, le=1)

    @validator('relevance_score')
    def score_must_be_valid(cls, v):
        if v < 0 or v > 1:
            raise ValueError('Score must be between 0 and 1')
        return v

agent = Agent(
    'openai:gpt-4o',
    result_type=list[SearchResult],
    system_prompt="Search and return structured results."
)

# Results are automatically validated
result = agent.run_sync("Search for Pydantic AI framework")
for item in result.data:
    print(f"{item.title}: {item.relevance_score}")

OpenTelemetry Tracing

from pydantic_ai import Agent
from opentelemetry import trace

# Automatic OpenTelemetry instrumentation
agent = Agent(
    'openai:gpt-4o',
    instrument=True  # Enable tracing
)

# Custom spans
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("custom_span"):
    result = agent.run_sync("Some task")

Logfire Monitoring

from pydantic_ai import Agent
import logfire

# Pydantic Logfire integration
logfire.configure()

agent = Agent(
    'openai:gpt-4o',
    name="my-agent",
    instrument=True
)

# All agent runs are automatically logged to Logfire
result = agent.run_sync("Monitor this run")

šŸ“„ MCP (Model Context Protocol) and ACP (Agent Communication Protocol) — 1122 lines

ACPAgentDPODeploymentGPTHermes AgentLLMLoRAMCPMulti-Agent

Overview

The emergence of Large Language Models (LLMs) as general-purpose reasoning engines has created a new challenge: how do these models interact with the outside world? Two protocols have emerged to address this question from different angles:

  • Model Context Protocol (MCP) — An open protocol developed by Anthropic that standardizes how LLMs connect to external tools, data sources, and services.
  • Agent Communication Protocol (ACP) — An emerging specification that defines how autonomous AI agents discover each other and communicate in a multi-agent ecosystem.

While both protocols deal with AI system interoperability, they serve fundamentally different purposes and operate at different layers of the stack. This document provides a comprehensive examination of each protocol, their architectures, use cases, and relationship to one another.


Part I: Model Context Protocol (MCP)

What is MCP?

The Model Context Protocol (MCP) is an open-standard, client-server protocol developed by Anthropic that provides a standardized interface for LLMs to interact with external systems — databases, file systems, APIs, search engines, code repositories, and any other tool or data source. It was first announced in November 2024 and released as an open specification and reference implementation.

MCP solves a fundamental problem in the LLM ecosystem: every integration between an LLM and an external system has historically been a bespoke, one-off implementation. Before MCP, connecting an LLM to a GitHub repository required custom code — and connecting the same LLM to a database required entirely different custom code. This created a fragmented landscape where each integration was brittle, non-portable, and costly to maintain.

MCP addresses this by introducing a universal protocol layer analogous to how USB standardized peripheral connections for computers. Just as USB allows any compliant device (keyboard, mouse, storage) to connect to any compliant host (laptop, desktop, tablet) through a standard interface, MCP allows any LLM application (the "host") to connect to any tool or data source (the "server") through a standard protocol.

Key characteristics of MCP:

  • Open standard: The specification, SDKs, and reference implementations are publicly available under permissive licenses.
  • Protocol-agnostic transport: Supports both local (stdio) and remote (SSE) communication channels.
  • Provider-neutral: Designed to work with any LLM provider, not just Anthropic's Claude.
  • Bidirectional: Servers can both respond to requests and push updates to clients.
  • Extensible: The primitive set (tools, resources, prompts) can be extended as the ecosystem evolves.

Historical Context and Motivation

MCP emerged from a specific pain point at Anthropic. As Claude gained capabilities and users began integrating it into their workflows, Anthropic observed that each integration required a custom adapter layer. This was unsustainable for three reasons:

  1. Duplication of effort: Every MCP-like capability (database access, file operations, API calls) was being reimplemented by every integrator.
  2. Fragmentation: No standard way for tools to declare their capabilities or for models to discover what's available.
  3. Context window pressure: Without a structured protocol, tool descriptions and invocation patterns consumed precious context tokens with inconsistent formatting.

The protocol was designed to be universal — not tied to any specific LLM or provider. While Anthropic developed it, the specification is vendor-neutral and can be implemented by any model provider. This is a deliberate architectural choice: the protocol sits between the application layer and the tool layer, not inside any particular model.

Architecture: Host, Client, Server

MCP uses a three-tier architecture with clearly defined roles:

+----------------------------------+
|         HOST APPLICATION         |
|  (Claude Desktop, IDE, CLI,      |
|   custom application)            |
|                                  |
|  +----------------------------+  |
|  |        MCP CLIENT         |  |
|  |  (embedded in host)       |  |
|  +----------+----------------+  |
+-------------|-------------------+
              |  MCP Protocol
              |  (JSON-RPC over stdio/SSE)
              |
+-------------|-------------------+
|  +----------+----------------+  |
|  |        MCP SERVER         |  |
|  |  (adapter for a tool or   |  |
|  |   data source)            |  |
|  +----------------------------+  |
|                                  |
|  +----------------------------+  |
|  |   EXTERNAL SYSTEM          |  |
|  |  (database, API, files,    |  |
|  |   search engine, etc.)     |  |
|  +----------------------------+  |
+----------------------------------+

Host

The host is the application or environment where the LLM operates and the user interacts. It is the top-level orchestrator that:

  • Manages the lifecycle of MCP client connections
  • Handles user authentication and authorization
  • Controls which servers are available to the LLM
  • Processes the LLM's decisions about which tools to invoke
  • Presents results back to the user

Examples of hosts include:
- Claude Desktop — Anthropic's desktop application
- IDE plugins — VS Code extension, JetBrains plugin, etc.
- CLI tools — Command-line interfaces that wrap LLM interactions
- Custom applications — Any application that embeds an LLM

The host is responsible for security policy enforcement. It decides which MCP servers a given LLM session can access, under what conditions, and with what permissions.

Client

The client is a protocol-level component embedded within the host application. It establishes and manages a 1:1 connection with an MCP server. The client:

  • Initiates the connection using the configured transport
  • Negotiates protocol version and capabilities during initialization
  • Sends requests on behalf of the LLM (tool calls, resource reads, prompt retrievals)
  • Receives responses and optional notifications from the server
  • Manages connection state, reconnection, and error handling

Important architectural note: The MCP client is not the LLM itself. The client is a protocol adapter within the host application that translates between the LLM's decisions and the MCP protocol messages. The LLM operates at a higher level — it decides which tool to call based on descriptions provided through the protocol, but the actual protocol negotiation and message formatting is handled by the client.

A single host may run multiple clients simultaneously, each connected to a different server. For example, Claude Desktop might maintain concurrent connections to a filesystem server, a database server, and a GitHub server.

Server

The server is a lightweight adapter that exposes a specific tool, data source, or service through the MCP protocol. Each server:

  • Implements the MCP protocol specification
  • Registers its capabilities (tools, resources, prompts) during initialization
  • Handles incoming requests from the client
  • Communicates with the underlying external system
  • Can send proactive notifications to the client

Servers are designed to be single-purpose and independently deployable. A typical MCP server wraps one underlying service — for example:

  • mcp-server-filesystem — Exposes local file system operations
  • mcp-server-postgres — Exposes PostgreSQL database queries
  • mcp-server-github — Exposes GitHub API operations
  • mcp-server-slack — Exposes Slack messaging operations
  • mcp-server-puppeteer — Exposes browser automation

Servers can run locally (started as subprocesses by the host) or remotely (accessed over the network). The server is stateless with respect to protocol state — each request contains enough information to be processed independently.

Initialization Sequence

The MCP connection lifecycle follows a structured handshake:

  1. Client sends initialize request — includes protocol version, client capabilities, and client metadata
  2. Server responds with initialized — includes protocol version, server capabilities (list of supported features), and server metadata
  3. Client sends initialized notification — acknowledges the server's response
  4. Normal operation begins — tools, resources, and prompts can be listed and used
  5. Termination — either side can close the connection gracefully

During initialization, both sides negotiate the protocol version (using semantic versioning) and declare which capabilities they support. This allows the protocol to evolve while maintaining backward compatibility.

Transport Layer

MCP defines two primary transport mechanisms, plus extensibility for custom transports.

1. Stdio Transport (Standard Input/Output)

The stdio transport is used for local, subprocess-based communication between a host and a server running on the same machine.

How it works:
- The host launches the MCP server as a child process
- The host writes JSON-RPC messages to the server's stdin
- The server writes JSON-RPC responses to its stdout
- The server writes log/debug information to stderr (which the host can capture separately)
- The host monitors the child process for termination

Characteristics:
- Low latency: No network overhead; communication happens via OS pipes
- High security: The server runs as a local process with the host's permissions; no network exposure
- Simple deployment: No network configuration, authentication, or TLS setup
- Process isolation: Each server runs in its own process; crashes are contained
- Single connection: One client connects to one server per process

Use cases:
- Desktop applications connecting to local tools (file system, local database, local services)
- Development tools (IDE plugins connecting to code analyzers, linters, local git)
- CLI-based LLM workflows where all tools run on the local machine

Example configuration (from Claude Desktop's config.json):

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/path/to/allowed/directory"
      ]
    },
    "github": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-github"
      ],
      "env": {
        "GITHUB_TOKEN": "<your-token>"
      }
    }
  }
}

2. SSE Transport (Server-Sent Events)

The SSE transport enables remote communication between a client and a server over HTTP. It uses a combination of:

  • SSE (Server-Sent Events) — for the server-to-client direction (notifications, responses)
  • HTTP POST — for the client-to-server direction (requests)

How it works:
- The client connects to the server's SSE endpoint
- The server sends an endpoint event containing the URL for client-to-server requests
- The client sends JSON-RPC requests via HTTP POST to that endpoint
- The server streams responses and notifications back through the SSE connection

Characteristics:
- Remote access: Servers can be anywhere on the network
- Persistent connection: The SSE channel stays open for the session duration
- Standard web infrastructure: Works through proxies, load balancers, and CDNs
- HTTP-based security: Can leverage TLS, API keys, OAuth, and other web security mechanisms
- Multiple clients: A remote server can serve multiple clients simultaneously

Use cases:
- Connecting to cloud-based services (SaaS tools, cloud databases, web APIs)
- Multi-user or shared server deployments
- Scenarios where the tool runs in a different environment than the LLM

3. Custom Transports

The protocol specification allows for custom transports beyond stdio and SSE. The SDK provides interfaces for implementing alternative transports, which could include:

  • WebSocket-based transport
  • gRPC-based transport (for high-performance scenarios)
  • Message queue transports (RabbitMQ, Kafka for enterprise deployments)
  • Unix domain sockets (for local inter-process communication with better lifecycle management than pipes)

Message Format

All MCP messages use JSON-RPC 2.0 as the wire format. This is a lightweight, well-established RPC protocol that uses JSON for data serialization. The protocol defines three types of messages:

  • Requests — Client sends to server, expects a response (e.g., tools/call, resources/read)
  • Responses — Server replies to a request (success with result, or error)
  • Notifications — Either side sends without expecting a response (e.g., notifications/initialized, notifications/cancelled)

Example JSON-RPC message (tool call request):

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "read_file",
    "arguments": {
      "path": "/path/to/document.txt"
    }
  }
}

Example JSON-RPC response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Contents of the file..."
      }
    ],
    "isError": false
  }
}

Protocol Primitives

MCP defines three core primitives that servers can expose: Tools, Resources

Part II: Agent Communication Protocol (ACP)

What is ACP?

The Agent Communication Protocol (ACP) is an emerging open specification that defines how autonomous AI agents discover each other, exchange messages, coordinate actions, and share information in a multi-agent environment. While MCP standardizes LLM-to-tool communication, ACP standardizes agent-to-agent communication.

ACP addresses a different but equally important problem: as AI agents become capable of performing complex, multi-step tasks, there is an increasing need for agents to work together. A single agent might be good at code generation but not at deployment; another might excel at data analysis but not at visualization. ACP provides the communication substrate that enables these agents to collaborate.

The protocol is being developed as a community effort with contributions from multiple organizations and researchers working on multi-agent systems. It is not owned by any single company, unlike MCP's origin at Anthropic.

Purpose and Goals

ACP is designed to address several fundamental challenges in multi-agent systems:

Primary Goals:

  1. Agent Discovery — Allow agents to find each other based on capabilities, without requiring a central registry or manual configuration. Agents should be able to join an ecosystem and announce "I can do X, Y, Z."

  2. Standardized Communication — Define a common message format and protocol for agent-to-agent interaction, regardless of what underlying LLM or technology each agent uses internally.

  3. Task Delegation — Enable one agent to request another agent's services. "Agent A, who is good at code generation, can ask Agent B, who is good at security auditing, to review the generated code."

  4. State Sharing — Allow agents to share context, intermediate results, and state information so that collaboration is coherent and agents don't need to rediscover information.

  5. Error Handling — Define how agents communicate failures, timeouts, and unexpected conditions to each other.

  6. Interoperability — Ensure that agents built with different frameworks, LLMs, or architectures can communicate productively.

Design Principles:

  • Decentralized: No central authority or registry required; agents can form ad-hoc networks
  • Extensible: The message types and interaction patterns can grow with the ecosystem
  • Language-agnostic: Works with any programming language or AI framework
  • Security-aware: Includes mechanisms for authentication, authorization, and trust
  • Graceful degradation: Communication failures shouldn't crash the system

How Agents Discover and Communicate

ACP defines a multi-layered approach to agent interaction:

Agent Discovery

ACP supports several discovery mechanisms, ranging from simple to sophisticated:

1. Static Configuration (Direct Connection):
Agents are configured with the addresses of other agents they should communicate with. This is the simplest approach, suitable for small, controlled deployments.

Agent A -> configured to know about Agent B at agent-b.example.com:8080
Agent B -> configured to know about Agent A at agent-a.example.com:8080

2. Capability Registry (Centralized Discovery):
A discovery service maintains a registry where agents register their capabilities and connection endpoints. Other agents query the registry to find agents with specific skills.

Agent A registers: {"capabilities": ["code_generation", "python"], "endpoint": "..."}
Agent B queries: "Find agents with code_review capability"
Registry returns: [Agent C (code_review), Agent D (security_review)]

3. Peer-to-Peer Discovery (Decentralized):
Agents use gossip protocols or distributed hash tables to discover each other without a central point of failure. This is more complex but more resilient and scalable.

4. DNS-Based Discovery:
Using DNS SRV records or well-known endpoints to discover agent services, similar to how service mesh architectures work.

Communication Patterns

ACP defines several fundamental communication patterns:

1. Request-Response (Synchronous):
An agent sends a request and waits for a response. Used for queries, calculations, or any interaction that expects an immediate answer.

Agent A -> Agent B: "Analyze this code for security vulnerabilities"
Agent B -> Agent A: "Found 2 vulnerabilities: SQL injection at line 42, XSS at line 78"

2. Task Delegation (Asynchronous):
An agent delegates a task and receives a task ID. The delegating agent can check back later or receive a callback/notification when the task completes.

Agent A -> Agent B: {"action": "delegate", "task": "audit_code", "payload": {...}, "callback": "agent-a/callback"}
Agent B -> Agent A: {"task_id": "task-123", "status": "accepted"}
... later ...
Agent B -> Agent A: {"task_id": "task-123", "status": "completed", "result": {...}}

3. Broadcast/Multicast:
An agent sends a message to multiple agents simultaneously. Used for announcements, questions ("Does anyone know X?"), or coordination signals.

Agent A -> All: "Starting deployment of version 2.1.0, pausing all dependent operations"
Agent B, Agent C, Agent D -> Agent A: "Acknowledged"

4. Publish-Subscribe:
Agents subscribe to specific topics or event types and receive notifications when relevant events occur. This is useful for ongoing monitoring and alerting scenarios.

Agent A subscribes to: "security_events", "deployment_status"
Agent B publishes: {"topic": "security_events", "severity": "high", "description": "..."}
Agent A receives the notification and takes action

5. Negotiation Protocol:
Agents engage in multi-turn conversations to agree on plans, resolve conflicts, or coordinate complex workflows.

Agent A: "I propose we split the task: I'll generate the code, you test it, she deploys it."
Agent B: "I can test, but I need a test plan first."
Agent C: "I'll handle deployment, but only after tests pass with 90%+ coverage."
Agent A: "Agreed. Let's proceed with this plan."

Message Format

ACP uses a structured message format (typically JSON) with common fields:

{
  "protocol": "acp/1.0",
  "message_id": "msg-abc-123",
  "sender_id": "agent-code-gen-01",
  "receiver_id": "agent-security-audit-02",
  "timestamp": "2026-05-31T10:30:00Z",
  "message_type": "request",
  "interaction_id": "interaction-456",
  "payload": {
    "action": "security_audit",
    "parameters": {
      "code": "def login(username, password): ...",
      "language": "python",
      "depth": "thorough"
    }
  },
  "metadata": {
    "ttl": 300,
    "priority": "normal",
    "signature": "base64signature..."
  }
}

Key fields:
- protocol: Version of ACP being used
- message_id: Unique identifier for this message (for deduplication)
- sender_id/receiver_id: Agent identities
- interaction_id: Correlates messages in a multi-turn interaction
- message_type: request, response, notification, broadcast, etc.
- payload: The actual content, structured by the interaction type
- metadata: Routing, security, and lifecycle information

Relationship with MCP

ACP and MCP are complementary protocols that operate at different layers of an AI system architecture. They are not competitors — they solve different problems.

Architectural Layering

+----------------------------------------------------+
|                  USER INTERFACE                     |
+----------------------------------------------------+
|              AGENT COMMUNICATION LAYER              |
|                   (ACP Layer)                       |
|  Multi-agent coordination, task delegation,         |
|  knowledge sharing between agents                   |
+----------------------------------------------------+
|              TOOL INTEGRATION LAYER                 |
|                   (MCP Layer)                       |
|  Tool discovery, resource access, prompt templates  |
+----------------------------------------------------+
|                    AI MODELS                        |
|  (LLMs, vision models, specialized models)          |
+----------------------------------------------------+
|                 INFRASTRUCTURE                      |
|  (Compute, storage, networking)                     |
+----------------------------------------------------+

In this architecture:
- MCP handles the vertical integration: how an AI agent talks to databases, APIs, files, and other external tools.
- ACP handles the horizontal integration: how multiple AI agents talk to each other.

How They Can Work Together

A realistic multi-agent system might use both protocols:

  1. A coordinator agent

Part III: Comparative Analysis

When to Use Which Protocol

Scenario Use MCP Use ACP Use Both
Single agent needs database access Yes No No
Single agent needs to read/write files Yes No No
Multiple agents collaborating on a task No Yes Yes
Agent needs to query an external API Yes No No
Agent network with specialized roles No Yes Yes
Building a coding assistant Yes No No
Building a multi-agent coding team Yes Yes Yes
Connecting LLM to a CRM system Yes No No
Coordinating across 10+ agents No Yes Yes
Rapid prototyping of tool access Yes No No

Maturity and Risk Assessment

MCP:
- Maturity: High (production-ready SDK, clear specification, growing ecosystem)
- Risk: Low (backed by Anthropic, open source, active community)
- Investment readiness: Ready for production use today
- Learning curve: Moderate (understand JSON-RPC, the three primitives, and transport options)

ACP:
- Maturity: Low (early specification, limited implementations)
- Risk: High (no standard implementation, potential for fragmentation)
- Investment readiness: Experimental only; expect breaking changes
- Learning curve: Steep (multiple design dimensions still being explored)

Ecosystem Comparison

Metric MCP ACP
Official servers ~20+ reference implementations None
Community servers 200+ (and growing rapidly) A few experimental
SDK languages TypeScript, Python (official) Python, TypeScript (experimental)
Integration platforms Claude Desktop, VS Code, CLI None mainstream
Documentation Comprehensive, with tutorials Limited, academic
Conference presence Multiple talks at AI/developer conferences Research workshops

Part IV: Future Directions

MCP Evolution

  1. Broader Model Support: While MCP works with any LLM via function calling, deeper integration with more model providers' native tool formats will reduce friction.

  2. Enhanced Security Model: Expect more sophisticated authorization frameworks, including role-based access control (RBAC) for MCP servers and fine-grained permission scopes.

  3. Streaming Responses: Ability for tools to stream partial results back to the LLM (useful for long-running operations, progress updates, and real-time data).

  4. Transactional Operations: Support for multi-step atomic operations across multiple servers (e.g., "read from DB, write to file, post to Slack" as a single transaction).

  5. Caching and Optimization: Standardized caching mechanisms for resources and tool results to reduce latency and save API calls.

  6. Multi-Server Coordination: Better primitives for MCP servers to coordinate with each other, potentially via the host as an intermediary.

  7. Mobile and Edge Support: Adaptations of the stdio transport for mobile environments and the SSE transport for edge computing scenarios.

ACP Evolution

  1. Standardization: The most critical path for ACP is convergence around a single specification. This may happen organically through community consensus or through adoption by a major AI platform.

  2. Reference Implementation: A robust, well-documented reference implementation in one or two languages is essential for adoption.

  3. Integration with MCP: As both protocols mature, expect tighter integration — perhaps ACP messages carrying MCP-style tool descriptions, or MCP servers being wrapped as ACP-capable agents.

  4. Identity and Trust Frameworks: Development of practical, cryptographically sound approaches to agent identity and trust, potentially leveraging existing standards like DID (Decentralized Identifiers) and Verifiable Credentials.

  5. Capability Ontologies: Standardized vocabularies for describing agent capabilities, making discovery more meaningful and automated.

  6. Testing and Verification Tools: Frameworks for testing multi-agent interactions, simulating failures, and verifying protocol compliance.

  7. Enterprise Features: Audit logging, compliance tracking, SLA management, and billing integration for agent-to-agent interactions.

Convergence Possibilities

The long-term trajectory suggests a convergence where:

  1. MCP becomes the standard tool-access layer for virtually all LLM applications, analogous to how HTTP/SSL became the standard for web communication.

  2. ACP builds on MCP's patterns but extends them to the multi-agent domain, possibly reusing MCP's primitive definitions for agent capability descriptions.

  3. A unified ecosystem emerges where tools are discoverable via MCP, agents are discoverable via ACP, and they share common patterns for security, identity, and message serialization.

  4. Protocol gateways may emerge that translate between MCP and ACP, allowing single tools to be accessed by both individual LLMs (via MCP) and agent collectives (via ACP).


Part V: Implementation Notes

Getting Started with MCP

Minimum setup to use MCP (as a user):
1. Install Claude Desktop (or another MCP-compatible host)
2. Configure MCP servers in the host's configuration file
3. Start interacting with tools through natural language

Minimum setup to create an MCP server:
1. Choose an SDK (TypeScript or Python)
2. Import the MCP SDK and create a server instance
3. Define tools using the tool() decorator or addTool() method
4. Implement the handler functions that execute the tool operations
5. Run the server with stdio or SSE transport

Example (Python, minimal server):

from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions

app = Server("my-server")

@app.list_tools()
async def list_tools():
    return [
        {
            "name": "greet",
            "description": "Greet a person by name",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "Name to greet"}
                },
                "required": ["name"]
            }
        }
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "greet":
        return {"content": [{"type": "text", "text": f"Hello, {arguments['name']}!"}]}
    raise ValueError(f"Unknown tool: {name}")

if __name__ == "__main__":
    from mcp.server.stdio import stdio_server
    import anyio
    anyio.run(stdio_server(app))

Getting Started with ACP

Since ACP is still in an experimental phase, the best approach is:

  1. Follow the community discussions: Watch for developments on GitHub and AI research forums
  2. Experiment with early implementations: Try proof-of-concept agent coordination systems
  3. Contribute to the specification: The protocol is still being shaped; community input is valuable
  4. Build on MCP first: For practical needs today, build MCP servers for your tools; the multi-agent layer can be added later as ACP matures

šŸ“„ OpenCode CLI, Claude Code, and Hermes Agent — Comprehensive Comparison — 667 lines

A detailed technical reference covering what these three AI coding tools are, who built them, their architectures, how they connect to MCP/ACP/tools, and when to choose each one.

ACPAgentClaude CodeDPOGPTHermes AgentInterpretabilityLLMMCPMultimodal

1. OpenCode CLI

1.1 What It Is and Who Created It

OpenCode is an open-source, terminal-based AI coding agent built by the opencode-ai community. It provides a rich Terminal User Interface (TUI) for interacting with various AI models to assist with coding, debugging, and software engineering tasks.

Important status update: The original opencode-ai/opencode repository has been archived (as of 2024/2025). The project has continued and evolved under the name Crush, developed by the same original author in collaboration with the Charm team (charmbracelet/crush). Crush inherits and extends OpenCode's architecture. References to "OpenCode" below describe the original project; Crush is its direct successor.

  • Language: Go
  • License: MIT
  • Repository: https://github.com/opencode-ai/opencode (archived)
  • Successor: https://github.com/charmbracelet/crush (Crush)

1.2 Core Features and Capabilities

Feature Description
Interactive TUI Built with Bubble Tea (Go TUI framework); vim-like keybindings, split panes, session management
Multi-Provider AI Supports OpenAI, Anthropic Claude, Google Gemini, AWS Bedrock, Groq, Azure OpenAI, OpenRouter, GitHub Copilot
Session Management Save, resume, and manage multiple conversation sessions per project
LSP Integration Language Server Protocol support for diagnostics, code intelligence (Go, TypeScript, etc.)
Tool Integration AI can execute shell commands, search files, read/write files, and modify code
MCP Support Model Context Protocol (stdio + SSE) for connecting external tools
File Change Tracking Visual diff of file changes during sessions
Auto-Compact Automatically summarizes conversations when approaching context limit
Custom Commands User-defined commands with named arguments, stored as markdown files
Vim-like Editor Built-in editor with text input for composing messages
External Editor Open your preferred editor for message composition
Persistent Storage SQLite database for conversations and sessions

1.3 Architecture (How It Works Internally)

OpenCode is written in Go and uses the Charm ecosystem of libraries:

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                  TUI Layer                   │
│  Bubble Tea (charmbracelet/bubbletea)        │
│  Lip Gloss (styling), Bubbles (components)   │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│              Agent Loop (ReAct)              │
│  Reasoning -> Tool Selection -> Execution    │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│           Provider Abstraction Layer         │
│  OpenAI │ Anthropic │ Gemini │ Groq │ etc.   │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│          Protocol Integration Layer          │
│  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  │
│  │ MCP stdio│  │ MCP SSE  │  │ LSP (gopls│  │
│  │ servers  │  │ servers  │  │  etc.)    │  │
│  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│              Storage Layer                   │
│  SQLite (sessions, conversations)            │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Agent Loop:

  1. User sends a message via the TUI
  2. OpenCode constructs a prompt with conversation history + system context
  3. The message is sent to the configured AI provider (e.g., Claude, GPT, Gemini)
  4. The model responds with text and/or tool call requests
  5. OpenCode executes approved tools (file read/write, terminal commands, MCP tools)
  6. Results are fed back to the model
  7. The loop continues until the task is complete or the user intervenes

Configuration File (~/.opencode.json or ./.opencode.json):

  • Provider configurations (API keys, endpoints)
  • Agent model selection per task type (coder, task, title)
  • MCP server definitions
  • LSP server configurations
  • Shell settings, debug flags, auto-compact toggle

1.4 ACP/MCP/Tool Integration

OpenCode implements the Model Context Protocol (MCP) as its primary extensibility mechanism:

  • MCP stdio: Connect to local MCP servers via standard input/output (process spawning)
  • MCP SSE: Connect to remote MCP servers via Server-Sent Events (HTTP streaming)
  • Tool Discovery: Automatically discovers available tools from connected MCP servers
  • Permission System: Tools require user approval before execution; configured via allow/deny lists
  • LSP as Tools: Language Server Protocol integration exposes diagnostics as AI-accessible tools

Example MCP configuration:

{
  "mcpServers": {
    "filesystem": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
    },
    "web-api": {
      "type": "sse",
      "url": "https://api.example.com/mcp",
      "headers": { "Authorization": "Bearer token" }
    }
  }
}

Built-in tools (no MCP needed):
- Terminal command execution
- File read/write
- File search (grep)
- LSP diagnostics queries

1.5 Successor: Crush by Charm

Crush is the active successor to OpenCode, developed by the Charm team. It adds:
- Industrial-grade terminal support across macOS, Linux, Windows, Android, FreeBSD, OpenBSD, NetBSD
- Multi-model mid-session switching
- Enhanced MCP support (http, stdio, sse)
- Charm ecosystem reliability (25k+ applications)


2. Claude Code

2.1 What It Is and Who Created It

Claude Code is a proprietary, agentic coding tool built by Anthropic (the company behind Claude AI models). It lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows through natural language commands.

  • Language: TypeScript/Node.js
  • License: Proprietary (free tier available with usage limits)
  • Website: https://code.claude.com
  • Installation: npm install -g @anthropic-ai/claude-code (deprecated), or platform-specific installers

2.2 Core Features and Capabilities

Feature Description
Terminal & IDE Runs in terminal, VS Code extension, JetBrains IDE, desktop app, browser (web), and Chrome extension (beta)
Codebase Understanding Reads your entire codebase, understands project structure, dependencies, and patterns
File Editing Edits files, creates new files, refactors code across multiple files simultaneously
Git Integration Creates commits, manages branches, resolves merge conflicts, writes commit messages
Terminal Commands Runs shell commands, manages processes, installs dependencies
Agentic Loop Can independently plan, execute, and iterate on multi-step software engineering tasks
Memory Project-level instructions (CLAUDE.md) and user-level memory (stored in .claude directory)
Permission Modes Configurable: Plan mode (no destructive changes), Auto mode (automatic approved actions), Ask mode (always prompt)
Context Window Management Prompt caching, automatic context compression, session management
MCP Support Full Model Context Protocol integration (stdio + SSE)
Plugin System Extensible via plugins (custom commands and agents)
Remote Control SSP (Secure Session Protocol) for remote machine control
Computer Use (Preview) GUI-driven interaction: clicking buttons, filling forms, navigating UIs
Slack Integration Run Claude Code from within Slack channels
GitHub Integration Tag @claude on GitHub for PR review and code review tasks
Prompt Caching Built-in caching for reduced latency and costs on repetitive contexts
Privacy Controls Data usage opt-outs, limited retention for sensitive data

2.3 Architecture (How It Works Internally)

Claude Code is built in TypeScript (Node.js) with a modular architecture:

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│              Interface Layer                 │
│  CLI (TUI) │ VS Code Ext. │ Web │ Desktop   │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│              Agent Orchestrator              │
│  Planning -> Tool Selection -> Execution     │
│  Multi-step decomposition, Git workflow      │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│           Claude API Integration             │
│  Anthropic Messages API (Claude models only) │
│  Prompt caching, streaming, tool use         │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│          Tool & Protocol Layer               │
│  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  │
│  │ MCP stdio│  │ MCP SSE  │  │ Plugins   │  │
│  │ servers  │  │ servers  │  │ (custom)  │  │
│  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  │
│  Bash tool  │  File ops  │  Git ops         │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│              Storage Layer                   │
│  ~/.claude/  │  CLAUDE.md  │  Session files  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Agent Loop:

  1. User provides a prompt (natural language task description)
  2. Claude Code analyzes the codebase for context (reads project structure, key files)
  3. The model plans an approach and begins executing steps
  4. Each step may involve: reading files, editing code, running terminal commands, git operations
  5. Results are streamed back to the user in real-time
  6. Claude iterates: fixes errors, tests solutions, refactors as needed
  7. Optionally creates commits or PRs with the completed work

Key architectural details:

  • Enterprise-grade prompt caching: Claude Code aggressively caches system prompts and frequently-used context to reduce cost and latency
  • Session persistence: Conversations are saved and resumable; sessions can be organized by project
  • The .claude directory: Contains project-level instructions (CLAUDE.md), memory files, and session data
  • Permission system: Three modes (Plan, Auto, Ask) control whether Claude can take actions autonomously

2.4 MCP/Tool Integration

Claude Code has robust Model Context Protocol (MCP) support:

  • MCP stdio servers: Local process-based MCP tools
  • MCP SSE servers: Remote MCP tool servers
  • Built-in tools: Bash execution, file operations, git operations, code search, web search, computer use
  • Plugin system: JavaScript/TypeScript plugins add custom commands and agents
  • SSP (Secure Session Protocol): Remote control for running Claude Code on another machine

MCP configuration is stored in the ~/.claude/settings.json file or per-project in .claude/settings.json:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
    }
  }
}

Notable: Claude Code is tied to Anthropic's Claude models only. It does not support OpenAI, Gemini, or other model providers. This is by design — Anthropic optimizes deeply for Claude's tool-use capabilities.

2.5 Pricing

Claude Code uses the Anthropic API under the hood. Users pay for token usage:
- Free tier: Limited usage (rate-limited)
- Paid: Per-token pricing via Anthropic API plans
- Max plan: Higher rate limits and priority access


3. Hermes Agent

3.1 What It Is and Who Created It

Hermes Agent is a self-improving, multi-platform AI agent built by Nous Research (an independent AI research organization known for open-source LLMs like Hermes, Mixtral finetunes, and research into mechanistic interpretability). It is designed as a general-purpose autonomous agent that learns from experience, persists knowledge across sessions, and operates across multiple messaging platforms simultaneously.

  • Language: Python (with Go-based CLI tools)
  • License: MIT (fully open source)
  • Repository: https://github.com/NousResearch/hermes-agent
  • Documentation: https://hermes-agent.nousresearch.com/docs/

3.2 Core Features and Capabilities

Feature Description
Multi-Platform Telegram, Discord, Slack, WhatsApp, Signal, CLI — all from a single gateway process
Multi-Provider Model Access 20+ providers: Nous Portal, OpenRouter (200+ models), OpenAI, Anthropic, NovitaAI, NVIDIA NIM, Xiaomi MiMo, z.ai/GLM, Kimi/Moonshot, MiniMax, Hugging Face, local endpoints
Closed Learning Loop Autonomous skill creation after complex tasks; skills self-improve during use; persistent memory with LLM summarization
Persistent Memory MEMORY.md, USER.md, FTS5 session search, Honcho dialectic user modeling, periodic memory nudges
Skills System Procedural memory (skill files), auto-generated skills, agentskills.io compatible
Subagent Delegation Spawn isolated subagents for parallel workstreams; Python RPC tool calls
Scheduled Automations Built-in cron scheduler with delivery to any platform (daily reports, backups, audits)
Terminal Backends Local, Docker, SSH, Singularity, Modal, Daytona — serverless persistence hibernate on idle
Messaging Gateway Unified gateway for Telegram, Discord, Slack, WhatsApp, Signal; voice memo transcription
MCP Support Full MCP integration (stdio + SSE), tool approval system
Plugins Hermes plugin system for extensibility
Profiles Multiple profiles with separate skills, plugins, cron, memories
API Server OpenAI-compatible API server
Voice Mode Voice input/output, voice memo transcription
Vision Image understanding and analysis
Browser Automation Cloud browser for web tasks
Image Generation Built-in image generation via supported providers
Context Files Project-level context (AGENTS.md, CONTEXT.md)
Security Command approval, DM pairing, container isolation
Trajectory Generation Research-ready batch trajectory generation for training new models

3.3 Architecture (How It Works Internally)

Hermes Agent is built in Python with a modular, service-oriented architecture:

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                   Interface Layer                         │
│  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  │
│  │ CLI TUI │ │ Telegram │ │ Discord  │ │ Slack/etc.  │  │
│  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                   Gateway Layer                           │
│  Message routing, platform abstraction, auth             │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│                Agent Orchestrator                         │
│  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  │
│  │ Agent Loop (ReAct + Reflection)                    │  │
│  │  Plan -> Reason -> Act -> Observe -> Learn         │  │
│  │  Subagent spawning, task decomposition             │  │
│  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│              Provider Abstraction Layer                   │
│  20+ providers, model routing, fallback, reasoning       │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│              Memory & Learning Layer                      │
│  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”    │
│  │Persistent│ │ Skills   │ │ FTS5    │ │ Honcho   │    │
│  │Memory    │ │System    │ │Search   │ │Dialectic │    │
│  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜    │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│              Tool & Protocol Layer                        │
│  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  │
│  │ MCP stdio│ │ MCP SSE  │ │ Plugins   │ │ Cron     │  │
│  │ servers  │ │ servers  │ │ (Python)  │ │ (Sched.) │  │
│  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  │
│  Terminal │ File ops │ Subagent │ Web search │ Vision    │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│              Backend Layer                                │
│  Local │ Docker │ SSH │ Modal │ Daytona │ Singularity    │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Agent Loop (detailed):

  1. Message arrives from any platform (CLI, Telegram, Discord, etc.)
  2. Hermes constructs a system prompt from:
    - SOUL.md (permanent identity — tone, personality, constraints)
    - AGENTS.md / CONTEXT.md (project context)
    - Skills (procedural memory files matched to the task)
    - Memories (persistent cross-session knowledge)
  3. The message is sent to the configured LLM provider
  4. The model responds with reasoning and/or tool calls
  5. Hermes executes approved tools (built-in, MCP, plugins, or subagent delegation)
  6. Results return to the model for further reasoning
  7. After task completion, Hermes may:
    - Create a new skill from the experience (autonomous skill generation)
    - Update memory with new information (periodic memory nudges)
    - Schedule cron jobs for recurring tasks

Key architectural innovations:

  • Hermes Home (~/.hermes/): Configuration, skills, memories, cron, profiles, plugins all organized under a single directory
  • SOUL.md: A permanent identity file that defines the agent's personality, tone, and behavior — distinct from project-level instructions (AGENTS.md)
  • Skills Hub integration: Compatible with agentskills.io for community-shared skills
  • Isolated subagents: Each subagent runs in its own context, can call tools via RPC, and reports back to the parent agent
  • Terminal backends: Choose where your agent runs — locally, on a remote server via SSH, in a Docker container, or serverless (Modal, Daytona)

3.4 MCP/ACP/Tool Integration

Hermes Agent supports MCP (Model Context Protocol) with both stdio and SSE transports:

  • MCP stdio: Connect to local tool servers as child processes
  • MCP SSE: Connect to remote MCP servers over HTTP streaming
  • Tool approval system: Granular permissions for each tool; users can approve/deny/allowlist
  • 40+ built-in tools: Terminal, file operations, search, web, vision, voice, image gen, subagent, etc.
  • Toolset system: Group tools into named sets for different contexts
  • Plugin system: Python-based plugins for deep extensibility
  • Cross-profile isolation: Each profile has its own set of tools, plugins, and MCP servers

MCP Configuration (in ~/.hermes/config.yaml or via hermes config set):

mcp_servers:
  filesystem:
    type: stdio
    command: npx
    args: ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
  web-scraper:
    type: 
Claude Code Hermes Agent
Open Source Yes (MIT) No Yes (MIT)
Multi-Provider Yes No (Claude only) Yes (20+)
Terminal UI Yes Yes Yes
IDE Integration No Yes (VS Code, JetBrains) No
Messaging Platforms No Slack only Telegram, Discord, Slack, WhatsApp, Signal
MCP Support Yes Yes Yes
Persistent Memory No Basic Advanced
Subagents / Parallelism No No Yes
Scheduled Tasks No No Yes
Self-Learning No No Yes
Voice / Vision No Limited vision Yes (both)
Remote Execution No Yes (SSP) Yes (SSH, Docker, Modal)
Best For Terminal coding Enterprise Claude dev Autonomous automation

AI Library of Alexandria — Agents Ā· 5 Docs Ā· 8,090 Lines

Home Ā· June 2026