Step 1: Meet the AgentCard
Two patterns, two problem spaces
In Lab 3 you built multi-agent orchestration with LangGraph — a supervisor routing queries to specialist agents (researcher, calculator, writer) all inside one Python process. That's great when you control all the agents.
A2A solves a different problem: what if you don't control the other agent? What if it was built by another team, in another language, running on another host?
Side by side
| Multi-Agent Orchestration (Lab 3) | Agent-to-Agent (A2A) | |
|---|---|---|
| Where agents run | Same Python process | Separate processes / hosts / orgs |
| Coupling | Tight (shared code, shared types) | Loose (HTTP + AgentCard) |
| Language | All Python | Any (Python, TS, Rust, Go) |
| Ownership | One team | Can be different teams |
| Discovery | Hardcoded routing logic | AgentCard (URL + capabilities) |
| Transport | Function calls in LangGraph | HTTP + JSON-RPC (or SSE for streaming) |
Use A2A when: different teams own different agents, polyglot services, cross-org integration, or you need to be discoverable by future callers you haven't coordinated with.
Use orchestration when: one team owns everything, latency matters, you need tight shared state.
A2A adds network overhead and serialization. Only pay for those when you're crossing a boundary (process, team, language, org).
The central artefact: the AgentCard
Everything interesting about A2A flows through one JSON document — the AgentCard. It's the public manifest an agent publishes so any client (even one it's never met) can:
- know its name, version, and what it can do (skills)
- know how to reach it (url, preferred_transport)
- know how to talk to it (capabilities: streaming? push?)
- know how to authenticate (covered in step 2)
The Python SDK models it as a Pydantic type in a2a.types. You'll spend the rest of this lab building real AgentCards — in step 1 you just instantiate the minimum viable one, serialize it, and read it.
Your task
In smoke.py:
- Import
AgentCard,AgentSkill, andAgentCapabilitiesfroma2a.types. - Build a minimal
hello-agentAgentCard with:name="hello-agent",description,url="http://localhost:9999/"version="0.1.0",protocol_version="0.2.0",preferred_transport="JSONRPC"default_input_modes=["text"],default_output_modes=["text"]capabilities=AgentCapabilities(streaming=False, push_notifications=False)- one
AgentSkill(id="say-hello", name="Say Hello", description=..., tags=["hello"])
- Print the card as pretty JSON via
card.model_dump_json(indent=2, by_alias=True). - Print a one-line summary at the end:
discovery: name={name} skills={ids} url={url}.
When you run it, you should see a proper JSON manifest printed. That JSON is what a future agent — in any language — would fetch at /.well-known/agent-card.json to learn about yours.
NCP-AAI exam note: Agent architecture decisions are tested heavily. You should know: when network/process boundaries justify A2A overhead, what AgentCards enable (discovery without coordination), and why A2A is emerging alongside MCP as a foundational agent-interop protocol.
smoke.py, the file you edit18 lines
# Step 1: Meet the AgentCard — inspect a minimal A2A agent manifest.
# TODO: import AgentCard, AgentSkill, AgentCapabilities from a2a.types
# TODO: build a minimal AgentCard for a 'hello-agent' with:
# - name='hello-agent', description=<one sentence>, url='http://localhost:9999/'
# - version='0.1.0', protocol_version='0.2.0', preferred_transport='JSONRPC'
# - default_input_modes=['text'], default_output_modes=['text']
# - capabilities=AgentCapabilities(streaming=False, push_notifications=False)
# - one AgentSkill(id='say-hello', name='Say Hello', description=..., tags=['hello'])
card = ______
# TODO: print the card as pretty JSON (card.model_dump_json(indent=2, by_alias=True))
# TODO: print a 1-line discovery summary
# discovery: name=<name> skills=<skill ids> url=<url>