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_ENDPOINT and MODEL_DEPLOYMENT_NAME in Python/.env. Then, from the Labfiles/C-build-multi-agent-solutions-with-agent-framework folder, 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 Python folder, your project, virtual environment, and .env are 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.

Learn more →

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 the title_agent, then wire up the routing_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.

  1. Open title_agent/agent.py.

  2. 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
         )
     )
    
  3. 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.
         """,
     )
    
  4. Find the comment Create a thread for the chat session and add:

     # Create a thread for the chat session
     thread = self.client.threads.create()
    
  5. 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)
    
  6. 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.

  7. Save the file (Ctrl+S). Now share the agent’s skills and card with the A2A protocol.

  8. Open title_agent/server.py.

  9. 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?',
             ],
         ),
     ]
    
  10. 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,
     )
    
  11. Find the comment Create agent executor and add:

     # Create agent executor
     agent_executor = create_foundry_agent_executor(agent_card)
    
  12. Find the comment Create request handler and add:

     # Create request handler
     request_handler = DefaultRequestHandler(
         agent_executor=agent_executor, task_store=InMemoryTaskStore()
     )
    
  13. 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.

  14. 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.

  1. Open routing_agent/agent.py.

    The routing agent orchestrates the system: when a user message arrives, it starts a thread, uses create_and_process to decide which remote agent should handle the request, and routes the message to that agent over HTTP with the send_message function. The send_message method is async and must be awaited for the run to complete.

  2. 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]
    
  3. 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,
         },
     }
    
  4. 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))
    
  5. 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)
    
  6. 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.

  7. Open title_agent/agent_executor.py.

    The AgentExecutor class must implement execute and cancel. The cancel method is provided. The execute method uses a TaskUpdater object to manage events and signal when the task is complete — add the execution logic below.

  8. In the execute method, find the comment Process the request and add:

     # Process the request
     await self._process_request(context.message.parts, context.context_id, updater)
    
  9. In the _process_request method, find the comment Get the title agent and add:

     # Get the title agent
     agent = await self._get_or_create_agent()
    
  10. 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),
     )
    
  11. Find the comment Run the agent conversation and add:

     # Run the agent conversation
     responses = await agent.run_conversation(user_message)
    
  12. 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),
         )
    
  13. 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.

  14. Save the file (Ctrl+S).

Run and test

  1. In the terminal, sign in and start all three agent servers:

     az login
    
     python run_all.py
    

    The servers start using your authenticated Azure session. Wait until each server reports it’s ready.

  2. In a second terminal (with the virtual environment activated), run the client:

     python client.py
    
  3. 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.py terminal) and try again, or change the *_PORT values in .env.

  4. When you’re finished, press Ctrl+C in the run_all.py terminal to stop every server, then enter deactivate in 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