The A2A protocol and deep agents

Agent-to-agent messaging, agent cards, and the deep-agent pattern for long-horizon research tasks.

πŸ•ΈοΈ Module 4 7 min read Not started

Why this matters

Two of the highest-signal 2026 skills:

  • A2A (Agent-to-Agent) β€” Google's open protocol (April 2025) that lets agents from different vendors and stacks interoperate. While MCP standardises agent ↔ tool, A2A standardises agent ↔ agent.
  • Deep Agents β€” long-horizon planning with sub-agents, virtual filesystem, episodic memory, self-reflection. The pattern behind Manus, Devin, Lindy, and many enterprise "research analyst" agents.

Putting both on your CV in 2026 is a distinct differentiator.

Learning objectives

  1. Understand A2A, Agent Cards, and tasks/messages/parts.
  2. Run a minimal A2A producer and consumer.
  3. Build a Deep Agent with planning, sub-agents, and a virtual filesystem.
  4. Map A2A and Deep Agents back to LangGraph patterns.

1. Why A2A exists

MCP solved tool integration. But your agent often needs to call another agent that you do not own β€” a partner's billing agent, a vendor's deep-research agent, an internal team's HR agent. Without a standard, it is NΓ—N integrations.

A2A defines a tiny REST/JSON-RPC + SSE protocol where:

  • Each agent publishes an Agent Card at /.well-known/agent.json advertising name, description, skills, auth.
  • A client opens a task (tasks.send or tasks.sendSubscribe for streaming).
  • The remote agent returns a stream of messages containing parts (text, file, structured JSON).
  • Status moves through submitted β†’ working β†’ input-required β†’ completed/failed/canceled.

Result: any A2A-speaking client can use any A2A-speaking agent. Like REST for agents.


2. Hello, A2A in Python

a2a-sdk ships official Python bindings.

powershell
uv add a2a-sdk

Producer (the agent)

python
# producer_agent.py
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore
from a2a.types import AgentCard, AgentSkill, AgentCapabilities

card = AgentCard(
    name="MathTutor",
    description="Solves math problems step-by-step.",
    url="http://localhost:9000/",
    version="1.0.0",
    capabilities=AgentCapabilities(streaming=True),
    skills=[
        AgentSkill(id="solve", name="Solve a math problem",
                   description="Returns the worked solution.",
                   input_modes=["text"], output_modes=["text"]),
    ],
)

class MathExecutor:
    async def execute(self, request_context, event_queue):
        q = request_context.get_user_input_text()
        # Replace this with your real LLM call
        await event_queue.send_text(f"Step 1: parse '{q}'\nStep 2: compute\nAnswer: 42")

handler = DefaultRequestHandler(agent_executor=MathExecutor(), task_store=InMemoryTaskStore())
app = A2AStarletteApplication(agent_card=card, http_handler=handler).build()
# Run with: uv run uvicorn producer_agent:app --port 9000

Consumer (a client agent)

python
# consumer.py
import asyncio
from a2a.client import A2ACardResolver, ClientFactory, ClientConfig
import httpx

async def main():
    async with httpx.AsyncClient() as http:
        card = await A2ACardResolver(http, base_url="http://localhost:9000").get_agent_card()
        client = ClientFactory(ClientConfig(httpx_client=http)).create(card)
        async for event in client.send_message_streaming(
            message_text="What is 6 * 7?", task_id="t1",
        ):
            print(event.text or event.status)

asyncio.run(main())

You now have one agent calling another, vendor-agnostic, over HTTP. Wrap your LangGraph or CrewAI agent inside an A2A executor and you immediately offer it as a service to anyone speaking A2A.


3. A2A best practices (2026)

  • Publish a clear Agent Card. Include scopes/permissions and example calls.
  • Streaming over SSE for any task >2 seconds.
  • Auth via OAuth 2.1 / mTLS / API keys declared in the card.
  • Push notifications for long tasks (the spec supports webhook callbacks).
  • Robust error model β€” A2A defines task lifecycle states; honour them.
  • Versioning β€” your card has a version; bump on breaking changes.

A2A and MCP are complementary: agents call tools via MCP and call other agents via A2A.


4. Deep Agents β€” the long-horizon pattern

A "deep agent" can plan over hours and produce hundreds of artifacts. The recipe: short-term reactive loops are not enough; you need explicit planning, sub-agents, a virtual filesystem, and reflection.

The four ingredients (per Hyung Won Chung / LangChain's "Deep Agents" article and the open-source deepagents library):

  1. A planner that writes and updates a plan.md of steps.
  2. Sub-agents that the main agent spawns to do focused work (research, code, write).
  3. A virtual filesystem (in-memory or on disk) where the agent reads/writes intermediate notes β€” keeping context windows small and durable.
  4. Self-reflection / verification between steps.

Minimal Deep Agent skeleton with LangGraph

python
class S(TypedDict):
    goal: str
    plan: list[str]                  # ordered steps
    files: dict[str, str]            # virtual fs: filename -> content
    step_index: int
    done: bool

def planner(state):
    if state["plan"]: return {}      # plan exists
    plan = json.loads(llm.invoke(f"Break this goal into 5-7 steps as JSON list:\n{state['goal']}").content)
    return {"plan": plan, "step_index": 0}

def step_runner(state):
    step = state["plan"][state["step_index"]]
    spawned = sub_agent_for(step).invoke({"task": step, "files": state["files"]})
    new_files = {**state["files"], **spawned["files"]}     # merge artifacts
    return {"files": new_files}

def reflect(state):
    judgement = critic.invoke(
        f"Did step '{state['plan'][state['step_index']]}' succeed?\n"
        f"Files updated: {list(state['files'].keys())}"
    ).content.lower()
    if "no" in judgement:
        return {}                                          # retry same step
    next_i = state["step_index"] + 1
    return {"step_index": next_i, "done": next_i >= len(state["plan"])}

g = StateGraph(S)
g.add_node("planner",  planner)
g.add_node("run_step", step_runner)
g.add_node("reflect",  reflect)
g.add_edge(START, "planner")
g.add_edge("planner", "run_step")
g.add_edge("run_step", "reflect")
g.add_conditional_edges("reflect", lambda s: "end" if s["done"] else "loop",
                        {"end": END, "loop": "run_step"})
deep = g.compile(checkpointer=cp)

The virtual filesystem is the trick that makes "long-horizon" work. Instead of holding 200k tokens in the prompt, the agent writes notes to files and loads only the ones the next step needs.

Open-source library: deepagents

LangChain ships deepagents (an opinionated LangGraph wrapper) implementing exactly this pattern:

python
# uv add deepagents
from deepagents import create_deep_agent

agent = create_deep_agent(
    instructions="You are a research assistant producing reports.",
    tools=[web_search, read_file, write_file],
    sub_agents=[
        {"name": "researcher", "description": "Gathers facts", "instructions": "..."},
        {"name": "writer",     "description": "Writes prose",  "instructions": "..."},
    ],
)
result = agent.invoke({"messages": [{"role":"user","content":"Write a 2000-word report on agentic AI in 2026"}]})

It handles the planner, file tools, and sub-agent spawning under the hood. Use it as a starting point, customise as needed.


5. When to reach for "deep" vs "shallow"

TaskPattern
Answer a single questionShallow ReAct agent
2-5 step taskLangGraph linear / supervisor
Hours-long plan, many artifactsDeep Agent
External multi-agent collaborationA2A

Deep agents cost more per run but earn it on tasks where shallow agents get lost mid-way (think: "produce a 30-page market report with 10 charts and 50 citations").


6. Combining the protocols

Real production stacks compose all three:

  • MCP servers expose data and tools.
  • LangGraph runs the agent logic.
  • A2A exposes that agent as a service to other teams.
[Other team's agent]
        ↓ A2A
[Your A2A endpoint] ─→ [LangGraph deep agent] ─→ [MCP servers (KB, billing, search)]

Hands-on lab (6 hours)

Build a deep research agent:

Spec:

  • Goal: produce a Markdown report with citations, executive summary, and 1-2 charts.
  • Tools (MCP server): web_search, fetch_url, summarise, save_file.
  • Sub-agents: researcher, analyst, writer.
  • Virtual filesystem on disk under out/{run_id}/.
  • Wraps the agent in an A2A server so other agents can call tasks/sendSubscribe with a goal.

Acceptance:

  • A 5-step plan is generated, each step runs and writes artifacts.
  • Reflection re-runs failed steps once.
  • A different agent (LangGraph chat agent on your laptop) calls the A2A server and streams progress to the user.
  • README includes Mermaid diagram, A2A Agent Card screenshot, sample run with artifacts.

This single project demonstrates LangGraph + MCP + A2A + Deep Agents in one repo. Ship it. It is the most "wow" project on this curriculum.


Common pitfalls

  1. Skipping the planner. Without an explicit plan, deep agents wander.
  2. Putting everything in prompt. That's why the virtual FS exists.
  3. No reflection step. Errors compound silently.
  4. A2A without auth. Public endpoints get abused.
  5. Mismatched A2A versions. Pin SDK versions.

Self-check

  1. What does an Agent Card declare and where is it served from?
  2. Why use SSE for A2A streaming?
  3. Why does the virtual filesystem reduce context length?
  4. How does reflection prevent error compounding?
  5. How do A2A and MCP complement each other?

References

Sign in to save your progress and earn badges.