Construisez un agent de diagnostic .NET avec Agent Harness : outils bornés, sessions, approbations, traces OpenTelemetry et contrôles CI reproductibles.

Agent Harness .NET : garde-fous et CI DevOps
Un agent qui diagnostique un incident finit vite par accumuler une boucle d’appels au modèle, des outils, un historique, des demandes d’approbation et des traces. Recoder cette infrastructure dans chaque application crée surtout des écarts : une limite d’itérations oubliée ici, une session absente là, une action sensible approuvée par défaut ailleurs.
Le Harness de Microsoft Agent Framework réunit ces briques autour d’un AIAgent standard. Il pilote les appels de fonctions, persiste l’historique entre les appels d’un même service, installe le mécanisme d’approbation et instrumente l’exécution. Il ne rend pas un agent sûr par magie : l’équipe doit toujours choisir ses outils, valider leurs paramètres, contrôler son identité et tester les règles déterministes.
Ce tutoriel construit un assistant C# de diagnostic de livraison. Il peut consulter un état de build et un journal de déploiement synthétique ; il ne peut ni exécuter une commande, ni modifier un environnement, ni déployer. La même séparation s’applique à une application réelle : l’agent propose et explique, le code vérifiable contrôle les capacités.

Ce que le Harness apporte — et ce qu’il n’apporte pas
Un HarnessAgent reste un AIAgent. On peut donc le brancher dans la même application qu’un agent plus simple, mais il compose des capacités généralement nécessaires à un travail en plusieurs étapes.
| Responsabilité | Ce que fournit le Harness | Ce qui reste à votre charge |
|---|---|---|
| Boucle outil-modèle | Invocation des fonctions et limite d’itérations configurable | Outils réduits, validation des entrées et autorisation métier |
| Continuité | Sessions et persistance d’historique après chaque appel de service | Durée de rétention, cloisonnement tenant/utilisateur et purge |
| Actions sensibles | Middleware d’approbation et règles d’auto-approbation | Identité de l’approbateur, journal d’audit et décision finale |
| Contexte | Compaction, mémoire et fournisseurs optionnels | Données autorisées, budget de tokens et protection contre les instructions non fiables |
| Exploitation | Instrumentation OpenTelemetry de l’agent et du client | Exporteur, échantillonnage, redaction et alertes |
Le point important est l’ordre de confiance : le modèle choisit éventuellement de demander un outil ; l’outil et la plateforme déterminent s’il est permis et avec quels paramètres. Une instruction en langage naturel améliore le comportement, mais ne constitue jamais un contrôle d’accès.
Prérequis et périmètre de l’exemple
Il faut :
- le SDK .NET 10 ou une version compatible avec la version de Microsoft Agent Framework choisie ;
- un fournisseur implémentant
IChatClientpour l’essai de bout en bout ; - un collecteur OTLP seulement pour exporter les traces hors du poste local ;
- aucun secret de production pour la partie test et CI.
Les classes de diagnostic ci-dessous sont volontairement en mémoire. Elles rendent les tests reproductibles et montrent où placer les vérifications. Remplacez-les ensuite par un adaptateur vers votre système de builds, avec une identité de lecture dédiée et limitée au projet demandé.
Créez la solution et épinglez les dépendances approuvées par votre équipe :
dotnet new sln --name DeliveryDiagnostics
dotnet new console --framework net10.0 --name DeliveryDiagnostics --output src/DeliveryDiagnostics
dotnet new xunit --framework net10.0 --name DeliveryDiagnostics.Tests --output tests/DeliveryDiagnostics.Tests
dotnet sln add src/DeliveryDiagnostics tests/DeliveryDiagnostics.Tests
dotnet add src/DeliveryDiagnostics package Microsoft.Agents.AI.Harness
dotnet add src/DeliveryDiagnostics package Microsoft.Extensions.AI
dotnet add src/DeliveryDiagnostics package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add tests/DeliveryDiagnostics.Tests reference src/DeliveryDiagnostics
dotnet restore --use-lock-file
Versionnez les fichiers src/DeliveryDiagnostics/packages.lock.json et tests/DeliveryDiagnostics.Tests/packages.lock.json générés par le restore avant de lancer la CI : le cache les utilise et --locked-mode vérifie leur cohérence.
Le Harness accepte n’importe quel IChatClient. Ce tutoriel ne fixe donc ni fournisseur ni clé dans le code. Consultez la documentation de votre fournisseur pour créer le client et conservez les paramètres sensibles hors du dépôt.
1. Définir un outil de diagnostic étroit
Commencez par une fonction qui n’a pas d’effet de bord. Elle n’accepte pas une commande, une URL ou un nom d’environnement libre : elle limite explicitement le projet et le nombre de lignes retournées. Ajoutez BuildDiagnostics.cs au projet applicatif :
namespace DeliveryDiagnostics;
public sealed record BuildSummary(
int BuildId,
string Project,
string Status,
IReadOnlyList<string> RecentEvents);
public interface IBuildDiagnosticsReader
{
Task<BuildSummary> GetAsync(
int buildId,
string project,
int maximumEvents,
CancellationToken cancellationToken);
}
public sealed class BuildDiagnosticsTools(IBuildDiagnosticsReader reader)
{
private static readonly HashSet<string> AllowedProjects =
["checkout", "catalogue"];
public async Task<BuildSummary> GetBuildSummaryAsync(
int buildId,
string project,
int maximumEvents = 20,
CancellationToken cancellationToken = default)
{
if (buildId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(buildId));
}
if (!AllowedProjects.Contains(project))
{
throw new ArgumentException("Projet non autorisé.", nameof(project));
}
if (maximumEvents is < 1 or > 50)
{
throw new ArgumentOutOfRangeException(nameof(maximumEvents));
}
return await reader.GetAsync(
buildId,
project,
maximumEvents,
cancellationToken);
}
}
Une validation dans l’outil est indispensable même si une interface utilisateur propose une liste déroulante. Un agent, un client MCP ou un appel HTTP direct peuvent contourner l’interface. Le lecteur réel doit aussi appliquer son autorisation côté serveur : la liste locale est une réduction de surface, non une preuve de droit sur le système de builds.
2. Créer un Harness sans capacités implicites
Ajoutez l’outil à ChatOptions.Tools, désactivez les capacités que ce scénario n’utilise pas, puis créez une session. HarnessAgent configure l’invocation de fonctions et le middleware d’approbation ; une session est donc nécessaire pour que les décisions soient cohérentes durant une exécution.
using DeliveryDiagnostics;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
static AIAgent CreateIncidentAgent(
IChatClient chatClient,
IBuildDiagnosticsReader reader)
{
var tools = new BuildDiagnosticsTools(reader);
return chatClient.AsHarnessAgent(new HarnessAgentOptions
{
Name = "delivery-diagnostics",
HarnessInstructions = "Utilise les outils avec parcimonie et distingue faits et hypothèses.",
DisableWebSearch = true,
DisableFileMemory = true,
DisableAgentSkillsProvider = true,
MaximumIterationsPerRequest = 4,
ChatOptions = new ChatOptions
{
Instructions = """
Tu diagnoses des échecs de livraison .NET.
Utilise seulement les résultats des outils fournis.
Ne propose jamais de relance, de déploiement ou de modification automatique.
Si les données sont insuffisantes, indique précisément la vérification humaine attendue.
""",
Tools =
[
AIFunctionFactory.Create(tools.GetBuildSummaryAsync,
name: "build_get_summary",
description: "Lit un résumé borné d’un build autorisé, sans le modifier."),
],
},
});
}
AIAgent agent = CreateIncidentAgent(chatClient, reader);
AgentSession session = await agent.CreateSessionAsync();
AgentResponse response = await agent.RunAsync(
"Analyse le build 42 du projet checkout et liste les faits utiles.",
session);
Console.WriteLine(response.Text);
DisableWebSearch, DisableFileMemory et DisableAgentSkillsProvider rendent visibles des choix qui ne sont pas nécessaires à un diagnostic de livraison. Évitez de fournir un accès aux fichiers ou au shell « au cas où ». Si l’application a vraiment besoin de ces capacités, ajoutez-les une par une, dans un répertoire isolé ou un bac à sable, avec une règle d’approbation et des tests dédiés.
La limite MaximumIterationsPerRequest protège contre une boucle outil-modèle inattendue ; elle ne constitue pas une limite de coût globale. Ajoutez aussi des limites côté fournisseur (tokens, durée, concurrence) et un timeout dans chaque appel aux dépendances.
3. Concevoir l’approbation comme une transition applicative
Le Harness installe l’approbation d’outils par défaut. Elle est utile lorsque l’agent peut demander une action conséquente, mais un serveur ne doit jamais remplacer cette décision par une approbation automatique globale.
Séparez les outils selon leur risque :
build_get_summarypeut rester lecture seule et ne demande pas une action humaine ;deployment_create_changedoit être une fonction distincte, avec son propre schéma et une approbation authentifiée ;- une approbation doit contenir le nom de l’outil, les arguments normalisés, le demandeur, l’approbateur et une expiration ;
- à la reprise, revalidez les autorisations et l’état réel : une décision prise il y a dix minutes ne doit pas déployer un environnement qui a changé entre-temps.
Le guide d’approbation des outils d’agents .NET détaille la boucle d’approbation au niveau Microsoft.Extensions.AI. Le Harness évite de réassembler ce mécanisme ; il ne doit pas raccourcir la frontière métier qui autorise une production.
4. Tester les invariants sans modèle
La CI d’une pull request doit prouver ce qui est déterministe : aucune requête réseau, aucun jeton et aucune réponse de LLM ne sont nécessaires pour vérifier les bornes de l’outil. Ajoutez BuildDiagnosticsToolsTests.cs :
using DeliveryDiagnostics;
namespace DeliveryDiagnostics.Tests;
public sealed class BuildDiagnosticsToolsTests
{
[Fact]
public async Task Returns_only_the_requested_bounded_summary()
{
var reader = new FakeBuildReader();
var tools = new BuildDiagnosticsTools(reader);
BuildSummary summary = await tools.GetBuildSummaryAsync(
buildId: 42,
project: "checkout",
maximumEvents: 10);
Assert.Equal(42, summary.BuildId);
Assert.Equal("checkout", summary.Project);
Assert.Equal(10, reader.MaximumEvents);
}
[Theory]
[InlineData(0, "checkout", 10)]
[InlineData(42, "production-admin", 10)]
[InlineData(42, "checkout", 51)]
public async Task Refuses_out_of_policy_arguments(
int buildId,
string project,
int maximumEvents)
{
var tools = new BuildDiagnosticsTools(new FakeBuildReader());
await Assert.ThrowsAnyAsync<ArgumentException>(() =>
tools.GetBuildSummaryAsync(buildId, project, maximumEvents));
}
private sealed class FakeBuildReader : IBuildDiagnosticsReader
{
public int MaximumEvents { get; private set; }
public Task<BuildSummary> GetAsync(
int buildId,
string project,
int maximumEvents,
CancellationToken cancellationToken)
{
MaximumEvents = maximumEvents;
return Task.FromResult(new BuildSummary(
buildId,
project,
"failed",
["Restore failed", "No credential was supplied"]));
}
}
}
Les modèles, prompts et fournisseurs changent ; une règle de capacité ne doit pas changer avec eux. Testez au minimum les identifiants invalides, les environnements interdits, les dépassements de volume et les refus d’approbation. Les tests d’intégration avec un vrai modèle sont utiles, mais doivent être isolés : ils mesurent l’assemblage et la qualité, pas l’autorisation fondamentale.
5. Rendre le raisonnement exploitable avec OpenTelemetry
Le Harness instrumente l’agent et son pipeline de client de chat, mais n’envoie rien tout seul. L’application choisit explicitement la destination. Voici un branchement OTLP minimal ; configurez l’endpoint via les variables de votre plateforme, jamais en dur dans le dépôt.
using OpenTelemetry;
using OpenTelemetry.Trace;
const string ApplicationSource = "DeliveryDiagnostics";
using TracerProvider tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource(ApplicationSource)
.AddSource("Experimental.Microsoft.Agents.AI")
.AddSource("microsoft.agent_framework.harness")
.AddOtlpExporter()
.Build();
Reliez ensuite la requête utilisateur au Activity HTTP ou de message déjà existant. Les traces doivent aider à répondre à des questions opérationnelles : quel outil a été demandé, a-t-il été refusé, combien d’itérations ont eu lieu et quelle dépendance a échoué ? Elles ne doivent pas devenir une copie du contexte : évitez les prompts complets, les jetons, les en-têtes d’autorisation, les sorties de build sensibles et les réponses brutes non expurgées.
6. Deux niveaux de CI plutôt qu’un agent privilégié dans chaque PR
La première pipeline vérifie le code et les invariants sans identité cloud. Elle peut tourner sur chaque pull request, y compris celles provenant d’un fork.
name: Validate agent guardrails
on:
pull_request:
paths:
- "src/DeliveryDiagnostics/**"
- "tests/DeliveryDiagnostics.Tests/**"
- ".github/workflows/agent-guardrails.yml"
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x
cache: true
cache-dependency-path: "**/packages.lock.json"
- run: dotnet restore --locked-mode
- run: dotnet build --configuration Release --no-restore
- run: dotnet test --configuration Release --no-build
Ajoutez une seconde campagne, déclenchée sur une branche de confiance ou manuellement, pour un test avec modèle. Elle reçoit une identité fédérée à privilèges minimaux, un environnement synthétique et un plafond de coût. Elle vérifie par exemple que l’agent cite le build demandé, n’invente pas une relance et met une action sensible en attente d’approbation. Les articles sur les évaluations IA dans une pipeline .NET et sur les tests MTP assistés par IA complètent ce niveau probabiliste.
Ne donnez pas l’identité de déploiement à la première pipeline pour « tester en vrai ». Une CI qui peut modifier la production pour démontrer une capacité d’agent transforme une vérification en chemin d’attaque.
Points de vigilance avant la production
- Le Harness est une composition, pas une politique d’accès. Désactivez les capacités inutiles et contrôlez les outils dans leur propre code.
- Une session doit être isolée. Attachez-la à l’utilisateur et au tenant authentifiés ; n’acceptez pas un identifiant de session arbitraire comme preuve d’accès.
- Le contexte est une donnée sensible. La compaction, la mémoire et les traces peuvent conserver des informations : définissez une rétention, une redaction et un propriétaire.
- L’auto-approbation ne convient qu’au faible risque. Une règle « toujours autoriser » sur un outil de déploiement annule l’intérêt du contrôle humain.
- La limite d’itérations n’est pas un budget complet. Cumulez limites de temps, tokens, concurrence et taille de réponse aux niveaux application et fournisseur.
- Épinglez et réévaluez les packages. Les fonctionnalités de Harness évoluent rapidement ; chaque mise à niveau doit repasser les tests de politique et la campagne d’intégration isolée.
Sources
- Microsoft Learn décrit l’architecture, les options et les capacités du Agent Harness, dont
AsHarnessAgent, les sessions, la compaction et les fonctionnalités opt-in. - La documentation Microsoft sur les function tools avec Harness précise la boucle d’invocation, la session et
MaximumIterationsPerRequest. - Le guide d’observabilité Agent Framework documente l’instrumentation OpenTelemetry du Harness et le fait qu’un exporteur reste à configurer par l’application.
- La série .NET officielle sur le Harness C# donne le contexte de mise en production et les exemples associés.
Le Harness accélère la construction d’un agent robuste parce qu’il standardise la plomberie, pas parce qu’il remplace les décisions d’exploitation. En gardant les outils étroits, les approbations explicites et les tests déterministes dans la CI, vous obtenez un assistant utile pour les incidents sans lui confier l’autorité de les provoquer ou de les résoudre seul.