<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Morten Christensen</title>
    <description>The latest articles on DEV Community by Morten Christensen (@sitereactor).</description>
    <link>https://dev.to/sitereactor</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1041705%2Ffebcd3af-19c1-4c43-bc43-9325dd48c3d8.jpg</url>
      <title>DEV Community: Morten Christensen</title>
      <link>https://dev.to/sitereactor</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sitereactor"/>
    <language>en</language>
    <item>
      <title>Multi-agent orchestration on Azure AI Foundry with .NET: AsAIFunction, A2A, and when one agent is enough</title>
      <dc:creator>Morten Christensen</dc:creator>
      <pubDate>Mon, 27 Jul 2026 07:59:30 +0000</pubDate>
      <link>https://dev.to/sitereactor/multi-agent-orchestration-on-azure-ai-foundry-with-net-asaifunction-a2a-and-when-one-agent-is-24c8</link>
      <guid>https://dev.to/sitereactor/multi-agent-orchestration-on-azure-ai-foundry-with-net-asaifunction-a2a-and-when-one-agent-is-24c8</guid>
      <description>&lt;p&gt;When you build a Foundry agent that needs more than one capability, the natural impulse is to reach for multi-agent orchestration. Spin up a ResearchAgent, a WriterAgent, an OrchestratorAgent. Problem solved, right? In my experience, that's not always the case. Most scenarios that feel like they need multiple agents can be handled cheaper and with less operational surface area by a single agent with well-chosen tools.&lt;/p&gt;

&lt;p&gt;This post covers three things: a concrete heuristic for deciding when to split agents, the two in-process orchestration patterns available in the Microsoft Agent Framework .NET SDK, and the Foundry Workflows retirement coming December 1, 2026. If your team built orchestration in the visual portal designer, I'll show you what the migration path actually looks like in code.&lt;/p&gt;

&lt;p&gt;I'm showing my preferred approach here. If you structure this differently, or if the A2A story has changed since I wrote this, I'd be curious to hear more about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When one agent is actually enough
&lt;/h2&gt;

&lt;p&gt;Four reasons I'll reach for multiple agents instead of one agent with more tools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The specialists need different models. A ReasoningAgent that plans and decomposes a task benefits from a slower, more capable model; a SummaryAgent that just reformats output can use something cheaper and faster. Mixing those workloads in a single agent means paying the heavier model's rate for work that doesn't need it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The specialists belong to different teams and need independent versioning and deployment. If the ResearchAgent is owned by one team and the WriterAgent by another, coupling them into a single process creates coordination overhead every time either team ships. If everything is handled by a single team this would be less of an issue, but I would still emphasize thinking about 'Separation of Concern'.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;One specialist has a traffic pattern that warrants independent scaling: bursty load that would starve the others if they shared capacity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The specialist lives in a different subscription, project, or organisation and can only be reached over a network.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If none of those conditions hold, stay with one agent. A single &lt;code&gt;gpt-4.1-mini&lt;/code&gt; deployment with three function tools will be cheaper and far easier to trace than three agents calling each other. The pattern I'd call orchestration by default, adding an OrchestratorAgent before you have a specific reason for it, adds a model call at every hop and makes traces meaningfully harder to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  In-process composition with AsAIFunction()
&lt;/h2&gt;

&lt;p&gt;When you do have a genuine reason to split, the simplest path in the Agent Framework .NET SDK is &lt;code&gt;AsAIFunction()&lt;/code&gt;. It exposes an &lt;code&gt;AIAgent&lt;/code&gt; as an &lt;code&gt;AIFunction&lt;/code&gt;, so the outer agent can call the inner agent as a tool, entirely within the same process. No HTTP round-trip, no Foundry deployment for the inner agent.&lt;/p&gt;

&lt;p&gt;This is not the same as the Foundry Responses API agent-reference pattern. With &lt;code&gt;AsAIFunction()&lt;/code&gt;, the inner agent is instantiated in your C# process with its own model connection. The outer agent's decision to call it happens through the normal tool-calling mechanism: the LLM emits a tool call, the framework resolves it to the inner &lt;code&gt;AIAgent.RunAsync()&lt;/code&gt;, and the result comes back as a tool response. Session state lives in memory.&lt;/p&gt;

&lt;p&gt;Here's the minimal version. A &lt;code&gt;WeatherAgent&lt;/code&gt; that answers weather questions, exposed as a callable tool to a &lt;code&gt;MainAgent&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// example-01: WeatherAgent as a callable tool of MainAgent via AsAIFunction()&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.AI.Projects&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.Identity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Agents.AI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.AI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// GetWeather is a static local function. [Description] attributes on the method&lt;/span&gt;
&lt;span class="c1"&gt;// and its parameter let AIFunctionFactory.Create() generate a JSON function schema&lt;/span&gt;
&lt;span class="c1"&gt;// that the model can call.&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Get the weather for a given location."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetWeather&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The location to get the weather for."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"The weather in &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is cloudy with a high of 15°C."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FOUNDRY_PROJECT_ENDPOINT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FOUNDRY_PROJECT_ENDPOINT is not set."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;deploymentName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FOUNDRY_MODEL"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"gpt-4.1-mini"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// DefaultAzureCredential — for local dev, use `az login` first.&lt;/span&gt;
&lt;span class="c1"&gt;// In production, prefer ManagedIdentityCredential to avoid fallback probing.&lt;/span&gt;
&lt;span class="n"&gt;AIProjectClient&lt;/span&gt; &lt;span class="n"&gt;aiProjectClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Wrap the static C# method as an AITool with a function schema.&lt;/span&gt;
&lt;span class="n"&gt;AITool&lt;/span&gt; &lt;span class="n"&gt;weatherTool&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AIFunctionFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetWeather&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create the WeatherAgent in-process. Nothing is deployed to Foundry.&lt;/span&gt;
&lt;span class="c1"&gt;// AsAIAgent() is an extension method from Microsoft.Agents.AI.Foundry.&lt;/span&gt;
&lt;span class="n"&gt;AIAgent&lt;/span&gt; &lt;span class="n"&gt;weatherAgent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aiProjectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsAIAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"You answer questions about the weather."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"WeatherAgent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;weatherTool&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// Expose the WeatherAgent as a named AIFunction the MainAgent can invoke as a tool.&lt;/span&gt;
&lt;span class="c1"&gt;// AsAIFunction() is the in-process composition primitive — no HTTP round-trip.&lt;/span&gt;
&lt;span class="n"&gt;AIAgent&lt;/span&gt; &lt;span class="n"&gt;mainAgent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aiProjectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsAIAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"You are a helpful assistant who responds in Portuguese."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"MainAgent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;weatherAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsAIFunction&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;

&lt;span class="c1"&gt;// CreateSessionAsync initialises in-memory conversation state.&lt;/span&gt;
&lt;span class="c1"&gt;// Pass the same session into every RunAsync() call to retain context across turns.&lt;/span&gt;
&lt;span class="n"&gt;AgentSession&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;mainAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateSessionAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;mainAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"What is the weather like in Amsterdam?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to pay attention to in that code. &lt;code&gt;AIFunctionFactory.Create(GetWeather)&lt;/code&gt; wraps the static C# method as an &lt;code&gt;AITool&lt;/code&gt; with a function schema the framework can serialise and send to the model. &lt;code&gt;aiProjectClient.AsAIAgent(deploymentName, instructions, name, tools)&lt;/code&gt; creates the agent in-process, nothing is deployed to Foundry. &lt;code&gt;weatherAgent.AsAIFunction()&lt;/code&gt; packages the whole &lt;code&gt;WeatherAgent&lt;/code&gt; as a named tool the &lt;code&gt;MainAgent&lt;/code&gt; can invoke. And &lt;code&gt;mainAgent.CreateSessionAsync()&lt;/code&gt; sets up the conversation state; pass the same session into every &lt;code&gt;RunAsync()&lt;/code&gt; call if you want the agent to remember earlier turns.&lt;/p&gt;

&lt;p&gt;The call flow: the user asks MainAgent about Amsterdam weather, MainAgent decides to call the WeatherAgent tool, WeatherAgent invokes its own &lt;code&gt;GetWeather&lt;/code&gt; function, and the result climbs back up the stack as a tool response. All of this is in memory. From the outer agent's perspective it looks like any other tool call.&lt;/p&gt;

&lt;p&gt;Oh, and a small twist: the main agent will respond in Portuguese. There is no point in having a main agent if it's just getting the weather from another agent and doing nothing else, so here the main agent also has a specialized instruction.&lt;/p&gt;

&lt;p&gt;Install &lt;code&gt;Microsoft.Agents.AI.Foundry&lt;/code&gt; (1.5.0 at time of writing) and it brings in &lt;code&gt;Microsoft.Agents.AI&lt;/code&gt;, &lt;code&gt;Azure.AI.Projects&lt;/code&gt;, and the other dependencies transitively. Set &lt;code&gt;FOUNDRY_PROJECT_ENDPOINT&lt;/code&gt; and &lt;code&gt;FOUNDRY_MODEL&lt;/code&gt; in your environment, authenticate with &lt;code&gt;az login&lt;/code&gt; for local development, and that's the full setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three agents, one pipeline
&lt;/h2&gt;

&lt;p&gt;Once two agents compose cleanly, adding a third follows the same pattern. The scenario that actually justifies three agents: a &lt;code&gt;ResearchAgent&lt;/code&gt; that retrieves current information using a web search tool, a &lt;code&gt;WriterAgent&lt;/code&gt; that takes research output and produces structured text, and an &lt;code&gt;OrchestratorAgent&lt;/code&gt; that coordinates them in sequence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// example-02: Three-agent sequential pipeline via AsAIFunction()&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.AI.Projects&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.Identity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Agents.AI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.AI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// --- Stub tools ---&lt;/span&gt;

&lt;span class="c1"&gt;// Real implementation: call Bing Grounding tool or a web search API.&lt;/span&gt;
&lt;span class="c1"&gt;// Stub returns hardcoded research so the pipeline runs without a search subscription.&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Research a topic and return a summary with key facts."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetCurrentResearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The topic to research."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"[ResearchAgent tool] GetCurrentResearch(\"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\")"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="s"&gt;$"""
&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;"topic"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"{{{topic}}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Recent studies show significant advances in {{{topic}}}."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;"keyFacts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s"&gt;"Fact 1: adoption grew 40% in the past year"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Fact 2: leading practitioners cite cost reduction as the primary driver"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Fact 3: open standards are gaining momentum over proprietary approaches"&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;""";
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Real implementation: write to a file or blob storage.&lt;/span&gt;
&lt;span class="c1"&gt;// Stub prints to Console so the delegation pattern is observable.&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Write a short article draft and save it."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;WriteArticle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The article content to write."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The filename to save the article to."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"[WriterAgent tool] WriteArticle(\"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\")"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"--- Draft content ---"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"--- End draft ---"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FOUNDRY_PROJECT_ENDPOINT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FOUNDRY_PROJECT_ENDPOINT is not set."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;deploymentName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FOUNDRY_MODEL"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"gpt-4.1-mini"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;AIProjectClient&lt;/span&gt; &lt;span class="n"&gt;aiProjectClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// ResearchAgent: uses a web-search stub tool.&lt;/span&gt;
&lt;span class="n"&gt;AIAgent&lt;/span&gt; &lt;span class="n"&gt;researchAgent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aiProjectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsAIAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"You are a research specialist. When asked to research a topic, call GetCurrentResearch and return the results."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"ResearchAgent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AIFunctionFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GetCurrentResearch&lt;/span&gt;&lt;span class="p"&gt;)]);&lt;/span&gt;

&lt;span class="c1"&gt;// WriterAgent: uses a file-writing stub tool.&lt;/span&gt;
&lt;span class="n"&gt;AIAgent&lt;/span&gt; &lt;span class="n"&gt;writerAgent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aiProjectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsAIAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"You are a technical writer. When given research results, call WriteArticle to draft a short article."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"WriterAgent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AIFunctionFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WriteArticle&lt;/span&gt;&lt;span class="p"&gt;)]);&lt;/span&gt;

&lt;span class="c1"&gt;// OrchestratorAgent: treats both specialists as named tools via AsAIFunction().&lt;/span&gt;
&lt;span class="c1"&gt;// The system prompt makes the delegation sequence deterministic.&lt;/span&gt;
&lt;span class="n"&gt;AIAgent&lt;/span&gt; &lt;span class="n"&gt;orchestratorAgent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aiProjectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsAIAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"""
&lt;/span&gt;        &lt;span class="n"&gt;You&lt;/span&gt; &lt;span class="n"&gt;coordinate&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;research&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;writing&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;Step&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Call&lt;/span&gt; &lt;span class="n"&gt;ResearchAgent&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;gather&lt;/span&gt; &lt;span class="n"&gt;research&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;Step&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Call&lt;/span&gt; &lt;span class="n"&gt;WriterAgent&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;research&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;produce&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;After&lt;/span&gt; &lt;span class="n"&gt;both&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="n"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;summarise&lt;/span&gt; &lt;span class="n"&gt;what&lt;/span&gt; &lt;span class="n"&gt;was&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="s"&gt;""",
&lt;/span&gt;    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"OrchestratorAgent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;researchAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsAIFunction&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;writerAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsAIFunction&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;

&lt;span class="c1"&gt;// A single session is kept alive across both specialist calls.&lt;/span&gt;
&lt;span class="c1"&gt;// The orchestrator accumulates intermediate results in this session.&lt;/span&gt;
&lt;span class="n"&gt;AgentSession&lt;/span&gt; &lt;span class="n"&gt;orchestratorSession&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;orchestratorAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateSessionAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"multi-agent orchestration patterns"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"[OrchestratorAgent] Starting pipeline for topic: \"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;orchestratorAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;$"Research and write a short article about: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;orchestratorSession&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[OrchestratorAgent] Pipeline complete."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a couple of things about this pattern that are easy to get wrong.&lt;/p&gt;

&lt;p&gt;The orchestrator does not automatically receive the inner agents' intermediate tool calls. When it calls &lt;code&gt;ResearchAgent&lt;/code&gt;, it gets back the text result. It never sees the individual web search calls the research agent made internally. That's usually what you want: specialists hide their implementation details behind a clean result boundary.&lt;/p&gt;

&lt;p&gt;Context does not flow between specialists on its own, either. If &lt;code&gt;WriterAgent&lt;/code&gt; needs the research results, the orchestrator has to include them explicitly in the message it sends to the writer. You own that assembly step. The framework does not thread context through for you.&lt;/p&gt;

&lt;p&gt;And the cost. Each specialist invocation is a separate model call. If the orchestrator loops over five topics and calls both specialists per topic, you're looking at ten model calls just for the specialists, plus the orchestrator's own calls on top of that. Multi-agent is not free. Run the numbers against a single-agent approach before committing.&lt;/p&gt;

&lt;h2&gt;
  
  
  A2A (Agent 2 Agent): when the specialist lives somewhere else
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;AsAIFunction()&lt;/code&gt; only works when both agents run in the same process. The moment a specialist lives in a different Foundry project, a different subscription, or a different organisation, you need A2A.&lt;/p&gt;

&lt;p&gt;The A2A tool implements the open &lt;a href="https://a2a-protocol.org/latest/" rel="noopener noreferrer"&gt;A2A protocol&lt;/a&gt;, so it works with any A2A-compatible endpoint, not just agents built on Foundry. You configure a project connection in the Foundry portal (key-based auth, managed OAuth, managed identity, or agent identity are all supported), and your agent references that connection by name at runtime. The code example has a readme with a step by step guide for creating the connection via Foundry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// example-03: A2A cross-boundary agent call using Azure.AI.Projects SDK&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.AI.Projects&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.AI.Projects.Agents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.AI.Extensions.OpenAI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.Identity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;OpenAI.Responses&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FOUNDRY_PROJECT_ENDPOINT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FOUNDRY_PROJECT_ENDPOINT is not set."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;modelDeployment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FOUNDRY_MODEL"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"gpt-4.1-mini"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;a2aConnectionName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FOUNDRY_A2A_CONNECTION_NAME"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FOUNDRY_A2A_CONNECTION_NAME is not set."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Only needed when the connection type is not RemoteTool_Preview (i.e., raw A2A endpoints&lt;/span&gt;
&lt;span class="c1"&gt;// stored as a non-RemoteTool connection type won't carry the URI automatically).&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;a2aBaseUri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FOUNDRY_A2A_BASE_URI"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;AIProjectClient&lt;/span&gt; &lt;span class="n"&gt;projectClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Resolve the connection name to a full AIProjectConnection.&lt;/span&gt;
&lt;span class="c1"&gt;// The .Id property is the ARM resource path used by A2APreviewTool.&lt;/span&gt;
&lt;span class="n"&gt;AIProjectConnection&lt;/span&gt; &lt;span class="n"&gt;a2aConnection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;projectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Connections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a2aConnectionName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Configure the A2A tool with the connection ID.&lt;/span&gt;
&lt;span class="n"&gt;A2APreviewTool&lt;/span&gt; &lt;span class="n"&gt;a2aTool&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ProjectConnectionId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a2aConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// The docs describe checking for "RemoteA2A" connection type to decide whether to set BaseUri.&lt;/span&gt;
&lt;span class="c1"&gt;// In Azure.AI.Projects 2.0.1, the corresponding enum value is ConnectionType.RemoteTool&lt;/span&gt;
&lt;span class="c1"&gt;// (string value "RemoteTool_Preview"). A RemoteTool connection carries the A2A endpoint URI&lt;/span&gt;
&lt;span class="c1"&gt;// inside the connection record; other connection types require BaseUri to be set explicitly.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a2aConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;ConnectionType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoteTool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a2aBaseUri&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;$"Connection '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;a2aConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;' has type '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;a2aConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;' (not RemoteTool). "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
            &lt;span class="s"&gt;"Set FOUNDRY_A2A_BASE_URI to the A2A endpoint base URI."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;a2aTool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseUri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a2aBaseUri&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Build the agent definition with the A2A tool.&lt;/span&gt;
&lt;span class="n"&gt;DeclarativeAgentDefinition&lt;/span&gt; &lt;span class="n"&gt;agentDefinition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;modelDeployment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Instructions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"You are a helpful assistant."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Tools&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;a2aTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Create a server-side Prompt Agent version.&lt;/span&gt;
&lt;span class="c1"&gt;// This deploys the agent definition to Foundry; the returned ProjectsAgentVersion&lt;/span&gt;
&lt;span class="c1"&gt;// carries .Name and .Version for subsequent calls.&lt;/span&gt;
&lt;span class="n"&gt;ProjectsAgentVersion&lt;/span&gt; &lt;span class="n"&gt;agentVersion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;projectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AgentAdministrationClient&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAgentVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;agentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"example-03-a2a-agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ProjectsAgentVersionCreationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentDefinition&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Agent created: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (version &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Get a ProjectResponsesClient scoped to this agent.&lt;/span&gt;
    &lt;span class="c1"&gt;// GetProjectResponsesClientForAgent takes an AgentReference (name + version),&lt;/span&gt;
    &lt;span class="c1"&gt;// not just a string — the overload in the Microsoft docs that accepts a bare string&lt;/span&gt;
    &lt;span class="c1"&gt;// does not exist in Azure.AI.Extensions.OpenAI 2.0.0.&lt;/span&gt;
    &lt;span class="n"&gt;AgentReference&lt;/span&gt; &lt;span class="n"&gt;agentRef&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;ProjectResponsesClient&lt;/span&gt; &lt;span class="n"&gt;responseClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;projectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProjectOpenAIClient&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProjectResponsesClientForAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentRef&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Send the message, forcing the agent to use the A2A tool via CreateRequiredChoice().&lt;/span&gt;
    &lt;span class="n"&gt;CreateResponseOptions&lt;/span&gt; &lt;span class="n"&gt;responseOptions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ToolChoice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ResponseToolChoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateRequiredChoice&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;InputItems&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ResponseItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateUserMessageItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"What can the secondary agent do?"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="n"&gt;ResponseResult&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;responseClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateResponseAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;responseOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;ResponseStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Completed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Response did not complete. Status: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"[Response from remote A2A agent]: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetOutputText&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;finally&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Clean up the server-side agent version regardless of success or failure.&lt;/span&gt;
    &lt;span class="n"&gt;projectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AgentAdministrationClient&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeleteAgentVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Agent version deleted."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gotcha that caught me first: &lt;code&gt;A2APreviewTool&lt;/code&gt; needs a &lt;code&gt;ProjectConnectionId&lt;/code&gt;, not a raw URL. Resolve the connection name to a full connection object with &lt;code&gt;projectClient.Connections.GetConnection(connectionName)&lt;/code&gt; at startup. The &lt;code&gt;.Id&lt;/code&gt; property is what the tool uses.&lt;/p&gt;

&lt;p&gt;The connection type check deserves a clear explanation, because the official docs sample and the C# SDK spell the connection type differently and it looks like a contradiction. The official docs sample does &lt;code&gt;string.Equals(a2aConnection.Type.ToString(), "RemoteA2A")&lt;/code&gt;, comparing against the string &lt;code&gt;"RemoteA2A"&lt;/code&gt;, which is the backend REST connection category name that comes back from the Foundry portal API. That string does not exist as a named constant in the C# SDK. In &lt;code&gt;Azure.AI.Projects&lt;/code&gt; 2.0.1, the typed value is &lt;code&gt;ConnectionType.RemoteTool&lt;/code&gt;, whose &lt;code&gt;.ToString()&lt;/code&gt; is &lt;code&gt;"RemoteTool_Preview"&lt;/code&gt;. Same concept, completely different strings. The code here uses the typed SDK value, &lt;code&gt;ConnectionType.RemoteTool&lt;/code&gt;, which is correct. A &lt;code&gt;RemoteTool&lt;/code&gt; connection carries the A2A endpoint URI inside the connection record automatically; any other type doesn't, so you also need to set &lt;code&gt;a2aTool.BaseUri&lt;/code&gt; explicitly. The code handles that branch inline.&lt;/p&gt;

&lt;p&gt;When wiring up the responses client, &lt;code&gt;GetProjectResponsesClientForAgent&lt;/code&gt; expects an &lt;code&gt;AgentReference(name, version)&lt;/code&gt;, not a bare string. The single-string overload shown in some documentation does not exist in &lt;code&gt;Azure.AI.Extensions.OpenAI&lt;/code&gt; 2.0.0. Use &lt;code&gt;new AgentReference(agentVersion.Name, agentVersion.Version)&lt;/code&gt; as the code shows.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;OpenAI.Responses&lt;/code&gt; types used here (&lt;code&gt;CreateResponseOptions&lt;/code&gt;, &lt;code&gt;ResponseToolChoice&lt;/code&gt;, &lt;code&gt;ResponseResult&lt;/code&gt;, and friends) carry &lt;code&gt;[Experimental("OPENAI001")]&lt;/code&gt; in the SDK. Without &lt;code&gt;&amp;lt;NoWarn&amp;gt;$(NoWarn);OPENAI001&amp;lt;/NoWarn&amp;gt;&lt;/code&gt; in your &lt;code&gt;PropertyGroup&lt;/code&gt;, the build fails with errors rather than warnings. Add that suppression before you try to compile; the example includes it and the comment explains why.&lt;/p&gt;

&lt;p&gt;Example-03 uses &lt;code&gt;Azure.AI.Projects&lt;/code&gt; 2.0.1 directly rather than via &lt;code&gt;Microsoft.Agents.AI.Foundry&lt;/code&gt;. One thing worth knowing: on &lt;code&gt;net10.0&lt;/code&gt;, &lt;code&gt;DefaultAzureCredential&lt;/code&gt; resolves without a separate &lt;code&gt;Azure.Identity&lt;/code&gt; package reference. &lt;code&gt;Azure.Core&lt;/code&gt; 1.53.0 embeds the identity types for that target, so the &lt;code&gt;using Azure.Identity;&lt;/code&gt; directive resolves through the transitive chain. You do not need an extra &lt;code&gt;&amp;lt;PackageReference&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before you build anything for production on this: the A2A feature is currently in public preview. The &lt;code&gt;Azure.AI.Projects&lt;/code&gt; 2.0.1 NuGet package is a stable GA release, so that's not the issue. The issue is the A2A feature itself, which Microsoft's official docs flag with a preview banner and the note that it is not recommended for production workloads. &lt;code&gt;A2APreviewTool&lt;/code&gt; is not a holdover class name from an earlier API; it reflects where the feature sits today. No SLA, behaviour subject to change. For demos and internal tooling, fine. For customer-facing infrastructure, wait for GA or decide consciously that you accept the risk.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ResponseToolChoice.CreateRequiredChoice()&lt;/code&gt; is worth using when you want to guarantee the agent actually calls the remote endpoint rather than answering from its own context.&lt;/p&gt;

&lt;p&gt;When does A2A actually earn its overhead? When the specialist is owned by a different team and deployed independently, when multiple callers need to reach the same endpoint without sharing a process, or when you're integrating with a non-Microsoft agent that speaks the A2A protocol. You might also have an agent that you want to expose to multiple consumers (internally or externally) where it makes sense to call the agent through A2A with authentication.&lt;/p&gt;

&lt;p&gt;For same-team, same-codebase work, &lt;code&gt;AsAIFunction()&lt;/code&gt; beats A2A on every practical dimension.&lt;/p&gt;

&lt;p&gt;Personally, I'm currently exploring using A2A from an n8n workflow calling an agent that knows how to provide me with a daily brief. The daily brief agent has an agent card and sits behind authentication, which n8n knows how to call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the pattern in code
&lt;/h2&gt;

&lt;p&gt;If you're onboarding a team to these patterns, it helps to make the decision explicit in code rather than leaving it in a doc somewhere that nobody reads. The following example captures a simplistic approach to selecting a scenario.&lt;/p&gt;

&lt;p&gt;I was unsure to whether to include this code in the post - it could probably also have been a matrix with options and green checks / red x's, but then it would be something you put in your docs (&lt;em&gt;that nobody reads&lt;/em&gt;) and I wanted to have something like this in code ... here you go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// example-04: Static decision harness — recommends an agent composition pattern&lt;/span&gt;

&lt;span class="c1"&gt;// ---- Scenarios ----&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scenarios&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AgentScenario&lt;/span&gt; &lt;span class="n"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;)[]&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"same process, same model, 1 specialist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AgentScenario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;SpecialistsNeedDifferentModels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;IndependentDeploymentRequired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;CrossProcessOrCrossSubscription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;NumberOfSpecialists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"same process, different models, 2 specialists"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AgentScenario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;SpecialistsNeedDifferentModels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;IndependentDeploymentRequired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;CrossProcessOrCrossSubscription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;NumberOfSpecialists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"different models, independent deployment needed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AgentScenario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;SpecialistsNeedDifferentModels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;IndependentDeploymentRequired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;CrossProcessOrCrossSubscription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;NumberOfSpecialists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"cross-subscription specialist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AgentScenario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;SpecialistsNeedDifferentModels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;IndependentDeploymentRequired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;CrossProcessOrCrossSubscription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;NumberOfSpecialists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scenarios&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;RecommendPattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Scenario: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; → &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pattern&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: \"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Rationale&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ---- Decision logic ----&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Recommendation&lt;/span&gt; &lt;span class="nf"&gt;RecommendPattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AgentScenario&lt;/span&gt; &lt;span class="n"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Rule 1: cross-process or cross-subscription boundary — only A2A can reach it.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CrossProcessOrCrossSubscription&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CompositionPattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A2A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Cross-subscription boundary requires A2A."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Rule 2: independently deployed specialist — use A2A to avoid coupling deployments.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IndependentDeploymentRequired&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CompositionPattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A2A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Independent deployment requires A2A; use A2APreviewTool."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Rule 3: multiple specialists with different model requirements — AsAIFunction lets&lt;/span&gt;
    &lt;span class="c1"&gt;// each specialist use its own model connection while staying in-process.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SpecialistsNeedDifferentModels&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumberOfSpecialists&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CompositionPattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsAIFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Use AsAIFunction() to compose specialists in-process."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Rule 4: multiple specialists, same model — AsAIFunction works; evaluate whether&lt;/span&gt;
    &lt;span class="c1"&gt;// a single agent with multiple tools is simpler before committing to orchestration.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NumberOfSpecialists&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CompositionPattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsAIFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Use AsAIFunction() to compose specialists in-process; "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
            &lt;span class="s"&gt;"consider single-agent-with-tools first if specialists share a model."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Rule 5: one specialist, same process — no orchestration layer needed.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CompositionPattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SingleAgentWithTools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"One agent with tools handles this; no orchestration needed."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ---- Types (must follow all top-level executable statements in C# top-level programs) ----&lt;/span&gt;

&lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;AgentScenario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;SpecialistsNeedDifferentModels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;IndependentDeploymentRequired&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;CrossProcessOrCrossSubscription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;NumberOfSpecialists&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;CompositionPattern&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;SingleAgentWithTools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AsAIFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;A2A&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;Recommendation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CompositionPattern&lt;/span&gt; &lt;span class="n"&gt;Pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Rationale&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is pure C#, no Foundry SDK required. The value is as executable documentation: it makes the selection criteria visible and gives you something concrete to point to in a code review when someone reaches for &lt;code&gt;A2APreviewTool&lt;/code&gt; and &lt;code&gt;AsAIFunction()&lt;/code&gt; would have been fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Foundry Workflows retirement
&lt;/h2&gt;

&lt;p&gt;If you use the Foundry visual workflow designer, the date to note is &lt;strong&gt;December 1, 2026&lt;/strong&gt;. After that, the portal designer and in-portal execution stop working. The orchestration patterns themselves (sequential pipelines, group chat, human-in-the-loop) are not disappearing. They carry over to Agent Framework, which supports both declarative YAML and code-first.&lt;/p&gt;

&lt;p&gt;The migration options Microsoft recommends:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your situation&lt;/th&gt;
&lt;th&gt;Where to go&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sequential or group-chat orchestration, want code-first&lt;/td&gt;
&lt;td&gt;Microsoft Agent Framework (YAML or C#)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visual designer is a requirement for your team&lt;/td&gt;
&lt;td&gt;Azure Logic Apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One agent calling another, no formal workflow needed&lt;/td&gt;
&lt;td&gt;A2A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Before you do anything else, export your workflow YAML from the portal. The YAML view gives you a portable definition, and in many cases you can take that exported YAML into an Agent Framework project and run it as a hosted agent with minimal changes. That's the closest thing to a lift-and-shift that exists.&lt;/p&gt;

&lt;p&gt;One pattern worth flagging separately: group-chat orchestration (where agents hand off to each other dynamically based on context) is documented in Agent Framework and the portal supports it today. But there is no published .NET sample for group chat at time of writing. If that's your target pattern, watch the &lt;a href="https://github.com/microsoft/agent-framework/tree/main/dotnet/samples" rel="noopener noreferrer"&gt;agent-framework GitHub&lt;/a&gt;; samples appear regularly. For the migration path with the most stable .NET footing right now, sequential composition via &lt;code&gt;AsAIFunction()&lt;/code&gt; or the A2A tool is a good place to start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability
&lt;/h2&gt;

&lt;p&gt;Be upfront with your team about this: multi-agent workflow tracing is currently in preview. Single-agent OpenTelemetry integration is GA and works well. &lt;a href="https://github.com/microsoft/agent-framework/tree/main/dotnet/samples/02-agents/AgentProviders/foundry/Agent_Step07_Observability" rel="noopener noreferrer"&gt;Step 07&lt;/a&gt; in the Foundry samples covers it. But cross-agent trace correlation, the ability to see a &lt;code&gt;ResearchAgent → OrchestratorAgent → WriterAgent&lt;/code&gt; span tree in a single trace, is not yet production-ready.&lt;/p&gt;

&lt;p&gt;For now, instrument each agent individually and correlate by session ID when you need to stitch runs together. It's a manual process and honestly a bit annoying, but running blind is worse. Keep an eye on the &lt;a href="https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/workflow" rel="noopener noreferrer"&gt;workflow tracing docs&lt;/a&gt; as multi-agent observability matures. It's the obvious next step once the patterns themselves have stabilised.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you go
&lt;/h2&gt;

&lt;p&gt;The agent-as-function-tool pattern handles most real-world multi-agent scenarios in .NET without introducing deployment complexity. A2A is the right answer when you hit an actual organisational or process boundary (this could be an integration between an off-the-shelf workflow engine and your custom agent). Everything else is usually just adding hops for its own sake, and every hop is a model call you're paying for.&lt;/p&gt;

&lt;p&gt;Start with the simplest thing that can work. One agent, good tools, a clear system prompt. You can always split later when the need arises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Examples
&lt;/h2&gt;

&lt;p&gt;You can find the examples and a markdown version of this article on my github. On github you'll find that the article has a few more notes in the code, which I intentionally left out here as its a bit verbose.&lt;br&gt;
&lt;a href="https://github.com/sitereactor/dev-to-articles/tree/main/foundry-multi-agent-orchestration-dotnet" rel="noopener noreferrer"&gt;Code examples for this article&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/agent-to-agent:" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/agent-to-agent:&lt;/a&gt; A2A tool: full C# SDK sample for both Prompt Agent (using &lt;code&gt;A2APreviewTool&lt;/code&gt;) and Hosted Agent (using &lt;code&gt;weatherAgent.AsAIFunction()&lt;/code&gt;) patterns, connection types, authentication methods, A2A endpoint hosting options (2026-07-21).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/workflow:" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/workflow:&lt;/a&gt; Workflow patterns (sequential, group chat, human-in-the-loop), retirement announcement (December 1, 2026), migration guide to Agent Framework / Logic Apps / A2A, YAML portability (2026-07-21).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/microsoft/agent-framework/tree/main/dotnet/samples/02-agents/AgentProviders/foundry:" rel="noopener noreferrer"&gt;https://github.com/microsoft/agent-framework/tree/main/dotnet/samples/02-agents/AgentProviders/foundry:&lt;/a&gt; .NET foundry agent samples including Agent_Step11_AsFunctionTool (in-process multi-agent composition), confirming the &lt;code&gt;AsAIFunction()&lt;/code&gt; and &lt;code&gt;AsAIAgent()&lt;/code&gt; extension method patterns (2026-07-21).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/tool-catalog:" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/tool-catalog:&lt;/a&gt; Tool catalog overview confirming A2A tool as GA custom tool; Toolbox as MCP-compatible multi-tool bundle for cross-agent reuse (2026-07-21).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>foundry</category>
      <category>dotnet</category>
      <category>agents</category>
    </item>
    <item>
      <title>Building Your First Agent with Microsoft Foundry Agent Service from .NET</title>
      <dc:creator>Morten Christensen</dc:creator>
      <pubDate>Thu, 23 Jul 2026 07:04:13 +0000</pubDate>
      <link>https://dev.to/sitereactor/building-your-first-agent-with-microsoft-foundry-agent-service-from-net-32d1</link>
      <guid>https://dev.to/sitereactor/building-your-first-agent-with-microsoft-foundry-agent-service-from-net-32d1</guid>
      <description>&lt;p&gt;For this post I wanted to share how I approached building my first agent in Microsoft Foundry Agent Service with C#. Every resource you actually need, every CLI command that provisions it, and the SDK code explained rather than assumed. If you've found a cleaner setup, or disagree with any of the trade-offs I mention here, I'd genuinely like to hear it.&lt;/p&gt;

&lt;p&gt;I'm writing this because the official quickstart is Python-first and hides the Azure setup behind &lt;code&gt;azd&lt;/code&gt; scaffolding. It doesn't tell you which resources you actually need, what the RBAC role assignments are, or why the SDK now ships four NuGet packages and which one will silently break your build. The C# SDK stabilised at 2.0.0 in mid-2026, the product was renamed from Azure AI Foundry at the same time, and the RBAC role names changed too. Hit the documentation cold and three separate things look broken at once. None of them are.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, the naming situation
&lt;/h2&gt;

&lt;p&gt;The service was called Azure AI Foundry. It's now called Microsoft Foundry. The RBAC roles went with it: the role you assign to give a developer project access is now &lt;strong&gt;Foundry User&lt;/strong&gt; (previously Azure AI User). The role IDs (the GUIDs) are unchanged, which matters when you script the assignment. Portal screens, error messages, and a lot of existing tutorials still use the old names. You're not confused; the rollout just isn't finished yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need in Azure
&lt;/h2&gt;

&lt;p&gt;Four things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Foundry resource (top-level Azure resource, kind &lt;code&gt;AIServices&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A project inside that resource&lt;/li&gt;
&lt;li&gt;A deployed model&lt;/li&gt;
&lt;li&gt;A role assignment so your identity can call the API&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. No Storage account, no Search index, nothing else for the basic prompt-agent path. Here's a setup script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# setup.sh — Provision all Azure resources needed for the Foundry Agent Service examples.&lt;/span&gt;
&lt;span class="c"&gt;# Run from any directory. Requires the Azure CLI (az) to be installed and logged in.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Usage:&lt;/span&gt;
&lt;span class="c"&gt;#   chmod +x setup.sh&lt;/span&gt;
&lt;span class="c"&gt;#   ./setup.sh&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# The final line prints the PROJECT_ENDPOINT you should export before running the C# examples.&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;RESOURCE_GROUP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"foundry-agent-rg"&lt;/span&gt;
&lt;span class="nv"&gt;LOCATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"eastus2"&lt;/span&gt;
&lt;span class="nv"&gt;RESOURCE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"foundry-agent-demo"&lt;/span&gt;   &lt;span class="c"&gt;# Must be globally unique — change this to something specific to your org&lt;/span&gt;
&lt;span class="nv"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"first-agent-project"&lt;/span&gt;
&lt;span class="nv"&gt;DEPLOYMENT_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.4-mini"&lt;/span&gt;
&lt;span class="nv"&gt;MODEL_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.4-mini"&lt;/span&gt;
&lt;span class="nv"&gt;MODEL_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"2026-03-17"&lt;/span&gt;           &lt;span class="c"&gt;# Verified GA version for gpt-5.4-mini as of July 2026&lt;/span&gt;
&lt;span class="nv"&gt;SKU_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"GlobalStandard"&lt;/span&gt;
&lt;span class="nv"&gt;SKU_CAPACITY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== Step 1: Create resource group ==="&lt;/span&gt;
az group create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCATION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== Step 2: Create Foundry resource ==="&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Note: This can take 1–2 minutes. --allow-project-management cannot be changed after creation."&lt;/span&gt;
az cognitiveservices account create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--kind&lt;/span&gt; AIServices &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--sku&lt;/span&gt; S0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCATION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--custom-domain&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allow-project-management&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--yes&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== Step 3: Create project ==="&lt;/span&gt;
az cognitiveservices account project create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--project-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== Step 4: Deploy model ==="&lt;/span&gt;
az cognitiveservices account deployment create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--deployment-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DEPLOYMENT_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MODEL_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model-format&lt;/span&gt; &lt;span class="s2"&gt;"OpenAI"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model-version&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MODEL_VERSION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--sku-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SKU_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--sku-capacity&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SKU_CAPACITY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== Step 5: Get project resource ID for RBAC ==="&lt;/span&gt;
&lt;span class="nv"&gt;PROJECT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az cognitiveservices account project show &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESOURCE_GROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--project-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Project resource ID: &lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== Step 6: Get current user ==="&lt;/span&gt;
&lt;span class="nv"&gt;CURRENT_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az account show &lt;span class="nt"&gt;--query&lt;/span&gt; user.name &lt;span class="nt"&gt;-o&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Current user: &lt;/span&gt;&lt;span class="nv"&gt;$CURRENT_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== Step 7: Assign Foundry User role on project ==="&lt;/span&gt;
&lt;span class="c"&gt;# Using the stable GUID for the Foundry User role (previously 'Azure AI User').&lt;/span&gt;
&lt;span class="c"&gt;# The display name was renamed; the GUID is unchanged.&lt;/span&gt;
az role assignment create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role&lt;/span&gt; &lt;span class="s2"&gt;"53ca6127-db72-4b80-b1b0-d745d6d5456d"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--assignee&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CURRENT_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scope&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROJECT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== Done ==="&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Project endpoint URL:"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RESOURCE_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.services.ai.azure.com/api/projects/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Set this in your shell before running the C# examples:"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"export PROJECT_ENDPOINT=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RESOURCE_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.services.ai.azure.com/api/projects/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"export AGENT_NAME=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;first-agent&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A couple of things worth calling out. The &lt;code&gt;--custom-domain&lt;/code&gt; value becomes part of your project endpoint URL and must be globally unique, so pick something specific to your organisation, not just &lt;code&gt;foundry-agent-demo&lt;/code&gt;. The &lt;code&gt;--allow-project-management&lt;/code&gt; flag cannot be changed after the resource is created, so don't skip it.&lt;/p&gt;

&lt;p&gt;Use the role definition GUID for the assignment rather than the display name. The display name was just renamed and, depending on your tenant, it may resolve incorrectly during the rollout period. The GUID is stable.&lt;/p&gt;

&lt;p&gt;Deploy into East US 2 if you have a choice. The model router and every standard built-in tool are available there. Some regions are missing tools, and the tool availability matrix is worth checking before you commit if you plan to use code interpreter or file search.&lt;/p&gt;

&lt;p&gt;The project endpoint you'll use in code looks like &lt;code&gt;https://&amp;lt;resource-name&amp;gt;.services.ai.azure.com/api/projects/&amp;lt;project-name&amp;gt;&lt;/code&gt;. Copy it from the Foundry portal once provisioning finishes, or construct it from the names you used in the script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt agents or hosted agents?
&lt;/h2&gt;

&lt;p&gt;Before writing any code, it's worth being clear about what you're actually building. Foundry Agent Service has two meaningfully different modes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt agents&lt;/strong&gt; are defined declaratively: model, instructions, tools. Foundry runs them for you. No application code to maintain, no compute to pay for. You send a request to the Responses API and get an answer back. For internal tools, question-answering, pipelines without custom orchestration logic: this is where I'd start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hosted agents&lt;/strong&gt; are code you write yourself (Agent Framework, LangGraph, the OpenAI Agents SDK, or anything else), packaged as a container and run on Foundry-managed compute. You get a managed endpoint, autoscaling, and a dedicated Entra identity. You also pay for container compute on top of inference. This is the right choice when you need custom business logic embedded in the agent, not just configuration.&lt;/p&gt;

&lt;p&gt;This article covers prompt agents. Hosted agents from .NET use the Microsoft Agent Framework (a separate open-source project) and are different enough to deserve their own treatment. I'll cover that in the next post in this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  The packages, and the one to avoid
&lt;/h2&gt;

&lt;p&gt;The C# SDK for Foundry requires four packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Exe&lt;span class="nt"&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net10.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Nullable&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/Nullable&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ImplicitUsings&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/ImplicitUsings&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Azure AI Projects: thin client over the Foundry project API.
         Provides AIProjectClient and access to AgentAdministrationClient. --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Azure.AI.Projects"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"2.0.1"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Azure AI Projects Agents: agent administration surface.
         Provides DeclarativeAgentDefinition, ProjectsAgentVersion,
         CreateAgentVersion, DeleteAgentVersion, and the tool type hierarchy. --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Azure.AI.Projects.Agents"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"2.0.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Azure AI Extensions OpenAI: invocation surface.
         Provides ProjectResponsesClient, ProjectConversationsClient,
         ProjectConversation, and GetProjectResponsesClientForAgent. --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Azure.AI.Extensions.OpenAI"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"2.0.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Azure Identity: DefaultAzureCredential (resolves via az login locally,
         managed identity in production — same code path either way). --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Azure.Identity"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.21.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!--
      *** DO NOT ADD ***
      Azure.AI.Projects.OpenAI (preview) defines the same types as
      Azure.AI.Extensions.OpenAI in different namespaces. Installing both
      causes ambiguous reference compile errors that are difficult to diagnose.
      Use only Azure.AI.Extensions.OpenAI.
    --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Azure.AI.Projects&lt;/code&gt; is the thin client over the Foundry project API. &lt;code&gt;Azure.AI.Projects.Agents&lt;/code&gt; adds the agent administration surface: creating, versioning, and deleting definitions. &lt;code&gt;Azure.AI.Extensions.OpenAI&lt;/code&gt; is what you use to invoke the agent; it provides &lt;code&gt;ProjectResponsesClient&lt;/code&gt;, which calls the Responses API on your project endpoint. &lt;code&gt;Azure.Identity&lt;/code&gt; covers credentials.&lt;/p&gt;

&lt;p&gt;The one to avoid is &lt;code&gt;Azure.AI.Projects.OpenAI&lt;/code&gt;. It's a preview package that defines the same types in a different namespace. Install it alongside &lt;code&gt;Azure.AI.Extensions.OpenAI&lt;/code&gt; and you get ambiguous reference compile errors that take a while to trace back to the source. The comment in the project file says the same thing. Just don't add it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating your first agent
&lt;/h2&gt;

&lt;p&gt;Here's a complete console application. It creates a prompt agent, asks a question, then asks a follow-up that only makes sense if the model remembers the first answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.AI.Extensions.OpenAI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.AI.Projects&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.AI.Projects.Agents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.Identity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;OpenAI.Responses&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ClientModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Configuration — read from environment variables.&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;projectEndpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PROJECT_ENDPOINT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PROJECT_ENDPOINT environment variable is not set."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;agentName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AGENT_NAME"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"first-agent"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Create the project client.&lt;/span&gt;
&lt;span class="n"&gt;AIProjectClient&lt;/span&gt; &lt;span class="n"&gt;projectClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;projectEndpoint&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;tokenProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Define the agent declaratively: model choice + system instructions.&lt;/span&gt;
&lt;span class="n"&gt;DeclarativeAgentDefinition&lt;/span&gt; &lt;span class="n"&gt;agentDefinition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gpt-5.4-mini"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Instructions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"You are a helpful assistant."&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Register the agent version.&lt;/span&gt;
&lt;span class="n"&gt;ClientResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProjectsAgentVersion&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;agentVersionResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;projectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AgentAdministrationClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAgentVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;agentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ProjectsAgentVersionCreationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentDefinition&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;ProjectsAgentVersion&lt;/span&gt; &lt;span class="n"&gt;agentVersion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agentVersionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Agent registered: name=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, version=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 4. Create a conversation.&lt;/span&gt;
&lt;span class="n"&gt;ClientResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProjectConversation&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;conversationResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;projectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProjectOpenAIClient&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProjectConversationsClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateProjectConversation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;ProjectConversation&lt;/span&gt; &lt;span class="n"&gt;conversation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conversationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Conversation created: id=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 5. Get a responses client bound to this agent and conversation.&lt;/span&gt;
&lt;span class="n"&gt;ProjectResponsesClient&lt;/span&gt; &lt;span class="n"&gt;responsesClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;projectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProjectOpenAIClient&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProjectResponsesClientForAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;defaultAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AgentReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;defaultConversationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 6. First turn.&lt;/span&gt;
&lt;span class="n"&gt;ClientResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ResponseResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;turn1Result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;responsesClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userInputText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"What is the capital of Denmark?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                   &lt;span class="n"&gt;previousResponseId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;ResponseResult&lt;/span&gt; &lt;span class="n"&gt;turn1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;turn1Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"[Turn 1]: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;turn1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetOutputText&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 7. Second turn — the model knows from conversation history what "that city" refers to.&lt;/span&gt;
&lt;span class="n"&gt;ClientResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ResponseResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;turn2Result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;responsesClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userInputText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"And what is the population of that city?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                   &lt;span class="n"&gt;previousResponseId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;ResponseResult&lt;/span&gt; &lt;span class="n"&gt;turn2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;turn2Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"[Turn 2]: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;turn2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetOutputText&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Note: agent version is intentionally not deleted here.&lt;/span&gt;
&lt;span class="c1"&gt;// example-04 creates its own agent; cleanup instructions are in README.md.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to walk through.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AIProjectClient&lt;/code&gt; takes the project endpoint URI and a &lt;code&gt;TokenCredential&lt;/code&gt;. Locally, &lt;code&gt;DefaultAzureCredential&lt;/code&gt; resolves via &lt;code&gt;az login&lt;/code&gt;. In production it picks up a managed identity without any code change.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ClientResult&amp;lt;T&amp;gt;&lt;/code&gt; shows up on every method that returns something from the service. It's in the &lt;code&gt;System.ClientModel&lt;/code&gt; package, which is pulled in transitively, but you still need &lt;code&gt;using System.ClientModel;&lt;/code&gt; in your file. Call &lt;code&gt;.Value&lt;/code&gt; to get the actual object.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DeclarativeAgentDefinition&lt;/code&gt; is where model choice and system instructions live. It goes into &lt;code&gt;CreateAgentVersion&lt;/code&gt;, which registers a versioned definition in Foundry. Each call creates a new immutable version. The method returns &lt;code&gt;ClientResult&amp;lt;ProjectsAgentVersion&amp;gt;&lt;/code&gt;; call &lt;code&gt;.Value&lt;/code&gt; to get the &lt;code&gt;ProjectsAgentVersion&lt;/code&gt;, which carries the stable name and version string you'll reference at invocation time.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ProjectConversation&lt;/code&gt; is what carries history across turns. Pass the conversation ID to &lt;code&gt;GetProjectResponsesClientForAgent&lt;/code&gt; alongside &lt;code&gt;new AgentReference(agentVersion.Name)&lt;/code&gt;, and each &lt;code&gt;CreateResponse&lt;/code&gt; call on that client automatically includes prior context. One detail: &lt;code&gt;CreateResponse&lt;/code&gt; always requires a &lt;code&gt;previousResponseId&lt;/code&gt; argument, even if you're passing &lt;code&gt;null&lt;/code&gt;. The second question in the example knows what "that city" refers to because of the conversation binding, not because you're stitching history together yourself. Honestly this is the bit that surprised me most. Less work than I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding web search
&lt;/h2&gt;

&lt;p&gt;The web search tool grounds the agent's responses in real-time public web data. It runs Bing Search under the hood. Two things to know before you enable it: there's an additional per-query cost, and the Microsoft Data Protection Addendum does not apply to data sent to Bing. If you're in a regulated environment, check the Grounding with Bing terms first. For internal tools or prototypes, it's fine.&lt;/p&gt;

&lt;p&gt;The change to the agent definition is small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.AI.Extensions.OpenAI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.AI.Projects&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.AI.Projects.Agents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.Identity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;OpenAI.Responses&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ClientModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Configuration — read from environment variables.&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;projectEndpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PROJECT_ENDPOINT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PROJECT_ENDPOINT environment variable is not set."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;agentName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AGENT_NAME"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"web-search-agent"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Create the project client.&lt;/span&gt;
&lt;span class="n"&gt;AIProjectClient&lt;/span&gt; &lt;span class="n"&gt;projectClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;projectEndpoint&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;tokenProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Define the agent with the web search tool.&lt;/span&gt;
&lt;span class="n"&gt;DeclarativeAgentDefinition&lt;/span&gt; &lt;span class="n"&gt;agentDefinition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gpt-5.4-mini"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Instructions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"You are a helpful assistant that can search the web."&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;agentDefinition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ResponseTool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateWebSearchPreviewTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;userLocation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WebSearchToolLocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateApproximateLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"DK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Capital Region"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Copenhagen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Register the agent version.&lt;/span&gt;
&lt;span class="n"&gt;ClientResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProjectsAgentVersion&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;agentVersionResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;projectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AgentAdministrationClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAgentVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;agentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ProjectsAgentVersionCreationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentDefinition&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;ProjectsAgentVersion&lt;/span&gt; &lt;span class="n"&gt;agentVersion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agentVersionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Agent registered: name=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, version=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 4. Create a conversation.&lt;/span&gt;
&lt;span class="n"&gt;ClientResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProjectConversation&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;conversationResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;projectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProjectOpenAIClient&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProjectConversationsClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateProjectConversation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;ProjectConversation&lt;/span&gt; &lt;span class="n"&gt;conversation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conversationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 5. Get a responses client bound to this agent and conversation.&lt;/span&gt;
&lt;span class="n"&gt;ProjectResponsesClient&lt;/span&gt; &lt;span class="n"&gt;responsesClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;projectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProjectOpenAIClient&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProjectResponsesClientForAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;defaultAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AgentReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;defaultConversationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 6. Call the agent with a web search query.&lt;/span&gt;
&lt;span class="n"&gt;ClientResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ResponseResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;responseResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;responsesClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;userInputText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"What is in the tech news today?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;previousResponseId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;ResponseResult&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;responseResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 7. Print the text response.&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"\nResponse: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetOutputText&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 8. Extract URL citations from the response output items.&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\nCitations:"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;foundCitation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ResponseItem&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutputItems&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;MessageResponseItem&lt;/span&gt; &lt;span class="n"&gt;messageItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ResponseContentPart&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;messageItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// OutputTextAnnotations is only populated on output_text parts.&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kind&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;ResponseContentPartKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutputText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ResponseMessageAnnotation&lt;/span&gt; &lt;span class="n"&gt;annotation&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutputTextAnnotations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;annotation&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;UriCitationMessageAnnotation&lt;/span&gt; &lt;span class="n"&gt;citation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"  Citation: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;citation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; — &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;citation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="n"&gt;foundCitation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;foundCitation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"  (no URL citations returned — web search may not have been triggered)"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 9. Clean up: delete the agent version.&lt;/span&gt;
&lt;span class="c1"&gt;// DeleteAgentVersion takes agentName and agentVersion (the version string, e.g. "1").&lt;/span&gt;
&lt;span class="n"&gt;projectClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AgentAdministrationClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeleteAgentVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;agentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"\nCleaned up: agent version &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;agentVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; deleted."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The invocation is identical to the basic example. &lt;code&gt;CreateResponse&lt;/code&gt; returns &lt;code&gt;ClientResult&amp;lt;ResponseResult&amp;gt;&lt;/code&gt;; call &lt;code&gt;.Value.GetOutputText()&lt;/code&gt; for the text. The difference shows up in what's attached to that response: the output items contain &lt;code&gt;UriCitationMessageAnnotation&lt;/code&gt; entries for the sources the model cited.&lt;/p&gt;

&lt;p&gt;Two things to watch for if you copy this. First: the correct factory is &lt;code&gt;ResponseTool.CreateWebSearchPreviewTool&lt;/code&gt;, not &lt;code&gt;CreateWebSearchTool&lt;/code&gt;. Both exist in OpenAI 2.9.x. &lt;code&gt;CreateWebSearchTool&lt;/code&gt; serialises to &lt;code&gt;"type": "web_search"&lt;/code&gt;, which the Foundry Responses API currently rejects with a 400. &lt;code&gt;CreateWebSearchPreviewTool&lt;/code&gt; serialises to &lt;code&gt;"web_search_preview"&lt;/code&gt;, which Foundry accepts. Second: &lt;code&gt;DeclarativeAgentDefinition.Tools&lt;/code&gt; is a read-only property. You can't set it in an object initialiser. Call &lt;code&gt;.Add()&lt;/code&gt; after construction, as the code above does.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;userLocation&lt;/code&gt; hint is optional but worth setting if your agent answers questions for users in a specific region. Bing uses it to bias results. Omitting it still works.&lt;/p&gt;

&lt;p&gt;Web search is in preview. It's available in East US 2 and most major regions, but check the &lt;a href="https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/limits-quotas-regions#tool-support-by-region-and-model" rel="noopener noreferrer"&gt;tool support matrix&lt;/a&gt; for your target region before you depend on it in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost and a few regional notes
&lt;/h2&gt;

&lt;p&gt;For prompt agents you pay for inference tokens. When web search is enabled, Bing Search usage is on top of that. With &lt;code&gt;gpt-5.4-mini&lt;/code&gt; Global Standard and developer-level traffic, a full day of testing runs well under a dollar. No premium quota tier required.&lt;/p&gt;

&lt;p&gt;Tool availability varies by region. Code interpreter is unavailable in South Central US and Spain Central as of this writing. East US 2 has the broadest coverage. Check the tool support matrix before you settle on a deployment region; it's easier to pick the right one up front than to migrate a resource after the fact.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;DeclarativeAgentDefinition&lt;/code&gt; you built here maps directly to what you'd put in a CI/CD pipeline. Create the agent version during deployment, update the definition when instructions change, roll back by version ID when something breaks. That versioning model is where prompt agents become production-usable rather than just a demo.&lt;/p&gt;

&lt;p&gt;Hosted agents from .NET, and what the Microsoft Agent Framework brings to that picture, is where this series goes next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Examples
&lt;/h2&gt;

&lt;p&gt;You can find the examples and a markdown version of this article on my github. On github you'll find that the article has a few more notes in the code, which I intentionally left out here as its a bit verbose.&lt;br&gt;
&lt;a href="https://github.com/sitereactor/dev-to-articles/tree/main/foundry-agent-service-dotnet-first-agent" rel="noopener noreferrer"&gt;Code examples for this article&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/azure/foundry/agents/overview" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/foundry/agents/overview&lt;/a&gt; — Agent Service overview, agent type comparison, GA confirmation (2026-07-21)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/azure/foundry/how-to/develop/sdk-overview?pivots=programming-language-csharp" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/foundry/how-to/develop/sdk-overview?pivots=programming-language-csharp&lt;/a&gt; — .NET SDK packages, C# code samples, package conflict warning, RBAC role rename notice (2026-07-21)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/function-calling" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/tools/function-calling&lt;/a&gt; — C# prompt agent and hosted agent function calling samples, tool execution loop (2026-07-21)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/azure/foundry/agents/quickstarts/quickstart-hosted-agent" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/foundry/agents/quickstarts/quickstart-hosted-agent&lt;/a&gt; — Hosted agent prerequisite resources, provisioning steps (2026-07-21)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/limits-quotas-regions" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/limits-quotas-regions&lt;/a&gt; — GA status confirmation, regional support matrix, tool availability by region and model (2026-07-21)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>dotnet</category>
      <category>azure</category>
      <category>foundry</category>
    </item>
    <item>
      <title>One approach to structuring terraform</title>
      <dc:creator>Morten Christensen</dc:creator>
      <pubDate>Sun, 19 Mar 2023 14:59:42 +0000</pubDate>
      <link>https://dev.to/sitereactor/one-approach-to-structuring-terraform-57fh</link>
      <guid>https://dev.to/sitereactor/one-approach-to-structuring-terraform-57fh</guid>
      <description>&lt;p&gt;For this post I wanted to share how I prefer to structure my terraform code for use with multiple environments, modules and how I can work with it locally. If you have a different approach then please feel free to leave a comment - I'm always happy to learn and improve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First off, the folder structure&lt;/strong&gt;. The infrastructure folder sits in the root along side src, test, build and whatever else goes in the git repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── infrastructure
│   │── environments
│   │   ├── dev
│   │   ├── live
│   │   ├── main
│   └── modules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within an environment folder I would put &lt;code&gt;local.backend.tfvars&lt;/code&gt; and &lt;code&gt;local.secrets.tfvars&lt;/code&gt; files for local development purposes. These files should NOT be committed to git, so I always make sure to include them in the .gitignore file of the repository.&lt;br&gt;
Then there is the &lt;code&gt;backend.tfvars&lt;/code&gt; file, which is used to configure the backend used for the terraform state. Finally the (maybe?) most important file for the environment &lt;code&gt;variables.tfvars&lt;/code&gt;, which contains all the environment specific variables. A good example of this is using different SKUs of Azure App Service in Development versus Production. It could also be the subscription used for the environment - maybe you want to use an Azure Dev/Test Subscription for Development and a CSP/PAYG subscription for Production.&lt;/p&gt;

&lt;p&gt;The main folder contains the &lt;code&gt;main.tf&lt;/code&gt;, &lt;code&gt;secrets.tf&lt;/code&gt;, &lt;code&gt;outputs.tf&lt;/code&gt; and &lt;code&gt;variables.tf&lt;/code&gt; files. Depending on the size and content of the &lt;code&gt;main.tf&lt;/code&gt; file I tend to split it up into multiple files, so I don't end up having to scroll thousands of lines of code. The structure and naming of files can vary depending on what makes sense, but one simple approach is to divide the different types of resources into different files. There will likely be things like KeyVault or Networking, which ties into other resources, so it really depends.&lt;/p&gt;

&lt;p&gt;The modules folder is used for reusable modules. It's not always relevant, but I tend to keep the modules outside of the environments. The structure highlights that its reusable across environments.&lt;/p&gt;
&lt;h2&gt;
  
  
  Working locally
&lt;/h2&gt;

&lt;p&gt;When you need to run Terraform locally against the Development environment, use the steps listed below.&lt;/p&gt;

&lt;p&gt;Ensure that you have both &lt;code&gt;local.backend.tfvars&lt;/code&gt; and &lt;code&gt;local.secrets.tfvars&lt;/code&gt; in your environment folder with the actual keys in place.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;cd infrastructure/environments/main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform init -backend-config="../dev/local.backend.tfvars"&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform validate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform plan -var-file="../dev/local.secrets.tfvars" -detailed-exitcode -out=tfplan&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's have a look at the content of the &lt;code&gt;local.backend.tfvars&lt;/code&gt; when using Azure Blob Storage as the backend for the terraform state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource_group_name     = "rg-terraform-state-we-dev"
storage_account_name    = "terraformwestatedev"
container_name          = "terraform-state-dev"
key                     = "azure/terraform.tfstate"
access_key              = "&amp;lt;actual access key goes here&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The content of the &lt;code&gt;local.secrets.tfvars&lt;/code&gt; should correspond to what you put in the &lt;code&gt;secrets.tf&lt;/code&gt; from within the main folder, and what is needed to create resources within Azure/AWS/GCP. For Azure it would be Service Principal details.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tenant_id       = "&amp;lt;actual tenant id goes here&amp;gt;"
subscription_id = "&amp;lt;actual subscription id goes here&amp;gt;"
client_id       = "&amp;lt;actual client id goes here&amp;gt;"
client_secret   = "&amp;lt;actual client secret goes here&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When working locally you could connect to the development environment or use a different subscription / account setup. If you use the same subscription and terraform state locally as when deploying to development, you should be aware of the changes between working locally versus deploying via CI/CD. The deployed changes might revert what you are testing locally, and other deployments might fail because the state is expecting resources, which are not part of what is being deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  main.tf
&lt;/h2&gt;

&lt;p&gt;With the backend and secrets setup as mentioned above there is one important thing to include in the &lt;code&gt;main.tf&lt;/code&gt; file - the required providers and something like &lt;code&gt;azurerm&lt;/code&gt; as the backend like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  backend "azurerm" {}
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "= 2.29.0"
    }
    cloudflare = {
      source  = "terraform-providers/cloudflare"
      version = "~&amp;gt; 2.9.0"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend part is what will be "filled" with the &lt;code&gt;*backend.tfvars&lt;/code&gt; files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure DevOps, Terraform and Secrets
&lt;/h2&gt;

&lt;p&gt;When running infrastructure as code (IaC) as part of a pipeline in something like Azure DevOps (same applies to Github Actions and other CI/CD setups), you want to keep your secrets safe and inject them when needed as part of the pipeline.&lt;br&gt;
In order to run the terraform init, plan and apply commands there needs to be a backend for storing state and there needs to be secrets (like a Service Principal) for creating the actual resources in Azure. We also need to know, which environment we are deploying to, so we can select the right one (using the &lt;code&gt;variables.tfvars&lt;/code&gt; file that corresponds to the environment).&lt;/p&gt;

&lt;p&gt;The secrets can be added as a variable group for the environment under Pipelines/Library in Azure DevOps. You could also store the secrets in KeyVault and connect KeyVault to Azure DevOps, so they are not visible by anyone.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;backend.tfvars&lt;/code&gt; for the development environment would typically look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource_group_name     = "rg-terraform-state-we-dev"
storage_account_name    = "terraformwestatedev"
container_name          = "terraform-state-dev"
key                     = "azure/terraform.tfstate"
access_key              = "#{terraform_access_key}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see its pretty much the same as the &lt;code&gt;local.backend.tfvars&lt;/code&gt; where the only difference is the &lt;code&gt;access_key&lt;/code&gt; property, which will be replaced within the pipeline.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;secrets.tf&lt;/code&gt; file within the main folder will be variable definitions for the secrets needed to run the terraform. These variables will need to be set in the pipeline, so the IaC can be initialized.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;variable "tenant_id" {
  description = "Azure subscription tenant id."
}

variable "subscription_id" {
  description = "Azure subscription id."
}

variable "client_secret" {
  description = "Azure provider client secret"
}

variable "client_id" {
  description = "Azure provider Azure AD client id."
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within the variables.tfvars file for an environment I keep the variable secrets as tokens, which can later be replaced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tenant_id       = "#{tenant_id}"
subscription_id = "#{subscription_id}"
client_id       = "#{client_id}"
client_secret   = "#{client_secret}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When doing this in Azure DevOps with a YAML pipeline I use the following approach for replacing the tokens with secrets and generating - here its the validation stage of the pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;parameters:
  variable_group_name:
  environment_name:
  working_directory:
  source_branch:

jobs:
  - job: Validate
    displayName: Terraform Validate Plan
    continueOnError: false
    variables:
      - name: tf_work_dir
        value: "$(working_directory)/environments/main"
      - group: ${{ format('telemetry-{0}', parameters.environment_name) }}
      - ${{ if ne(parameters.variable_group_name, '') }}:
          - group: ${{ format('{0}-{1}', parameters.variable_group_name, parameters.environment_name) }}
    steps:
      - task: qetza.replacetokens.replacetokens-task.replacetokens@3
        displayName: "Replace tokens in *.tfvars with variables from the desired Variable Group"
        inputs:
          targetFiles: "$(working_directory)/environments/$(environment_name)/*.tfvars =&amp;gt; *.env.tfvars"
          encoding: "auto"
          writeBOM: true
          actionOnMissing: "warn"
          keepToken: false
          tokenPrefix: "#{"
          tokenSuffix: "}"
      - bash: |
          terraform init -backend-config="../$(environment_name)/backend.env.tfvars" -input=false
        displayName: Initialize configuration
        workingDirectory: $(tf_work_dir)
      - bash: terraform validate
        displayName: Validate configuration
        workingDirectory: $(tf_work_dir)

      - bash: |
          terraform plan -var-file="../$(environment_name)/variables.env.tfvars" -input=false
        displayName: Create execution plan
        workingDirectory: $(tf_work_dir)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup I can work with terraform locally, and I can choose to create a throw-away environment for trying out different things locally without interfering with Development. Alternatively, I can run it against the Development environment, which can be handy if resources needs to be imported or deleted without having to involve a pipeline.&lt;br&gt;
I can create any number of environments, which can have each their own subscription and resource configuration.&lt;/p&gt;

&lt;p&gt;Here is the file structure with all of the mentioned files&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsygn2wg3rk546z34etr0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsygn2wg3rk546z34etr0.png" alt=" " width="343" height="679"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The screenshot is from a real world telemetry service, which is composed of a Cloudflare Worker (point of entry / reverse proxy) and an Azure backend (API and Storage).&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>azure</category>
    </item>
  </channel>
</rss>
