Phase 12 · Multi-Agent Collaboration (Agent-to-Agent, A2A)
Why
Phase 5 compared several "multi-agent workflow" patterns; Phase 11 used MCP to let one agent call external tools (agent ↔ tool).
But in real production, a single agent rarely works alone — multiple agents often cooperate: a "researcher agent" gathers facts, a "writer agent" turns them into prose, and a "supervisor agent" orchestrates. That is A2A (Agent-to-Agent).
MCP is agent-to-tool; A2A is agent-to-agent. Together they form the bigger picture of "agent interconnection".
This Phase uses a native LangGraph multi-agent (supervisor + worker) — no extra protocol SDK, runs with a single make run. It perfectly echoes the tutorial's thesis: StateGraph + Node + Conditional Edge.
What
A collaborative system built from three state graphs:
- supervisor: an agent that uses an LLM to decide the next step — outputs
researcher/writer/FINISH. - researcher: an agent that only lists key points.
- writer: an agent that turns points into prose.
All three share one messages state and can "see" each other's output; a conditional edge routes between workers based on the supervisor's decision.
┌─────────────┐
START → │ supervisor │ ──researcher──→ ┌────────────┐
└─────────────┘ ←──────────────│ researcher │
│FINISH→END └────────────┘
└──writer──→ ┌──────────┐
│ writer │ ──→ back to supervisor
└──────────┘How
1. Shared state
next records the supervisor's routing decision; messages accumulates via add_messages.
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], add_messages]
next: str2. Two workers (both agents)
Wrap each role prompt into an independent agent with create_react_agent. Note: the role prompt is injected only for this call via SystemMessage and is not written into the shared state, keeping the conversation trace clean.
async def call_researcher(state: AgentState) -> dict:
agent = create_react_agent(get_chat_model(), tools=[])
result = await agent.ainvoke(
{"messages": [SystemMessage(content=RESEARCHER_PROMPT)] + list(state["messages"])}
)
answer = result["messages"][-1]
return {"messages": [answer]} # append only the final answer to avoid duplication3. Supervisor routing node
The supervisor node calls the LLM to decide, with a fallback (only accept a valid token):
async def supervisor(state: AgentState) -> dict:
model = get_chat_model()
decision = await model.ainvoke(
[HumanMessage(content=ROUTING_INSTRUCTION)] + list(state["messages"])
)
text = decision.content.strip()
for token in ("FINISH", "writer", "researcher"):
if token in text:
text = token
break
else:
text = "FINISH"
return {"messages": [AIMessage(content=f"(supervisor chose: {label})")], "next": text}4. Assemble the graph with a conditional edge
builder = StateGraph(AgentState)
builder.add_node("supervisor", supervisor)
builder.add_node("researcher", call_researcher)
builder.add_node("writer", call_writer)
builder.add_edge(START, "supervisor")
builder.add_conditional_edges(
"supervisor", should_continue,
{"researcher": "researcher", "writer": "writer", "FINISH": END},
)
builder.add_edge("researcher", "supervisor")
builder.add_edge("writer", "supervisor")
graph = builder.compile()Key point: after a worker finishes, it returns to the supervisor, which decides the next step — that is the essence of "supervisor + worker" multi-agent orchestration.
Run
make run p=12
# or python -m examples.p12.hello_chainThe example asks "explain what an Agent is, in three sentences, to a non-technical person". You'll see the terminal print the supervisor's routing decisions, the researcher's points, the writer's prose, and the final messages trace. A typical run:
[supervisor] routing → researcher
[worker] researcher output:
- An agent is software that perceives its environment and acts autonomously
- It uses an LLM to make decisions
- It can call tools to get things done
[supervisor] routing → writer
[worker] writer output:
An agent is like a little assistant……
[supervisor] routing → FINISHSummary
- A2A = multiple agents cooperating; MCP = agent calling tools. Together they form "agent interconnection".
- A native LangGraph multi-agent is simply state graph + node + conditional edge: each agent is a node, the supervisor's decision is the conditional edge.
- As in Phase 11, workers and supervisor share
messages, but role prompts stay out of the shared state to keep the trace clean. - Next steps: turn workers into real sub-agents behind MCP servers, adopt the
langgraph-supervisorstandard handoff pattern, or connect across processes with the Google A2A protocol (this tutorial focuses on the native paradigm and does not expand on the protocol standard).