When the UAE government set a target for agentic AI to power half of government services, a lot of .NET developers I know in Dubai and Abu Dhabi felt both excited and stuck. Excited because the opportunity is real. Stuck because nearly every AI agent demo you see online uses Python, LangChain, or Node.js. If your stack is .NET and you work on enterprise systems in the UAE, switching languages just to build an AI agent is not practical.
Then Microsoft shipped the Agent Framework v1.0 in April 2026. It is part of the Microsoft.Extensions.AI abstractions that landed in .NET 9, and it lets you build semi-autonomous AI agents using plain C#. No Python. No Flask. No separate AI microservice in another language. Just your ASP.NET Core project, a few NuGet packages, and the same orchestration patterns you already use.
This article walks through four concrete capabilities. I will show you code, explain the tradeoffs, and frame everything around enterprise use cases that matter in the UAE: compliance document processing, customer service triage, and automated report generation.
What the Microsoft Agent Framework Actually Does
Before we get into code, here is what this framework is and is not. It is a set of abstractions and implementations on top of Microsoft.Extensions.AI that let you define an AI agent, give it tools, give it memory, and orchestrate multiple agents as a graph. It is not a replacement for Copilot Studio or Azure AI Agent Service. Think of it as the low-level SDK you use when you need full control over agent behavior inside your .NET application.
The core namespace is Microsoft.Agents.AI. It builds on the IChatClient interface from Microsoft.Extensions.AI, which means it works with OpenAI, Azure OpenAI, Ollama, or any provider that implements that interface. As the official documentation explains, the framework combines AutoGen’s simple agent abstractions with Semantic Kernel’s enterprise features, adding graph-based workflows for explicit multi-agent orchestration.
Four concepts matter most:
- AIAgent — the base class for agents that use a chat client and can process tool calls.
- AIFunctionFactory — wraps any .NET method into a tool the agent can call.
- AIContextProvider — gives agents persistent memory across conversations.
- Agent workflows — graph-based orchestration that connects multiple agents in a defined execution path.
Let us build something real with each of these.
1. Creating Your First Agent with AsAIAgent()
You start with any IChatClient. If you already use Azure OpenAI in your UAE project, you are halfway there.
using Microsoft.Agents.AI;
using Azure.AI.OpenAI;
using Azure.Identity;
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME");
var chatClient = new AzureOpenAIClient(
new Uri(endpoint),
new AzureCliCredential())
.GetResponsesClient(deploymentName);
var agent = chatClient.AsAIAgent(
name: "ComplianceAgent",
instructions: "You are a UAE compliance specialist.");
That is it. You have an agent that can hold a conversation and respond to messages. But a raw chat agent is not very useful for enterprise workflows. The real power comes from what you attach to it.
One design detail worth noting: AsAIAgent() returns an AIAgent that implements IAgent. The interface is small. You call SendAsync() with a list of ChatMessage objects and get back a response. You can also stream the response with SendStreamingAsync(). If you have used IChatClient, the pattern is identical.
2. Equipping Agents with Tools Using AIFunctionFactory
An agent that cannot do anything is just a chatbot with extra steps. Tools are what turn it into an agent. AIFunctionFactory converts any .NET method into a callable tool that the agent can invoke when it decides it needs to.
Here is a real example for a UAE compliance use case. Let us say your agent needs to look up VAT regulations or check whether a document has been signed.
public class ComplianceTools
{
private readonly ComplianceDbContext _db;
public ComplianceTools(ComplianceDbContext db)
{
_db = db;
}
[Description("Retrieves the current VAT law text for a given article number.")]
public async Task<string> GetVatLawArticle(int articleNumber)
{
var article = await _db.VatLaws
.FirstOrDefaultAsync(a => a.ArticleNumber == articleNumber);
return article?.FullText ?? "Article not found.";
}
[Description("Checks whether a compliance document has been digitally signed.")]
public async Task<bool> IsDocumentSigned(string documentId)
{
var doc = await _db.ComplianceDocuments
.FirstOrDefaultAsync(d => d.Id == documentId);
return doc?.IsSigned ?? false;
}
}
// Register tools with the agent
var toolsInstance = new ComplianceTools(dbContext);
var tools = new List<AITool>
{
AIFunctionFactory.Create(toolsInstance.GetVatLawArticle),
AIFunctionFactory.Create(toolsInstance.IsDocumentSigned)
};
var agent = chatClient.AsAIAgent(
name: "ComplianceAgent",
options: new AIAgentOptions
{
Tools = tools,
Instructions = "You are a UAE compliance specialist. Use the provided tools to verify documents before responding."
});
The [Description] attribute is critical. The LLM uses it to decide whether to call the tool. Write descriptions that clearly state what the tool does and when to use it. If the description is vague, the agent will ignore it or misuse it.
One tradeoff worth knowing: the agent decides when to call tools. You cannot force it. If your workflow requires mandatory tool calls before proceeding, you need to build that logic in your orchestration layer, not inside the agent itself. I will show how to do that in the multi-agent section.
3. Persistent Memory with AIContextProvider
By default, an agent has no memory between calls. You give it the chat history each time, and that is all it knows. For enterprise use cases like a customer service triage agent that remembers a user’s previous tickets, you need persistent memory.
AIContextProvider is the abstraction for that. It stores and retrieves context across conversations. The framework ships with an in-memory provider and a file-based provider, but you can implement your own for SQL Server or Cosmos DB.
public class TicketContextProvider : AIContextProvider
{
private readonly ICosmosDbContext _cosmos;
public TicketContextProvider(ICosmosDbContext cosmos)
{
_cosmos = cosmos;
}
public override async Task<AgentContext> GetContextAsync(
string contextId, CancellationToken ct = default)
{
var data = await _cosmos.GetItemAsync<AgentContextData>(
"agent-contexts", contextId);
return data?.ToAgentContext() ?? new AgentContext(contextId);
}
public override async Task SetContextAsync(
string contextId, AgentContext context, CancellationToken ct = default)
{
await _cosmos.UpsertItemAsync(
"agent-contexts", context.ToData(contextId));
}
}
// Usage
var contextProvider = new TicketContextProvider(cosmosClient);
var agent = chatClient.AsAIAgent(
name: "SupportAgent",
options: new AIAgentOptions
{
ContextProvider = contextProvider
});
When you call SendAsync, you pass the contextId. The framework loads the context before processing and saves it after. This is how your agent remembers that the customer from Abu Dhabi complained about a billing issue three days ago and already received a partial refund.
Be careful with context size. If you persist entire conversation histories without trimming, you will hit token limits fast. Implement a strategy: keep the last N messages, summarize older ones, or store a structured state object instead of raw history. For Cosmos DB, I recommend storing a summary field and the last 10 messages per session.
4. Multi-Agent Orchestration with Graph Workflows
Single agents are fine for simple tasks. But enterprise workflows often require multiple agents with different specializations. The Agent Framework handles this with graph-based workflows that let you define nodes (agents) and edges (transitions).
Let us build a multi-agent workflow for automated report generation. This is a common request I see in UAE enterprises: pull data from an ERP, analyze it, generate a PDF report, and email it.
// Define three specialized agents
var dataAgent = chatClient.AsAIAgent(
name: "DataAgent",
options: new AIAgentOptions
{
Instructions = "You retrieve financial data from the ERP system. Output DATA_READY when done.",
Tools = new[] { AIFunctionFactory.Create(erpService.GetFinancialData) }
});
var analysisAgent = chatClient.AsAIAgent(
name: "AnalysisAgent",
options: new AIAgentOptions
{
Instructions = "You analyze financial data and identify trends. Output ANALYSIS_COMPLETE when done."
});
var reportAgent = chatClient.AsAIAgent(
name: "ReportAgent",
options: new AIAgentOptions
{
Instructions = "You generate a structured report in markdown format and call the PDF tool.",
Tools = new[] { AIFunctionFactory.Create(pdfService.GeneratePdf) }
});
// Build the workflow graph
var workflow = new AgentWorkflow();
workflow.AddNode("data_retrieval", dataAgent);
workflow.AddNode("analysis", analysisAgent);
workflow.AddNode("report_generation", reportAgent);
// Define the execution path
workflow.AddEdge("data_retrieval", "analysis",
condition: (ctx) => ctx.Output.Contains("DATA_READY"));
workflow.AddEdge("analysis", "report_generation",
condition: (ctx) => ctx.Output.Contains("ANALYSIS_COMPLETE"));
// Execute the workflow
var workflowAgent = workflow.AsAIAgent("ReportWorkflow");
var result = await workflowAgent.SendAsync(new[] {
new ChatMessage(ChatRole.User, "Generate a Q2 financial report for ADNOC.")
});
The workflow engine evaluates conditions on each edge and routes execution accordingly. If the data agent fails to retrieve data, the condition does not match, and the workflow stops, returning the error. This gives you deterministic control over non-deterministic agents.
A few things I have learned building these workflows:
- Keep the Instructions property strict. Tell each agent exactly what to output when done. The condition checks look for those markers.
- Use
CancellationTokenon every workflow execution. LLM calls can timeout, and you do not want a stuck agent blocking a request. - Log every edge transition. When a workflow fails, you need to know which node caused it. The framework does not log transitions by default, so add your own logging around each
SendAsynccall.
Putting It All Together: A UAE Enterprise Use Case
Let me tie everything together with a single scenario. A Dubai-based insurance company wants to automate compliance document processing. Documents come in from brokers, need to be checked against UAE Insurance Authority regulations, signed off, and archived.
The workflow looks like this:
- Ingestion agent receives the document, extracts metadata using OCR tools, and classifies the document type.
- Validation agent checks the document against IA regulations using a retrieval tool connected to a vector database of regulations.
- Approval agent determines if manual review is needed or if it can auto-approve based on rules.
- Archival agent stores the final document in Azure Blob Storage and updates the compliance database.
Each agent has its own tools, its own instructions, and its own context provider. The graph ensures they execute in order, with manual escalation as a fallback node. This entire pipeline runs inside an existing ASP.NET Core background service, no separate infrastructure needed.
What the Framework Does Not Handle (Yet)
I want to be honest about the gaps. The framework v1.0 is solid for what it does, but it is not complete.
There is no built-in human-in-the-loop support. You have to implement your own approval mechanism, typically by pausing the workflow execution and persisting the state until a human responds. You can hack this by storing the workflow state in your database and reloading it, but it is not elegant.
There is no built-in agent observability. No OpenTelemetry integration for agent steps. You need to add your own logging and tracing around SendAsync calls. I recommend wrapping each agent call with ActivitySource spans if you use .NET’s distributed tracing.
Workflow execution is single-threaded within a process. For high-throughput scenarios, you need to manage concurrency yourself. The graph does not support parallel node execution in v1.0, though the documentation hints it may come in future releases.
Final Thoughts
If you are a .NET developer in the UAE and you have been watching the agentic AI trend from the sidelines because the tools felt like they belonged to the Python world, the Microsoft Agent Framework changes that equation. You can build agents using the same patterns you already use for dependency injection, middleware, and background services.
The UAE’s push toward agentic AI in government services means there will be demand for developers who can deliver these systems on Microsoft infrastructure. That is the stack most government entities here already run. You are not choosing between AI and .NET anymore. You get both.
Start small. Build a single agent that can query your database. Give it one tool. Let it fail. Then add memory. Then add a second agent. The framework’s learning curve is gentle because the abstractions mirror what you already know. That is the point.
Frequently Asked Questions
- Does the Microsoft Agent Framework work with OpenAI or only Azure OpenAI?
- It works with any provider that implements the
IChatClientinterface from Microsoft.Extensions.AI. That includes OpenAI, Azure OpenAI, Ollama, and GitHub Models. You callAsAIAgent()on anyIChatClientinstance. The framework is provider-agnostic by design, as confirmed in the official overview. - Can I run multiple agents in parallel with AgentWorkflow?
- In v1.0, workflow execution is sequential within a single process. Nodes run one after another based on edge conditions. For parallel execution, you need to manage concurrency yourself by running multiple workflow instances or using a separate orchestration layer like Durable Functions.
- How do I handle authentication and user context in multi-agent workflows?
- Pass user context through the
ChatMessageproperties or through theAgentContextobject stored byAIContextProvider. The framework does not manage authentication. You handle that in your ASP.NET Core middleware before the agent code runs, then pass the relevant user claims into the agent’s context. - What happens when the agent tries to call a tool that fails?
- The tool call returns an error message that the LLM receives. The agent may retry or report the failure depending on its instructions. For critical workflows, build error handling in the workflow edges by checking the output and routing to a fallback node instead of relying on the agent to handle it gracefully.
- Can I deploy these agents as Azure Functions or containerized services?
- Yes. The agents are plain .NET objects. You can host them in Azure Functions, a console app, a Windows Service, or a container on AKS. The framework does not impose a hosting model. For serverless, be aware that cold starts add latency to the first LLM call because the initial connection setup takes time even though the model weights do not reload.