Créez un agent C# pour analyser un incident DevOps avec GitHub Copilot, une liste blanche d’outils et des tests de permissions reproductibles.

Agent Copilot .NET : diagnostiquer sans droits d’écriture
Un agent qui peut lire les fichiers d’un dépôt, lancer des commandes et modifier une configuration peut accélérer un diagnostic. Il peut aussi transformer une consigne ambiguë, un log malveillant ou une simple erreur de modèle en modification non désirée. Le bon point de départ n’est donc pas un agent « autonome » dans la CI : c’est un agent de diagnostic dont les capacités sont explicitement limitées.
Microsoft Agent Framework propose désormais un fournisseur .NET pour le SDK GitHub Copilot. Cette combinaison est intéressante pour les équipes C# : l’agent utilise les capacités de Copilot, tandis que l’application garde la main sur les permissions, les outils MCP et le cycle de vie de la session. Les hooks du SDK GitHub Copilot permettent notamment de refuser un outil avant son exécution.
Ce tutoriel construit un assistant d’incident en lecture seule. Il peut inspecter un dépôt et expliquer un échec, mais ne peut ni lancer un shell, ni écrire un fichier, ni déployer. La règle est du code C# testé et une pipeline vérifie qu’elle ne régresse pas.
Statut à vérifier. L’intégration GitHub Copilot de Microsoft Agent Framework évolue vite. Épinglez les versions NuGet dans une application de production, relisez les notes de version et testez une mise à jour dans un environnement isolé avant de l’élargir.
Le périmètre : diagnostic, pas remédiation
Pour un incident de build, l’agent peut répondre à des questions utiles : quels fichiers de configuration sont présents, où est définie la version du SDK, quelle erreur apparaît dans un journal déjà collecté. Il n’a pas besoin de modifier le dépôt pour cela.
| Capacité | Exemple | Décision de cet article |
|---|---|---|
| Lecture | read_file, glob, grep, view | autorisée par liste blanche |
| Shell | dotnet test, git status | refusée |
| Écriture | modifier un YAML ou un fichier source | refusée |
| Mutation distante | appeler un outil MCP de déploiement | refusée |
Cette séparation importe davantage que le prompt. Une instruction telle que « ne modifie jamais de fichier » est utile, mais elle reste interprétée par le modèle. Un hook qui renvoie deny constitue une frontière technique indépendante de cette instruction.
Le SDK demande par défaut une autorisation avant d’exécuter un shell, d’accéder aux fichiers ou de récupérer une URL. Microsoft précise également que les agents qui reçoivent des permissions de shell ou de fichiers doivent être exécutés dans un environnement conteneurisé, par exemple un Dev Container. Ici, nous commençons sans ces droits d’écriture ou de shell ; si vous les ajoutez plus tard, isolez l’agent et son identité dès le premier essai.
Prérequis
Prévoyez :
- le SDK .NET 10 ou plus récent ;
- une installation authentifiée de GitHub Copilot CLI, utilisée par le SDK ;
- un abonnement GitHub Copilot autorisant cet usage dans votre organisation ;
- un dépôt de démonstration ne contenant ni secret ni donnée de production.
Le runtime Copilot doit être authentifié : l’agent ne peut pas être testé de bout en bout uniquement avec dotnet build. En revanche, la politique de permissions présentée ci-dessous ne dépend d’aucun appel au modèle et se teste localement comme une règle métier ordinaire.
Créer le projet
Créez un projet console et installez le SDK Copilot ainsi que l’adaptateur Agent Framework :
dotnet new console --framework net10.0 --name IncidentDiagnosticAgent
Set-Location IncidentDiagnosticAgent
dotnet add package GitHub.Copilot.SDK
dotnet add package Microsoft.Agents.AI.GitHub.Copilot
Le premier package pilote le runtime Copilot ; le second expose un AIAgent utilisable par Microsoft Agent Framework. La documentation du fournisseur confirme qu’un CopilotClient peut être transformé avec AsAIAgent et que les outils, les permissions et les serveurs MCP passent par SessionConfig.
Ajoutez ensuite un projet de tests à côté de l’application :
Set-Location ..
dotnet new xunit --framework net10.0 --name IncidentDiagnosticAgent.Tests
dotnet add IncidentDiagnosticAgent.Tests reference IncidentDiagnosticAgent
La structure minimale devient :
IncidentDiagnosticAgent/
Program.cs
ReadOnlyToolPolicy.cs
IncidentDiagnosticAgent.Tests/
ReadOnlyToolPolicyTests.cs
Exprimer la liste blanche dans du code testable
Créez ReadOnlyToolPolicy.cs. Gardez la liste courte et donnez un nom explicite aux commandes autorisées : une liste très large de « commandes de lecture » devient rapidement impossible à auditer.
namespace IncidentDiagnosticAgent;
public static class ReadOnlyToolPolicy
{
private static readonly HashSet<string> AllowedTools =
[
"read_file",
"glob",
"grep",
"view"
];
public static bool Allows(string toolName) => AllowedTools.Contains(toolName);
}
Cette politique ne tente pas de déduire qu’une commande est inoffensive. Elle fait l’inverse : tout ce qui n’est pas recensé est refusé. En particulier, shell, bash, write_file, edit_file et les futurs outils inconnus ne peuvent pas être activés accidentellement par une montée de version.
Ajoutez les tests ReadOnlyToolPolicyTests.cs :
using IncidentDiagnosticAgent;
namespace IncidentDiagnosticAgent.Tests;
public sealed class ReadOnlyToolPolicyTests
{
[Theory]
[InlineData("read_file")]
[InlineData("glob")]
[InlineData("grep")]
[InlineData("view")]
public void Allows_only_declared_diagnostic_tools(string toolName)
{
Assert.True(ReadOnlyToolPolicy.Allows(toolName));
}
[Theory]
[InlineData("shell")]
[InlineData("write_file")]
[InlineData("edit_file")]
[InlineData("deploy-production")]
public void Denies_any_tool_outside_the_allow_list(string toolName)
{
Assert.False(ReadOnlyToolPolicy.Allows(toolName));
}
}
Exécutez-les avant de connecter un modèle :
dotnet test ../IncidentDiagnosticAgent.Tests
Un test de politique n’est pas un test d’inférence. C’est volontaire : il prouve rapidement une propriété qui ne doit jamais varier avec la température du modèle, un changement de prompt ou le contenu d’un ticket.
Brancher le hook qui refuse les outils
Remplacez Program.cs par le programme suivant. Le hook OnPreToolUse est appelé avant l’outil. Il renvoie allow uniquement pour la liste blanche ; autrement, il retourne deny avec une explication utilisable dans les traces. Le gestionnaire de permission refuse aussi toute demande qui arriverait hors de ce flux : il n’existe pas d’opérateur humain dans cette démonstration à qui déléguer une permission inattendue.
using GitHub.Copilot;
using GitHub.Copilot.Rpc;
using IncidentDiagnosticAgent;
using Microsoft.Agents.AI;
await using CopilotClient copilotClient = new();
await copilotClient.StartAsync();
SessionConfig sessionConfig = new()
{
Hooks = new SessionHooks
{
OnPreToolUse = (input, invocation) =>
{
if (!ReadOnlyToolPolicy.Allows(input.ToolName))
{
return Task.FromResult<PreToolUseHookOutput?>(new()
{
PermissionDecision = "deny",
PermissionDecisionReason =
$"Diagnostic mode blocks tool '{input.ToolName}'.",
});
}
return Task.FromResult<PreToolUseHookOutput?>(new()
{
PermissionDecision = "allow",
});
},
},
OnPermissionRequest = (_, _) =>
Task.FromResult(PermissionDecision.UserNotAvailable()),
};
AIAgent agent = copilotClient.AsAIAgent(
sessionConfig,
instructions: """
Tu es un assistant de diagnostic DevOps .NET.
Analyse les fichiers et explique les causes possibles.
Ne tente jamais de modifier un fichier, d'exécuter une commande,
de publier un changement ou d'appeler un outil de déploiement.
Si une vérification exige une action, décris la commande à l'opérateur.
""");
Console.WriteLine(await agent.RunAsync("""
Analyse ce dépôt .NET en lecture seule. Identifie les fichiers de build
pertinents et propose trois causes vérifiables d'un échec de restauration.
Ne fais aucune modification.
"""));
Les noms read_file, glob, grep et view proviennent de l’exemple .NET officiel de contrôle de permissions par hook. Il faut néanmoins les considérer comme une interface versionnée du runtime : mettez à jour simultanément le SDK et les tests si le runtime documente un changement de nom ou de comportement.
OnPermissionRequest n’est pas une seconde liste blanche. Il représente le refus sûr lorsqu’un outil demande une validation après le hook. Ne remplacez pas UserNotAvailable() par PermissionHandler.ApproveAll dans un agent de service : le SDK GitHub le présente comme une option de simplicité, pas comme un modèle de permissions minimales.
Essayer l’agent sans lui donner de pouvoir d’écriture
Lancez l’application depuis un clone de test :
dotnet run --project IncidentDiagnosticAgent
Demandez-lui d’expliquer un échec de restauration ou de localiser la version du SDK. Si le modèle essaie d’appeler un shell ou une écriture, le hook doit le refuser. Cette vérification comportementale est importante : un modèle peut proposer une action interdite, mais l’interdiction doit être appliquée par le programme, pas seulement racontée dans la réponse.
Commencez avec un répertoire sans secrets. Même un agent en lecture seule peut recopier le contenu d’un fichier dans son contexte ou ses traces. Excluez les fichiers .env, les caches de credentials, les clés SSH et les journaux non anonymisés de la zone accessible à l’agent.
Ajouter des outils MCP sans élargir le risque par défaut
Le fournisseur Copilot peut aussi connecter des serveurs MCP locaux ou distants via SessionConfig. C’est tentant pour consulter un build ou une pull request, mais un serveur MCP n’est pas automatiquement en lecture seule. Son outil de publication, de création de ticket ou de déploiement est une capacité distincte.
Avant d’ajouter un serveur, répondez pour chaque outil à ces questions :
- Quelle donnée ou quelle ressource lit-il exactement ?
- Peut-il écrire ou déclencher un effet de bord indirect ?
- Quelle identité porte son appel et quels droits possède-t-elle ?
- Son résultat peut-il contenir un secret ou une instruction non fiable ?
- Le nom de l’outil figure-t-il dans une liste blanche spécifique et testée ?
Le serveur MCP Azure DevOps distant est une illustration utile : il expose des données de pull requests, de pipelines et de work items, mais son authentification et les environnements clients compatibles doivent être validés. Ne donnez pas à un agent de diagnostic l’identité d’un déployeur, même si le même serveur offre les deux catégories d’opérations.
Faire respecter la règle dans la CI
La CI doit vérifier le code de politique, sans exécuter l’agent avec un jeton Copilot ni des secrets de production. Ajoutez par exemple .github/workflows/incident-agent-policy.yml :
name: Validate incident-agent policy
on:
pull_request:
paths:
- "src/IncidentDiagnosticAgent/**"
- "tests/IncidentDiagnosticAgent.Tests/**"
- ".github/workflows/incident-agent-policy.yml"
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x
- run: dotnet restore src/IncidentDiagnosticAgent/IncidentDiagnosticAgent.csproj
- run: dotnet build src/IncidentDiagnosticAgent/IncidentDiagnosticAgent.csproj --configuration Release --no-restore
- run: dotnet test tests/IncidentDiagnosticAgent.Tests/IncidentDiagnosticAgent.Tests.csproj --configuration Release
Cette pipeline possède seulement contents: read. Elle ne reçoit pas de secret Copilot et ne déclenche aucun appel au modèle. Cela permet de tester les règles y compris sur les pull requests issues d’un fork, sans ouvrir un chemin indirect vers une infrastructure interne.
Une campagne d’intégration séparée peut démarrer l’agent dans un conteneur jetable et utiliser un dépôt synthétique. Protégez-la par un environnement GitHub, une identité dédiée et une approbation humaine. Les tests unitaires de la liste blanche restent le contrôle rapide de chaque pull request ; la campagne d’intégration vérifie l’assemblage runtime, l’authentification et les traces.
Points de vigilance
- Le prompt n’est pas une permission. Il complète la politique ; il ne la remplace jamais.
- Une lecture peut divulguer. Montez un répertoire minimal dans un conteneur et anonymisez les logs envoyés au modèle.
allowdoit rester rare. Ajoutez un outil seulement après avoir défini son risque, son périmètre et son test de refus correspondant.- Séparez diagnostic et remédiation. Une proposition de correctif peut être rendue en texte ou en pull request préparée ; le déploiement doit être une action distincte, avec identité et approbation indépendantes.
- Conservez la preuve. Journalisez le nom de l’outil, la décision, l’identifiant de session et le commit analysé. Évitez de journaliser le prompt complet ou la sortie brute si elle peut contenir des secrets.
- Épinglez les dépendances. Les SDK d’agents changent vite ; une mise à jour de package doit repasser par les tests de politique et une démo isolée.
Ressources officielles
Microsoft décrit le fournisseur GitHub Copilot pour Microsoft Agent Framework, notamment AsAIAgent, les sessions et les permissions. Le README .NET du SDK GitHub Copilot documente l’installation de GitHub.Copilot.SDK, les décisions de permission et la télémétrie.
La documentation GitHub sur les hooks du Copilot SDK fournit l’exemple C# de liste blanche utilisé ici, et celle sur les événements de permissions détaille les catégories d’actions demandées par le runtime. Enfin, l’aperçu Microsoft Agent Framework situe cette intégration parmi les options d’agents, d’outils et d’observabilité .NET.
Un agent de diagnostic utile n’a pas besoin de privilèges de production. En partant d’une liste blanche testée, l’équipe obtient d’abord de l’aide pour comprendre un incident, puis décide consciemment quelles capacités supplémentaires méritent une identité, une approbation et un environnement isolé.