# Microsoft Agent Framework — Setup Guide (.NET 10)

> **Scope:** stacks=["dotnet"]
> **Layer:** 0 (always-load via persona maf-expert)
> **Keywords:** maf setup, microsoft agents ai, chatclient, ai agent, agent framework, agent dotnet, AsAIAgent, responses api, chat completions, store false
> **Read by Claude in:** plan (pra citar pacote em tasks.json) + implement (pra escrever .csproj e Program.cs)

**Verified against:** Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 + OpenAI 2.13.0 (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs` (pipeline e escolha de protocolo). A superfície do SDK `OpenAI` e das pontes `Microsoft.Agents.AI.OpenAI` / `Microsoft.Extensions.AI.OpenAI` foi **medida por reflexão sobre as assemblies do pin** em 2026-09-08 — o ai-kit **não exercita** o cliente concreto da OpenAI — referencia o pacote no `.csproj`, mas não há `using OpenAI` em `src/` (medido 2026-09-08); ver a tabela "Dois níveis de garantia" no README do kit. Last-verified: 2026-09-08.

---

## Provider philosophy — direct models always

This framework is **provider-agnostic**. There is no "primary" provider hardcoded. Every model is reached as a **direct model** (you call the model API directly) — never via a hosted agent service. Hosted services lag 6-9 months on models and remove control.

First-class providers: **OpenAI, Google (Gemini), Anthropic (Claude), Ollama** (local/on-prem).
Azure OpenAI is also a direct model but is **not a default** — see note in §3.4.

The concrete model choice lives in the project's Model Registry, not in this standard. See `ai-agents-providers-model-registry`.

## Decision tree (when reading this in plan phase)

```
Está adicionando um agente AI a um projeto .NET?
├─ Sim → use este standard + ai-agents-sweet-spot + ai-agents-providers-model-registry
│   ├─ Projeto tem >1 modelo ou >1 provider? → SEMPRE use Model Registry
│   ├─ Provider OpenAI? → §3.1 (Responses = default) / §3.1.b (Chat Completions = opt-in)
│   ├─ Provider Ollama (local)? → §3.2
│   ├─ Provider Google / Anthropic? → §3.3
│   └─ Provider Azure OpenAI (data residency)? → §3.4 (nota, não default)
└─ Não → este standard não se aplica
```

---

## 1. Packages (NuGet)

**Nenhuma faixa flutuante.** Um SDK de cadência semanal com `2.*` é como o ProspectPRO comprou um `MissingMethodException` na Responses API **depois de a chamada ser paga**. Versões abaixo: as do `framework/ai-pin.json` (`pinnedAt` 2026-09-08) ou, quando o pacote **não está no pin**, a versão apurada no nuget.org em **2026-09-08**, dita como tal.

**Required for any MAF project:**

```xml
<!-- no ai-pin.json -->
<PackageReference Include="Microsoft.Agents.AI" Version="1.20.0" />
```

**Provider-specific (escolha conforme o Model Registry do projeto):**

```xml
<!-- OpenAI direct.
     Microsoft.Agents.AI.OpenAI e Microsoft.Extensions.AI.OpenAI NÃO estão no ai-pin.json;
     versões apuradas no nuget.org em 2026-09-08. -->
<PackageReference Include="Microsoft.Agents.AI.OpenAI"     Version="1.20.0" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.9.0" />
<PackageReference Include="OpenAI"                         Version="2.13.0" />   <!-- pin -->

<!-- Ollama (local / on-prem) — usa o mesmo OpenAI-compatible client -->
<PackageReference Include="OpenAI" Version="2.13.0" />                            <!-- pin -->

<!-- Google Gemini — Google.GenAI 1.21.0 (pin). Ver ai-agents-providers-model-registry §providers -->
<!-- Anthropic Claude — SDK verificado no standard model-registry.md -->
<!-- Azure OpenAI (somente se data residency exigir) — confira Azure.AI.OpenAI + Azure.Identity no NuGet -->
```

> **Armadilha de restore, medida em 2026-09-08:** `Microsoft.Extensions.AI.OpenAI` 10.9.0 declara a dependência `OpenAI (>= 2.12.0 && < 2.13.0)`. Com o `OpenAI` **2.13.0** do pin, o `dotnet restore` emite **NU1608** (*"Versão detectada do pacote fora da restrição de dependência"*). Duas saídas honestas: fixar `OpenAI` em 2.12.x ao lado da ponte, **ou** aceitar o aviso conscientemente e registrar a decisão em `decisions.md`. Não descubra isso no CI.

> **De onde vem cada extensão** (medido por reflexão sobre as assemblies do pin, 2026-09-08):
> | Extensão | Assembly que a publica | Namespace do `using` |
> |---|---|---|
> | `.AsIChatClient()` sobre `OpenAI.Chat.ChatClient` e sobre `OpenAI.Responses.ResponsesClient` | **`Microsoft.Extensions.AI.OpenAI`** (`Microsoft.Extensions.AI.OpenAIClientExtensions`) | `Microsoft.Extensions.AI` |
> | `.AsAIAgent(...)` sobre `ResponsesClient` / `ChatClient` | **`Microsoft.Agents.AI.OpenAI`** (`OpenAI.Responses.OpenAIResponseClientExtensions`, `OpenAI.Chat.OpenAIChatClientExtensions`) | `Microsoft.Agents.AI` + `OpenAI.Responses` / `OpenAI.Chat` |
> | `.AsIChatClientWithStoredOutputDisabled(...)` | **`Microsoft.Agents.AI.OpenAI`** | `OpenAI.Responses` |
>
> A abstração `IChatClient` em si vive em `Microsoft.Extensions.AI.Abstractions` (dependência transitiva) — código que usa `IChatClient` precisa de `using Microsoft.Extensions.AI;`.

> Os pacotes de Google e Anthropic mudam de nome/versão com frequência. O standard `ai-agents-providers-model-registry` mantém a lista verificada com data. Não trave SDK aqui.

**Optional (only when feature requires):**

```xml
<!-- Hosting helpers (AddAIAgent, AddSequentialWorkflow).
     PRERELEASE: não há versão estável. Apurado no nuget.org em 2026-09-08. -->
<PackageReference Include="Microsoft.Agents.AI.Hosting" Version="1.20.0-preview.260831.1" />

<!-- A2A protocol. PRERELEASE, fora do pin. Apurado em 2026-09-08. -->
<PackageReference Include="Microsoft.Agents.AI.A2A" Version="1.20.0-preview.260831.1" />

<!-- Workflows (grafo). Estável, fora do pin. Apurado em 2026-09-08. -->
<PackageReference Include="Microsoft.Agents.AI.Workflows" Version="1.20.0" />
```

**Forbidden in new MAF projects:**

- `Microsoft.SemanticKernel.*` — superseded by MAF, do not introduce (ver a exceção de Vector Store abaixo).
- `Microsoft.Extensions.AI.OpenAI` **9.\*** — a linha 9.x é a antiga. A linha **10.x é a atual e é o que a ponte `AsIChatClient` publica hoje**; ver a tabela de origem das extensões acima. (Correção de 2026-09-08: a versão anterior deste standard mandava substituir `Microsoft.Extensions.AI.OpenAI` por `Microsoft.Agents.AI.OpenAI`, o que não é possível — os dois pacotes publicam extensões **diferentes**.)
- Hosted agent services (Foundry Agent Service, etc.) — use direct models.

> **Exceção — conectores de Vector Store:** the ban above targets the SK **orchestration** surface (`Kernel`, `KernelFunction`, `ChatHistory`, `Kernel.InvokePromptAsync()`). It does **not** apply to `Microsoft.SemanticKernel.Connectors.*` packages — those are `Microsoft.Extensions.VectorData` backend connectors with zero SK orchestration code, despite the package name. See `ai-agents-vector-data-extensions` before treating one of these as forbidden — e note que o conector PgVector foi **renomeado** para `CommunityToolkit.VectorData.PgVector`.

---

## 2. Quick Start (canonical minimal agent)

**Caminho default para OpenAI: a Responses API.** Código conferido contra a doc oficial (learn.microsoft.com/en-us/agent-framework/integrations/by-component/model-providers/openai, `ms.date` 2026-09-03) e contra a assinatura real medida na assembly `Microsoft.Agents.AI.OpenAI` 1.20.0.

```csharp
using Microsoft.Agents.AI;
using OpenAI;
using OpenAI.Responses;

OpenAIClient client = new OpenAIClient("<from-config>");
ResponsesClient responses = client.GetResponsesClient();

AIAgent agent = responses.AsAIAgent(
    model: "gpt-4o-mini",
    instructions: "You are a helpful assistant.",
    name: "assistant");

var response = await agent.RunAsync("Analyze this order.");
Console.WriteLine(response.Text);
```

Para o caminho `IChatClient` puro (sem MAF):

```csharp
using Microsoft.Extensions.AI;
using OpenAI;

IChatClient chat = new OpenAIClient("<key>")
    .GetResponsesClient()
    .AsIChatClient("gpt-4o-mini");   // o modelo é argumento da ponte, não do GetResponsesClient()
```

> **Assinatura medida:** `OpenAIClient.GetResponsesClient()` **não recebe modelo**; o modelo entra em `AsAIAgent(model:)` ou em `AsIChatClient(defaultModelId)`. Isso difere do `GetChatClient(model)` da Chat Completions.

This snippet is the baseline. In a real project you never hardcode the model — use the Model Registry (`ai-agents-providers-model-registry`). Anything more complex (DI, tools, structured output, RAG) layers on top via the other ai-agents standards.

### Por que Responses é o default

| Ferramenta hospedada | Responses | Chat Completions |
|---|---|---|
| Code Interpreter | ✔ | ✘ |
| File Search | ✔ | ✘ |
| Hosted MCP | ✔ | ✘ |
| Function calling (tools do seu código) | ✔ | ✔ |

Fontes, ambas lidas em 2026-09-08:

- Microsoft Learn, *Agent Framework → model providers → OpenAI* (`ms.date` 2026-09-03), verbatim: *"Responses is the recommended primary client when available"* — é de lá que sai a matriz acima.
- OpenAI, *Migrate to the Responses API* (developers.openai.com/api/docs/guides/migrate-to-responses), verbatim: *"While Chat Completions remains supported, Responses is recommended for all new projects."* e *"…improved cache utilization (40% to 80% improvement when compared to Chat Completions in internal tests)"*.

**Chat Completions continua legítima** — não está deprecada. Escolha-a, **por escrito no alias** (`"api": "chat"`), quando: (a) o modelo alvo não é servido pela Responses; (b) a integração existente já fala Chat Completions e o custo de migrar não se paga; (c) há um SDK de outra linguagem no caminho (o Go, por exemplo) sem cliente de Responses. Ver `ai-agents-providers-model-registry` §API per alias — inclusive o limite documentado de tool calling com `reasoning_effort` na Chat Completions.

### `store: false` é a regra da casa

A Responses API **armazena por padrão**. OpenAI, mesmo guia, verbatim: *"Responses are stored by default. Chat completions are stored by default for new accounts. To disable storage when using either API, set `store: false`."* Respostas retidas ficam 30 dias no provedor.

Projetos desta casa mantêm o histórico **próprio** (ver `ai-agents-agent-session`), então não se quer uma segunda cópia da conversa no provedor. **Regra: `store: false` explícito.** Quem quiser o oposto registra o porquê em `decisions.md`.

O MAF tem um atalho de primeira classe para isso — medido na assembly `Microsoft.Agents.AI.OpenAI` 1.20.0:

```csharp
using OpenAI.Responses;

// AsIChatClientWithStoredOutputDisabled(ResponsesClient, string model, bool includeReasoningEncryptedContent)
IChatClient chat = new OpenAIClient("<key>")
    .GetResponsesClient()
    .AsIChatClientWithStoredOutputDisabled(
        model: "gpt-4o-mini",
        includeReasoningEncryptedContent: true);   // reasoning encriptado viaja no lugar do estado no servidor
```

No caminho cru do SDK, o campo é `CreateResponseOptions.StoredOutputEnabled = false` (medido; o nome C# não é `Store`).

> **Nota de leitura sobre reasoning encriptado:** a doc da OpenAI (*deployment checklist*) diz que *"…encrypted reasoning content enables a stateless handoff"*. Que isso seja **exclusivo** da Responses API é **inferência** desta casa a partir do conjunto das páginas, não uma frase da OpenAI — e está marcado como inferência de propósito.

### `OPENAI001`

**Medido, não inferido:** em `OpenAI` 2.13.0, `OpenAI.Responses.ResponsesClient` e `OpenAI.Responses.CreateResponseOptions` **ainda carregam** `[Experimental("OPENAI001")]` (reflexão sobre a assembly do pin, 2026-09-08). `OpenAI.Chat.ChatClient` não carrega.

Consequência prática: código que toca os tipos concretos de Responses precisa de `#pragma warning disable OPENAI001` ou `<NoWarn>OPENAI001</NoWarn>` no `.csproj`. Código que fica em `IChatClient`/`AIAgent` **não** precisa — a ponte absorve o diagnóstico. É um argumento objetivo a favor da abstração, não preferência de estilo.

---

## 3. Setup by Provider

> In any project with >1 model or >1 provider, **do not register `IChatClient` directly** — use the Model Registry (`ai-agents-providers-model-registry`). The snippets below show the raw client construction the registry wraps.

### 3.1 OpenAI direct — Responses (default)

```csharp
using OpenAI;
using OpenAI.Responses;
using Microsoft.Extensions.AI;

builder.Services.AddSingleton<IChatClient>(sp =>
{
    var cfg = sp.GetRequiredService<IConfiguration>();
    var apiKey = cfg["OpenAI:ApiKey"];
    if (string.IsNullOrWhiteSpace(apiKey))
    {
        throw new InvalidOperationException(
            "OpenAI:ApiKey is required. Bind it from an environment variable or secret store.");
    }
    return new OpenAIClient(apiKey)
        .GetResponsesClient()
        .AsIChatClientWithStoredOutputDisabled(
            model: cfg["OpenAI:Model"] ?? "gpt-4o-mini",
            includeReasoningEncryptedContent: true);
});
```

> **Why explicit `string.IsNullOrWhiteSpace` instead of `?? throw` or `!`?**
> `IConfiguration` returns the **empty string** (not `null`) for keys that
> exist with blank values — which is the common shape of a checked-in
> `appsettings.json` placeholder (`"OpenAI": { "ApiKey": "" }`). A
> `?? throw` guard against `null` therefore never fires; the program
> proceeds to a client built with `""` and crashes much later with
> an opaque OpenAI SDK error. Always check whitespace.

### 3.1.b OpenAI direct — Chat Completions (opt-in explícito)

Legítimo, mas escolhido por escrito. Ver `ai-agents-providers-model-registry` §API per alias.

```csharp
using OpenAI;
using OpenAI.Chat;
using Microsoft.Extensions.AI;

builder.Services.AddSingleton<IChatClient>(sp =>
    new OpenAIClient(apiKey)
        .GetChatClient(cfg["OpenAI:Model"] ?? "gpt-4o-mini")   // modelo no Get, aqui
        .AsIChatClient());
```

### 3.2 Ollama (local dev / on-prem)

Ollama exposes an **OpenAI-compatible** endpoint — Chat Completions, não Responses. A Responses API é da OpenAI, não do protocolo compatível: nunca aponte um endpoint de Ollama para o caminho de Responses.

```csharp
using OpenAI;
using OpenAI.Chat;
using System.ClientModel;
using Microsoft.Extensions.AI;

builder.Services.AddSingleton<IChatClient>(sp =>
    new ChatClient(
            model: "llama3.2:3b",
            credential: new ApiKeyCredential("ollama"),  // any non-empty string
            options: new OpenAIClientOptions { Endpoint = new Uri("http://localhost:11434/v1") })
        .AsIChatClient());
```

> Models < 3B params often fail tool calling. Use Ollama only for non-tool agents in dev.

### 3.3 Google (Gemini) / Anthropic (Claude)

Both are first-class providers reached as direct models. The exact NuGet SDK and client construction are documented (with verification date) in `ai-agents-providers-model-registry` §providers — that standard is the single source of truth for provider SDKs, since they change names/versions often. O pin traz `Google.GenAI` **1.21.0**.

### 3.4 Azure OpenAI (note — not a default)

Azure OpenAI is also a direct model, but requires an Azure resource + deployment per model. It is **not a default provider** in this framework — projects deploy to VPS, not Azure. Add an Azure branch to the Model Registry only when a client explicitly requires data residency in a specific Azure region. The pattern: `new AzureOpenAIClient(endpoint, credential).GetChatClient(deployment).AsIChatClient()`. Confirme as versões de `Azure.AI.OpenAI` e `Azure.Identity` no NuGet antes de travar — não estão no pin.

---

## 4. DI Registration (keyed agents)

> The `builder.AddAIAgent(...)` extension requires the `Microsoft.Agents.AI.Hosting` package (see §1 Optional) — que está em **prerelease**. For projects that register agents manually without `AddAIAgent`, the base `Microsoft.Agents.AI` package is enough.

```csharp
// Program.cs
builder.AddAIAgent("OrderAnalyzer", (sp, key) =>
{
    var chatClient = sp.GetRequiredService<IChatClient>();
    return new ChatClientAgent(
        chatClient,
        name: key,
        instructions: "You are an order analysis expert.");
});
```

Inject by key:

```csharp
public class AgentController(
    [FromKeyedServices("OrderAnalyzer")] AIAgent orderAgent) : ControllerBase { ... }
```

For agents with tools and structured output, see `ai-agents-multi-agent-patterns` and `ai-agents-structured-output`.

---

## 5. OpenTelemetry

**Correção medida em 2026-09-08:** `AgentOpenTelemetryConsts` e `agent.WithOpenTelemetry()` **não existem** em `Microsoft.Agents.AI` 1.20.0 (reflexão sobre a assembly do pin). A API real é `UseOpenTelemetry` sobre o *builder* de cada nível:

```csharp
using OpenTelemetry;
using OpenTelemetry.Trace;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

// Duas ActivitySource DIFERENTES, uma por nível — medidas nas assemblies do pin:
//   agente     → "Experimental.Microsoft.Agents.AI"        (span invoke_agent)
//   chat/tools → "Experimental.Microsoft.Extensions.AI"    (spans chat e execute_tool)
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .AddSource("Experimental.Microsoft.Agents.AI")
    .AddSource("Experimental.Microsoft.Extensions.AI")
    .AddOtlpExporter()
    .Build();

AIAgent instrumented = agent.AsBuilder()
    .UseOpenTelemetry(sourceName: "Experimental.Microsoft.Agents.AI",
                      configure: o => o.EnableSensitiveData = false)
    .Build();
```

> A regra de **onde** instrumentar (e como não duplicar span) é do standard `ai-agents-observability-patterns` — que é o dono canônico do assunto. O que precisa ficar aqui é só o fato que muda o `.csproj` e o `Program.cs`: são **duas** fontes, e o `AddSource` precisa das duas quando os dois níveis estão instrumentados. A instrução antiga deste standard — *"não use `Experimental.Microsoft.Extensions.AI.*`"* — está **errada para o pin** e foi removida.

---

## 6. Security checklist

- [ ] API keys never in source. Bind from environment variables / secret store (the VPS deploy uses env vars; Docker secrets or a vault are fine too).
- [ ] `appsettings.json` checked into git never contains real keys — only placeholder/empty values.
- [ ] Provider keys (`OpenAI:ApiKey`, `Google:ApiKey`, `Anthropic:ApiKey`) bound from environment.
- [ ] `store: false` (ou `AsIChatClientWithStoredOutputDisabled`) quando o projeto tem histórico próprio — senão a conversa fica duplicada no provedor por 30 dias.
- [ ] No agent receives raw user input without input-validation middleware (see `ai-agents-middleware-patterns`).

---

## 7. What NOT to do (anti-patterns)

| Wrong | Right |
|-------|-------|
| `using Microsoft.SemanticKernel` | `using Microsoft.Agents.AI` |
| `Kernel.InvokePromptAsync()` | `agent.RunAsync()` |
| `[KernelFunction]` | `[Description]` on method + parameters; register via `AIFunctionFactory.Create()` |
| `ChatHistory` | `IEnumerable<ChatMessage>` |
| Hardcoded model strings spread across codebase | Single source via Model Registry (see `ai-agents-providers-model-registry`) |
| `PackageReference` com faixa flutuante (`2.*`, `1.0.*`) | Versão exata; fora do pin, com data de apuração |
| Chat Completions por inércia num projeto novo OpenAI | Responses, e Chat Completions só com `"api": "chat"` escrito no alias |
| Responses com o `store` default | `store: false` / `AsIChatClientWithStoredOutputDisabled` |
| Apontar Ollama/endpoint compatível para o caminho de Responses | Ollama fala Chat Completions |
| Hosted agent service "to simplify" | Direct models always |

---

## 8. Checklist (verifiable by morph-eval)

- [ ] `Microsoft.Agents.AI` package present in `.csproj`, com versão exata
- [ ] No `Microsoft.SemanticKernel.*` orchestration references
- [ ] Nenhum `PackageReference` de SDK de IA com faixa flutuante
- [ ] `IChatClient` registered (either directly or via Model Registry)
- [ ] At least one `AIAgent` created via `AsAIAgent()` or `new ChatClientAgent()`
- [ ] Projeto OpenAI novo usa Responses, ou tem `"api": "chat"` declarado no alias
- [ ] `store` desligado quando o histórico é próprio
- [ ] Telemetria (se ligada) faz `AddSource` das **duas** fontes: `Experimental.Microsoft.Agents.AI` e `Experimental.Microsoft.Extensions.AI`
- [ ] Models reference Model Registry alias when project has >1 model use case

---

## 9. References

- `ai-agents-sweet-spot` — when to use agents vs workflows
- `ai-agents-providers-model-registry` — provider-agnostic pattern, `api` por alias, reasoning effort
- `ai-agents-agent-session` — histórico próprio, e por que `store: false`
- `ai-agents-observability-patterns` — dono canônico de spans, métricas e duplicação
- `ai-agents-structured-output` — typed responses
- `ai-agents-middleware-patterns` — telemetry, retry, anti-hallucination, streaming agent responses
- `ai-agents-vector-data-extensions` — a exceção `Microsoft.SemanticKernel.Connectors.*` da lista Forbidden acima
- Microsoft.Agents.AI docs: https://learn.microsoft.com/agent-framework/ (`ms.date` 2026-09-03 na página de OpenAI)
- OpenAI, *Migrate to the Responses API*: https://developers.openai.com/api/docs/guides/migrate-to-responses (lido 2026-09-08)

---

*MORPH-SPEC by Polymorphism Tech — ai-agents/setup.md v3.0 (2026-09-08)*
