Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Connected agents in Azure AI Foundry Agent Service let you break down complex tasks into coordinated, specialized roles—without the need for a custom orchestrator or hand-coded routing logic. With this capability, you can design systems where a primary agent intelligently delegates to purpose-built sub-agents, streamlining workflows like customer support, market research, legal summarization, and financial analysis.
Rather than overloading one agent with too many skills, you can build focused, reusable agents that collaborate seamlessly—scaling both performance and maintainability.
Features
- Simplified workflow design: Break down complex tasks across specialized agents to reduce complexity and improve clarity.
- No custom orchestration required: The main agent uses natural language to route tasks, eliminating the need for hardcoded logic.
- Easy extensibility: Add new connected agents (for example, translation or risk scoring) without modifying the main agent.
- Improved reliability and traceability: Assign focused responsibilities to each agent for easier debugging and better auditability.
- Flexible setup options: Configure agents using a no-code interface in the Foundry portal or programmatically via the Python SDK.
Example: building a modular contract review agent with connected agents
As your use cases grow in complexity, you can scale your AI solution by assigning specific responsibilities to multiple connected agents. This lets each agent specialize in a narrow task while the main agent coordinates the overall workflow. This modular design enhances accuracy, maintainability, and traceability—especially for document-heavy domains like legal, compliance, and procurement. Let’s walk through a real-world example of how to build a Contract Review Assistant using connected agents.
Architecture Overview
Main agent – contract orchestrator
Acts as the central interface. It interprets user prompts (such as "summarize clauses," "compare drafts," or "check compliance"), determines the task type, and delegates it to the appropriate connected agent.
Tools Used: None directly
Responsibilities: Intent classification and delegation
Example Agent Description:
"You are a contract review assistant. Depending on the user query, determine if the task involves clause summarization, document comparison, or compliance checking, and route accordingly."
Connected agent 1: clause summarizer
Extracts key sections (like Termination, Indemnity, or Confidentiality) from a contract and summarizes them in plain language.
Tools Used:
- File Search to retrieve the uploaded contract
- Code Interpreter to scan the document for clause headings and summarize the content
Responsibilities: Information extraction and summarization
Example agent description:
"Extract and summarize the 'Termination,' 'Payment terms,' and 'Indemnity' clauses from the provided contract."
Connected agent 2: compliance validator
Checks the contract against internal standards or uploaded guidelines to identify risky or noncompliant language.
Tools Used:
- File Search to access internal policy documents or contract templates
- OpenAPI Tool to call an internal compliance rules API
- Azure Function or Azure Logic Apps to run simple logic checks (for example required clause presence or threshold validations)
Responsibilities: Policy matching and risk flagging
Example Prompt Instruction:
"Review this document against company compliance guidelines and flag any deviations from the approved template."
Limitations
- Connected agents cannot call local functions using the function calling tool. We recommend using the OpenAPI tool or Azure Functions instead.
- It is currently not possible to guarantee citations will be passed from connected agents. You can try using prompt engineering combined with different models to try and improve the possibility that citations will be outputted by the main agent, but results are subject to variability.
Creating a multi-agent setup
- Navigate to the Agents page in the portal
- Select an existing agent from the list or create a new one.
- Scroll down to the Connected agents section in the agent's setup panel and select Add +.
In the dialog that appears, choose an agent for the main agent to delegate tasks to, and describe:
- Select an existing agent from the dropdown. This is the connected agent that the main agent will delegate tasks to.
- Enter a unique name for the connected agent (letters and underscores only). This name is used for API-level function calling. Keep it descriptive and machine-readable to maximize recall accuracy (for example,
summarize_text
,lookup_product_info
). - Add a clear description of when and why the connected agent should be invoked. This helps guide the main agent’s decision-making on when to hand off tasks to connected agents during runtime.
Select Add +
Repeat steps 3–5 to add additional specialized agents to the main agent.
Once the connected agent(s) appear in the setup panel, scroll up and select Try in Playground
Use test prompts in the Agent Playground to validate that the main agent correctly routes tasks to the connected agents when applicable. For example, if you’ve created a main agent called
research_agent
, which doesn't have any tools configured, and connected an agent namedstock_price_bot
, try a prompt like:"What is the current stock price of Microsoft?"
The
research_agent
should delegate this request tostock_price_bot
based on the routing description you defined.
Use the .NET SDK
Note
This shows a synchronous usage. You can find an asynchronous example on GitHub
To enable your Agent to use a connected agent, you use ConnectedAgentToolDefinition
along with the agent ID, name, and a description.
First we need to create agent client and read the environment variables, which will be used in the next steps.
var projectEndpoint = configuration["ProjectEndpoint"]; var modelDeploymentName = configuration["ModelDeploymentName"]; PersistentAgentsClient client = new(projectEndpoint, new DefaultAzureCredential());
Next we will create the main agent
mainAgent
, and the connectedstockAgent
agent using the agent client. This connected agent will be used to initialize theConnectedAgentToolDefinition
.PersistentAgent stockAgent = client.Administration.CreateAgent( model: modelDeploymentName, name: "stock_price_bot", instructions: "Your job is to get the stock price of a company. If you don't know the realtime stock price, return the last known stock price." // tools: [...] tools that would be used to get stock prices ); ConnectedAgentToolDefinition connectedAgentDefinition = new(new ConnectedAgentDetails(stockAgent.Id, stockAgent.Name, "Gets the stock price of a company")); PersistentAgent mainAgent = client.Administration.CreateAgent( model: modelDeploymentName, name: "stock_price_bot", instructions: "Your job is to get the stock price of a company, using the available tools.", tools: [connectedAgentDefinition] );
Now we will create the thread, add the message, containing a question for agent and start the run.
PersistentAgentThread thread = client.Threads.CreateThread(); // Create message to thread PersistentThreadMessage message = client.Messages.CreateMessage( thread.Id, MessageRole.User, "What is the stock price of Microsoft?"); // Run the agent ThreadRun run = client.Runs.CreateRun(thread, agent); do { Thread.Sleep(TimeSpan.FromMilliseconds(500)); run = client.Runs.GetRun(thread.Id, run.Id); } while (run.Status == RunStatus.Queued || run.Status == RunStatus.InProgress); // Confirm that the run completed successfully if (run.Status != RunStatus.Completed) { throw new Exception("Run did not complete successfully, error: " + run.LastError?.Message); }
Print the agent messages to console in chronological order.
Pageable<PersistentThreadMessage> messages = client.Messages.GetMessages( threadId: thread.Id, order: ListSortOrder.Ascending ); foreach (PersistentThreadMessage threadMessage in messages) { Console.Write($"{threadMessage.CreatedAt:yyyy-MM-dd HH:mm:ss} - {threadMessage.Role,10}: "); foreach (MessageContent contentItem in threadMessage.ContentItems) { if (contentItem is MessageTextContent textItem) { string response = textItem.Text; if (textItem.Annotations != null) { foreach (MessageTextAnnotation annotation in textItem.Annotations) { if (annotation is MessageTextUriCitationAnnotation urlAnnotation) { response = response.Replace(urlAnnotation.Text, $" [{urlAnnotation.UriCitation.Title}]({urlAnnotation.UriCitation.Uri})"); } } } Console.Write($"Agent response: {response}"); } else if (contentItem is MessageImageFileContent imageFileItem) { Console.Write($"<image from ID: {imageFileItem.FileId}"); } Console.WriteLine(); } }
Clean up resources by deleting thread and agent.
agentClient.DeleteThread(threadId: thread.Id); agentClient.DeleteAgent(agentId: agent.Id); agentClient.DeleteAgent(agentId: connectedAgent.Id);
Creating a multi-agent setup
To create a multi-agent setup, follow these steps:
Initialize the client object.
import os from azure.ai.projects import AIProjectClient from azure.ai.projects.models import ConnectedAgentTool, MessageRole from azure.identity import DefaultAzureCredential project_client = AIProjectClient( endpoint=os.environ["PROJECT_ENDPOINT"], credential=DefaultAzureCredential(), api_version="latest", )
Create an agent that will be connected to a "main" agent.
stock_price_agent = project_client.agents.create_agent( model=os.environ["MODEL_DEPLOYMENT_NAME"], name="stock_price_bot", instructions="Your job is to get the stock price of a company. If you don't know the realtime stock price, return the last known stock price.", #tools=... # tools to help the agent get stock prices )
Initialize the connected agent tool with the agent ID, name, and description
connected_agent = ConnectedAgentTool( id=stock_price_agent.id, name=connected_agent_name, description="Gets the stock price of a company" )
Create the "main" agent that will use the connected agent.
agent = project_client.agents.create_agent( model=os.environ["MODEL_DEPLOYMENT_NAME"], name="my-agent", instructions="You are a helpful agent, and use the available tools to get stock prices.", tools=connected_agent.definitions, ) print(f"Created agent, ID: {agent.id}")
Create a thread and add a message to it.
thread = project_client.agents.create_thread() print(f"Created thread, ID: {thread.id}") # Create message to thread message = project_client.agents.create_message( thread_id=thread.id, role=MessageRole.USER, content="What is the stock price of Microsoft?", ) print(f"Created message, ID: {message.id}")
Create a run and wait for it to complete.
# Create and process Agent run in thread with tools run = project_client.agents.create_and_process_run(thread_id=thread.id, agent_id=agent.id) print(f"Run finished with status: {run.status}") if run.status == "failed": print(f"Run failed: {run.last_error}") # Delete the Agent when done project_client.agents.delete_agent(agent.id) print("Deleted agent") # Delete the connected Agent when done project_client.agents.delete_agent(stock_price_agent.id) print("Deleted connected agent")
Print the agent's response. The main agent will compile the responses from the connected agents and provide the response. connected agent responses are only visible to the main agent, and not to the end user.
# Print the Agent's response message with optional citation response_message = project_client.agents.list_messages(thread_id=thread.id).get_last_message_by_role( MessageRole.AGENT ) if response_message: for text_message in response_message.text_messages: print(f"Agent response: {text_message.text.value}") for annotation in response_message.url_citation_annotations: print(f"URL Citation: [{annotation.url_citation.title}]({annotation.url_citation.url})")