Task 3 — Connect remote agents with A2A
Part of the Build multi-agent solutions with the Agent Framework lab. New here? Start with Getting started.
Set up (start here): This task needs a Foundry project and the starter code. If you haven’t already, complete Getting started to create your project, clone the code, and set
PROJECT_ENDPOINTandMODEL_DEPLOYMENT_NAMEinPython/.env. Then, from theLabfiles/C-build-multi-agent-solutions-with-agent-frameworkfolder, verify you’re ready:
python setup/check_env.py --task 3
Continuing from a previous task? If you just finished an earlier task in the same
Pythonfolder, your project, virtual environment, and.envare already set — go straight to Create a discoverable agent below.
So far your agents have lived in a single process. Real systems are often split across services — each agent runs on its own, and they collaborate over the network. The Agent-to-Agent (A2A) protocol is a standard way for agents to advertise what they can do and send each other work. In this task you’ll build a Tailwind Traders trip-planning system from three remote agents: a trip-title agent suggests a headline, a trip-itinerary agent drafts an outline, and a routing agent discovers both and delegates each request to the right one.
What is the A2A protocol?
The Agent-to-Agent (A2A) protocol lets agents in separate processes discover and call one another. Each agent publishes an agent card — a small document describing its name, skills, and endpoint — so other agents can find it at runtime. One agent (here, the routing agent) reads those cards, decides who should handle a request, and sends a message over HTTP; the remote agent does the work and returns a response.
Open the Python folder and activate the virtual environment from Getting started (.\labenv\Scripts\Activate.ps1), then continue below.
The starter code for this task is organized into one folder per agent, plus a client and a launcher:
Python
├── outline_agent/ # remote agent: drafts a trip itinerary outline (provided complete)
│ ├── agent.py
│ ├── agent_executor.py
│ └── server.py
├── routing_agent/ # orchestrator that discovers and delegates to the other agents
│ ├── agent.py
│ └── server.py
├── title_agent/ # remote agent: suggests a guided-trip title
│ ├── agent.py
│ ├── agent_executor.py
│ └── server.py
├── client.py # sends your prompt to the routing agent
└── run_all.py # launches all three agent servers
Each agent folder contains the Foundry agent code and a server to host it. The routing agent
discovers and communicates with the trip-title and trip-itinerary agents. The client
lets you submit prompts to the routing agent. run_all.py launches all the servers.
The
outline_agent(trip-itinerary agent) is provided complete as a reference — you’ll build the equivalent code in thetitle_agent, then wire up therouting_agent.
Create a discoverable agent
In this task you complete the trip-title agent that suggests headlines for Tailwind Traders guided trips. You also define the agent’s skills and card, which the A2A protocol uses to make the agent discoverable.
Tip: As you add code, keep the indentation aligned with the comments.
-
Open title_agent/agent.py.
-
Find the comment Create the agents client and add the code to connect to your Foundry project:
# Create the agents client self.client = AgentsClient( endpoint=os.environ['PROJECT_ENDPOINT'], credential=DefaultAzureCredential( exclude_environment_credential=True, exclude_managed_identity_credential=True ) ) -
Find the comment Create the title agent and add the code to create the agent:
# Create the title agent self.agent = self.client.create_agent( model=os.environ['MODEL_DEPLOYMENT_NAME'], name='trip-title-agent', instructions=""" You are a helpful trip marketing assistant for Tailwind Traders. Given a region or activity the customer wants to explore, suggest a single clear and catchy guided-trip title. """, ) -
Find the comment Create a thread for the chat session and add:
# Create a thread for the chat session thread = self.client.threads.create() -
Find the comment Send user message and add:
# Send user message self.client.messages.create(thread_id=thread.id, role=MessageRole.USER, content=user_message) -
Find the comment Create and run the agent and add:
# Create and run the agent run = self.client.runs.create_and_process(thread_id=thread.id, agent_id=self.agent.id)The rest of the file processes and returns the agent’s response.
-
Save the file (Ctrl+S). Now share the agent’s skills and card with the A2A protocol.
-
Open title_agent/server.py.
-
Find the comment Define agent skills and add:
# Define agent skills skills = [ AgentSkill( id='generate_trip_title', name='Generate Trip Title', description='Generates a guided-trip title based on a region or activity', tags=['title'], examples=[ 'Can you give me a title for a hiking trip in Patagonia?', ], ), ] -
Find the comment Create agent card and add the metadata that makes the agent discoverable:
# Create agent card agent_card = AgentCard( name='Tailwind Traders Trip Title Agent', description='An intelligent trip-title generator agent powered by Foundry. ' 'I can help you generate catchy titles for Tailwind Traders guided trips.', url=f'http://{host}:{port}/', version='1.0.0', default_input_modes=['text'], default_output_modes=['text'], capabilities=AgentCapabilities(), skills=skills, ) -
Find the comment Create agent executor and add:
# Create agent executor agent_executor = create_foundry_agent_executor(agent_card) -
Find the comment Create request handler and add:
# Create request handler request_handler = DefaultRequestHandler( agent_executor=agent_executor, task_store=InMemoryTaskStore() ) -
Find the comment Create A2A application and add:
# Create A2A application a2a_app = A2AStarletteApplication( agent_card=agent_card, http_handler=request_handler )This creates an A2A server that shares the trip-title agent’s information and handles incoming requests using the agent executor.
-
Save the file (Ctrl+S).
Enable messages between the agents
In this task you use the A2A protocol to let the routing agent send messages to the other agents, and let the trip-title agent receive them by completing its agent executor.
-
Open routing_agent/agent.py.
The routing agent orchestrates the system: when a user message arrives, it starts a thread, uses
create_and_processto decide which remote agent should handle the request, and routes the message to that agent over HTTP with thesend_messagefunction. Thesend_messagemethod is async and must be awaited for the run to complete. -
Find the comment Retrieve the remote agent’s A2A client using the agent name and add:
# Retrieve the remote agent's A2A client using the agent name client = self.remote_agent_connections[agent_name] -
Find the comment Construct the payload to send to the remote agent and add:
# Construct the payload to send to the remote agent payload: dict[str, Any] = { 'message': { 'role': 'user', 'parts': [{'kind': 'text', 'text': task}], 'messageId': message_id, }, } -
Find the comment Wrap the payload in a SendMessageRequest object and add:
# Wrap the payload in a SendMessageRequest object message_request = SendMessageRequest(id=message_id, params=MessageSendParams.model_validate(payload)) -
Find the comment Send the message to the remote agent client and await the response and add:
# Send the message to the remote agent client and await the response send_response: SendMessageResponse = await client.send_message(message_request=message_request) -
Save the file (Ctrl+S). The routing agent can now discover and message the remote agents. Next, complete the trip-title agent’s executor so it can handle those incoming messages.
-
Open title_agent/agent_executor.py.
The
AgentExecutorclass must implementexecuteandcancel. Thecancelmethod is provided. Theexecutemethod uses aTaskUpdaterobject to manage events and signal when the task is complete — add the execution logic below. -
In the
executemethod, find the comment Process the request and add:# Process the request await self._process_request(context.message.parts, context.context_id, updater) -
In the
_process_requestmethod, find the comment Get the title agent and add:# Get the title agent agent = await self._get_or_create_agent() -
Find the comment Update the task status and add:
# Update the task status await task_updater.update_status( TaskState.working, message=new_agent_text_message('Title Agent is processing your request...', context_id=context_id), ) -
Find the comment Run the agent conversation and add:
# Run the agent conversation responses = await agent.run_conversation(user_message) -
Find the comment Update the task with the responses and add:
# Update the task with the responses for response in responses: await task_updater.update_status( TaskState.working, message=new_agent_text_message(response, context_id=context_id), ) -
Find the comment Mark the task as complete and add:
# Mark the task as complete final_message = responses[-1] if responses else 'Task completed.' await task_updater.complete( message=new_agent_text_message(final_message, context_id=context_id) )The trip-title agent is now wrapped with an executor that the A2A protocol uses to handle messages.
-
Save the file (Ctrl+S).
Run and test
-
In the terminal, sign in and start all three agent servers:
az loginpython run_all.pyThe servers start using your authenticated Azure session. Wait until each server reports it’s ready.
-
In a second terminal (with the virtual environment activated), run the client:
python client.py -
When prompted, enter a prompt such as:
Create a title and outline for a guided hiking trip in Patagonia.After a few moments, the routing agent delegates to the trip-title and trip-itinerary agents, and you should see a suggested title and an itinerary outline in the response.
Tip: If a server fails to start because a port is already in use, stop any earlier run (Ctrl+C in the
run_all.pyterminal) and try again, or change the*_PORTvalues in.env. -
When you’re finished, press Ctrl+C in the
run_all.pyterminal to stop every server, then enterdeactivatein each terminal to exit the virtual environment.
✅ Checkpoint: You’ve connected agents running in separate processes with the A2A protocol — publishing agent cards, routing requests to the right remote agent, and returning their results.
Next (optional): Task 4 — Classify and route a support ticket