« Phase 03 · Warmup · Track Overview

Lab 01 — A2A Delegation & a Protocol-Agnostic Core

The problem

Group Compliance runs a sanctions-screening agent. Wholesale runs a payment-investigation agent. The investigation needs the screening. Neither team wants to own the other's code, and neither system can hold the other's data.

That is delegation, and it is a different problem from tool calling. The work is long-running (minutes, sometimes a human approval), it produces artifacts rather than a return value, it can be cancelled, it may need to ask you something halfway through, and — the part that matters most in a bank — it crosses an organizational boundary carrying an identity chain.

A2A specifies the wire format for all of that. It specifies none of the admission control: how deep delegation may go, whether a cycle is forming, whether that agent may see this tenant's data, whether it is cleared for this classification. You build both halves, plus the adapter layer that keeps your kernel from learning any protocol's vocabulary.

What you build

#ComponentWhat it does
1Part, Message, Artifactthe exchange model — text, files and structured data; artifacts as durable outputs distinct from the conversation
2TaskState, TASK_TRANSITIONS, advancethe long-running task lifecycle, including input-required and auth-required, with terminal states absorbing
3AgentCard, Skill, AgentDirectorydiscovery documents and skill-tag search, filtered by tenant and classification before listing
4check_delegationthe admission control A2A does not specify: depth, cycles, tenant, classification, acceptable auth
5A2AServermessage/send, message/stream, tasks/get, tasks/cancel, push-notification config
6A2AClientthe delegating side, which owns the identity chain
7PushNotificationConfig + host allow-listthe callback mechanism, and the SSRF it invites
8InternalTask + A2A/ACP adaptersthe protocol-agnostic core, proven by a lossless round-trip

Key concepts

ConceptWhereWhy it matters
Task ≠ tool callTasklong-running, cancellable, artifact-producing, and able to ask you a question
context_idTask.context_idgroups several delegated tasks into one investigation for the audit record
Chain from the callermessage_streamthe callee cannot forge its own position in the chain
Depth and cycle limitscheck_delegationunbounded delegation is the multi-agent equivalent of an infinite loop, with a bill
Classification flows downhillcheck_delegationyou may not delegate restricted data to an agent cleared for internal
Denied ⇒ no taskmessage_streama refused delegation must leave no state behind
Generator handlerAgentHandlerone agent implementation serves both send and stream
Callback allow-listset_push_configa caller-supplied URL your server fetches is SSRF by construction
Internal vocabularyInternalTaskthe kernel never learns a protocol's words, so a revision is an adapter change
Declared lossinessINTERNAL_TO_ACP_STATUSACP has no rejected; the collapse is pinned by a test, not discovered in production

Files

FileRole
lab.pyyour implementation
solution.pyreference; python solution.py runs an eight-part session
test_lab.py56 tests
requirements.txtpytest

Run

pip install -r requirements.txt
pytest test_lab.py -v
LAB_MODULE=solution pytest test_lab.py -v
python solution.py

Success criteria

  • All 56 tests green against your lab.py.
  • submitted → completed is illegal; working → completed is legal.
  • input-required → rejected is illegal (rejection is an admission decision, not a mid-flight one).
  • Every non-terminal state has a path to a terminal state.
  • to_json() emits exactly ten keys, with no tenant, owner or classification.
  • Delegation depth is bounded exclusively: a chain of 3 is allowed at max_depth=4, a chain of 4 is not.
  • A denied delegation leaves server.tasks empty.
  • delegation_chain on the created task is caller.chain + (caller.agent_id,).
  • A push config with an off-list host, or with no token, is refused.
  • Every TaskState round-trips through the internal vocabulary.
  • An InternalTask with no output round-trips through ACP unchanged.

How this maps to the real stack

This labThe real thingWhat we simplified
A2AServer / A2AClientthe A2A protocol over JSON-RPC (and gRPC/REST bindings), with SSE for streamingno transport, no authentication of the connection
AgentCardthe published /.well-known/agent-card.json, plus authenticated extended cardsours is an object, not an HTTP resource; no signature
TaskStatethe A2A task lifecyclefaithful in shape; real implementations also carry richer status metadata
message/streamSSE with TaskStatusUpdateEvent / TaskArtifactUpdateEventours is a generator; no reconnection or event ids
PushNotificationConfigthe real config method, with webhook auth (JWT/bearer)we validate the host and the token's presence, not its signature
check_delegationnothing in the protocol — your control planethis is the point of the lab
AgentDirectorya private agent registry; hyperscaler catalogues (Azure AI Foundry, Bedrock, Agent Engine)no storage, no approvals
ACP adapterthe real ACP REST shapeours captures the envelope and status vocabulary, not the full spec

Honest limits. No transport, no connection authentication (the token exchange that makes the identity chain real is Phase 08), no retry or timeout semantics, no partial-artifact streaming with append, and no negotiation of input/output modes.

Extensions

  1. Streaming artifacts. Implement append=True chunking so a long report streams. Then make the client assemble chunks correctly when one arrives out of order.
  2. Real identity. Replace CallerContext with a verified JWT carrying an act chain (RFC 8693). Have check_delegation read the chain from the token rather than a parameter, and watch how much of the lab's trust model tightens.
  3. Timeouts and compensation. Give a delegated task a deadline; on expiry, cancel it and run a compensating action. Then ask what "cancelled" means for a callee that already moved money.
  4. Federated directories. Two directories in two trust domains, with an explicit federation agreement. Which fields must be re-verified rather than trusted?
  5. Card signing. Sign agent cards and verify on discovery, so a rogue endpoint cannot claim to be the sanctions agent.
  6. A third adapter. Add an adapter for a hyperscaler fabric's task shape and confirm InternalTask still needs no changes. If it does, your core was not protocol-agnostic.

Interview / resume bullets

  • "Built the bank's agent-to-agent delegation layer on A2A: agent cards with skill-based discovery, the long-running task lifecycle with input-required and cancellation, artifacts as first-class outputs, and push notifications with a callback allow-list."
  • "Added the admission control A2A leaves undefined — bounded delegation depth, cycle detection, tenant and data-classification checks, and a minimum authentication scheme — so a multi-agent flow cannot recurse, loop, or carry restricted data to an uncleared agent."
  • "Kept the kernel protocol-agnostic: an internal task model with A2A and ACP adapters at the edge, verified by a lossless round-trip test, so a protocol revision is an adapter change rather than a data migration."
  • "Made the delegation chain non-forgeable by deriving it from the caller's verified context rather than the message body, so every hop of a multi-agent flow is attributable in the audit record."