{"id":5246,"date":"2026-04-13T02:49:20","date_gmt":"2026-04-13T09:49:20","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/agent-framework\/?p=5246"},"modified":"2026-07-06T02:52:02","modified_gmt":"2026-07-06T09:52:02","slug":"agent-skills-in-net-three-ways-to-author-one-provider-to-run-them","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/agent-framework\/agent-skills-in-net-three-ways-to-author-one-provider-to-run-them\/","title":{"rendered":"Agent Skills in .NET: Three Ways to Author, One Provider to Run Them"},"content":{"rendered":"<p>Your agents can now draw on skills authored in three different ways \u2013 as files on disk, as inline C# code, or as encapsulated classes \u2013 and combine them freely in a single provider. Add built-in script execution support and a human-approval mechanism for script calls, and you have a practical authoring model that fits real-world scenarios: skills that evolve over time, skills owned by different teams, and scripts that need human oversight before they act on your systems.<\/p>\n<h2>The scenario<\/h2>\n<p>You&#39;re building an HR self-service agent for your company. It starts life with a single file-based skill that walks new hires through their onboarding checklist. A few weeks in, the HR systems team ships a benefits enrollment skill as a package on your internal NuGet feed \u2013 you want to add it alongside the onboarding skill without rewriting anything. And when you learn the same team is working on a time-off balance skill that you need <em>now<\/em> but won&#39;t be published for another sprint, you write a quick code-defined skill as a bridge. When their package eventually ships, you remove the inline skill and replace it with theirs.<\/p>\n<p>Each of these steps is a real, self-contained improvement to the agent. None of them requires changing how the others work.<\/p>\n<h2>Step 1: A file-based skill with script execution<\/h2>\n<p>The onboarding guide lives as a skill directory containing a <code>SKILL.md<\/code>, a Python provisioning-check script, and a reference document with the checklist:<\/p>\n<pre class=\"wp-block-code\"><code>skills\/\r\n\u2514\u2500\u2500 onboarding-guide\/\r\n    \u251c\u2500\u2500 SKILL.md\r\n    \u251c\u2500\u2500 scripts\/\r\n    \u2502   \u2514\u2500\u2500 check-provisioning.py\r\n    \u2514\u2500\u2500 references\/\r\n        \u2514\u2500\u2500 onboarding-checklist.md<\/code><\/pre>\n<pre class=\"wp-block-code\"><code class=\"language-yaml\">---\r\nname: onboarding-guide\r\ndescription: &gt;-\r\n  Walk new hires through their first-week setup checklist. Use when a new\r\n  employee asks about system access, required training, or onboarding steps.\r\n---\r\n\r\n## Instructions\r\n\r\n1. Ask for the employee's name and start date if not already provided.\r\n2. Run the `scripts\/check-provisioning.py` script to verify their IT accounts are active.\r\n3. Walk through the steps in the `references\/onboarding-checklist.md` reference.\r\n4. Follow up on any incomplete items.<\/code><\/pre>\n<p>Set up the provider with <code>SubprocessScriptRunner.RunAsync<\/code> to enable script execution, then wire it into an agent:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-csharp\">using Azure.AI.OpenAI;\r\nusing Azure.Identity;\r\nusing Microsoft.Agents.AI;\r\nusing OpenAI.Responses;\r\n\r\nstring endpoint = Environment.GetEnvironmentVariable(&quot;AZURE_OPENAI_ENDPOINT&quot;)!;\r\nstring deploymentName = Environment.GetEnvironmentVariable(&quot;AZURE_OPENAI_DEPLOYMENT_NAME&quot;) ?? &quot;gpt-4o-mini&quot;;\r\n\r\nvar skillsProvider = new AgentSkillsProvider(\r\n    Path.Combine(AppContext.BaseDirectory, &quot;skills&quot;),\r\n    SubprocessScriptRunner.RunAsync);\r\n\r\nAIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())\r\n    .GetResponsesClient()\r\n    .AsAIAgent(new ChatClientAgentOptions\r\n    {\r\n        Name = &quot;HRAgent&quot;,\r\n        ChatOptions = new() { Instructions = &quot;You are a helpful HR self-service assistant.&quot; },\r\n        AIContextProviders = [skillsProvider],\r\n    },\r\n    model: deploymentName);\r\n\r\nAgentResponse response = await agent.RunAsync(&quot;I just started here. What are the onboarding steps I need to follow?&quot;);\r\nConsole.WriteLine(response.Text);<\/code><\/pre>\n<p>The agent discovers the skill, loads it when a new hire&#39;s request matches the description, and runs the provisioning script when it needs to check account status.<\/p>\n<p><code>SubprocessScriptRunner<\/code> is responsible for script execution &#8211; it receives the skill, script, and arguments and runs the script accordingly. For production, use a runner that adds sandboxing, resource limits, input validation, and audit logging.<\/p>\n<h2>Step 2: Adding a class-based skill from an internal package<\/h2>\n<p>The HR systems team published <code>Contoso.Skills.HrEnrollment<\/code> to the company&#8217;s internal NuGet feed &#8211; a package containing a benefits enrollment skill. Under the hood, class-based skills derive from <code>AgentClassSkill&lt;T&gt;<\/code> and use <code>[AgentSkillResource]<\/code> and <code>[AgentSkillScript]<\/code> attributes so the framework discovers resources and scripts automatically via reflection:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-csharp\">\/\/ Inside the Contoso.Skills.HrEnrollment package\r\nusing System.ComponentModel;\r\nusing System.Text.Json;\r\nusing Microsoft.Agents.AI;\r\n\r\npublic sealed class BenefitsEnrollmentSkill : AgentClassSkill&lt;BenefitsEnrollmentSkill&gt;\r\n{\r\n    public override AgentSkillFrontmatter Frontmatter { get; } = new(\r\n        &quot;benefits-enrollment&quot;,\r\n        &quot;Enroll an employee in health, dental, or vision plans. Use when asked about benefits sign-up, plan options, or coverage changes.&quot;);\r\n\r\n    protected override string Instructions =&gt; &quot;&quot;&quot;\r\n        Use this skill when an employee asks about enrolling in or changing their benefits.\r\n        1. Read the available-plans resource to review current offerings and pricing.\r\n        2. Confirm the plan the employee wants to enroll in.\r\n        3. Use the enroll script to complete the enrollment.\r\n        &quot;&quot;&quot;;\r\n\r\n    [AgentSkillResource(&quot;available-plans&quot;)]\r\n    [Description(&quot;Health, dental, and vision plan options with monthly pricing.&quot;)]\r\n    public string AvailablePlans =&gt; &quot;&quot;&quot;\r\n        ## Available Plans (2026)\r\n        - Health: Basic HMO ($0\/month), Premium PPO ($45\/month)\r\n        - Dental: Standard ($12\/month), Enhanced ($25\/month)\r\n        - Vision: Basic ($8\/month)\r\n        &quot;&quot;&quot;;\r\n\r\n    [AgentSkillScript(&quot;enroll&quot;)]\r\n    [Description(&quot;Enrolls an employee in the specified benefit plan. Returns a JSON confirmation.&quot;)]\r\n    private static string Enroll(string employeeId, string planCode)\r\n    {\r\n        bool success = HrClient.EnrollInPlan(employeeId, planCode);\r\n        return JsonSerializer.Serialize(new { success, employeeId, planCode });\r\n    }\r\n}<\/code><\/pre>\n<p><code>[AgentSkillResource]<\/code> can be applied to a property or a method. Use a method when the resource content needs to be computed at read time, or when the method needs to accept an <code>IServiceProvider<\/code> to resolve application services. <code>[AgentSkillScript]<\/code> can only be applied to methods \u2013 use <code>IServiceProvider<\/code> as a parameter there too when the script needs injected services.<\/p>\n<p>To use this skill alongside the existing file-based one, use <code>AgentSkillsProviderBuilder<\/code>:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-csharp\">\/\/ dotnet add package Contoso.Skills.HrEnrollment\r\nvar skillsProvider = new AgentSkillsProviderBuilder()\r\n    .UseFileSkill(Path.Combine(AppContext.BaseDirectory, &quot;skills&quot;))  \/\/ file-based: onboarding guide\r\n    .UseSkill(new BenefitsEnrollmentSkill())                         \/\/ class-based: benefits enrollment skill from internal feed\r\n    .UseFileScriptRunner(SubprocessScriptRunner.RunAsync)            \/\/ runner for file-based scripts\r\n    .Build();<\/code><\/pre>\n<p>No routing or special logic is needed. Both skills are advertised in the agent&#39;s system prompt, and the agent decides which one to load based on what the employee is asking about.<\/p>\n<h2>Step 3: Bridging a gap with an inline code-defined skill<\/h2>\n<p>You hear that the HR systems team is also working on a time-off balance skill \u2013 but it won&#39;t be published for another sprint and you need it now. Rather than waiting, you define the skill inline in your application code using <code>AgentInlineSkill<\/code>:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-csharp\">using System.Text.Json;\r\nusing Microsoft.Agents.AI;\r\n\r\nvar timeOffSkill = new AgentInlineSkill(\r\n    name: &quot;time-off-balance&quot;,\r\n    description: &quot;Calculate an employee&#39;s remaining vacation and sick days. Use when asked about available time off or leave balances.&quot;,\r\n    instructions: &quot;&quot;&quot;\r\n        Use this skill when an employee asks how many vacation or sick days they have left.\r\n        1. Ask for the employee ID if not already provided.\r\n        2. Use the calculate-balance script to get the remaining balance.\r\n        3. Present the result clearly, showing both used and remaining days.\r\n        &quot;&quot;&quot;)\r\n    .AddScript(&quot;calculate-balance&quot;, (string employeeId, string leaveType) =&gt;\r\n    {\r\n        \/\/ Temporary implementation \u2013 replace with the official package when available\r\n        int totalDays = HrDatabase.GetAnnualAllowance(employeeId, leaveType);\r\n        int daysUsed = HrDatabase.GetDaysUsed(employeeId, leaveType);\r\n        int remaining = totalDays - daysUsed;\r\n        return JsonSerializer.Serialize(new { employeeId, leaveType, totalDays, daysUsed, remaining });\r\n    });<\/code><\/pre>\n<p>Add it to the provider alongside the other skills:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-csharp\">var skillsProvider = new AgentSkillsProviderBuilder()\r\n    .UseFileSkill(Path.Combine(AppContext.BaseDirectory, &quot;skills&quot;))  \/\/ file-based: onboarding guide\r\n    .UseSkill(new BenefitsEnrollmentSkill())                         \/\/ class-based: benefits enrollment skill from internal feed\r\n    .UseSkill(timeOffSkill)                                          \/\/ code-defined: temporary bridge\r\n    .UseFileScriptRunner(SubprocessScriptRunner.RunAsync)            \/\/ runner for file-based scripts\r\n    .Build();<\/code><\/pre>\n<p>The agent treats the inline skill exactly like the others. When the official package ships, remove <code>timeOffSkill<\/code> from the builder and add the class-based version \u2013 no other changes required.<\/p>\n<p><code>AgentInlineSkill<\/code> also fits naturally when you need resources that execute logic at read time rather than serving static content, when skill definitions must be constructed at runtime from data (for example, a skill per business unit or region from a configuration list), or when a resource or script needs to close over call-site state rather than resolve a service from the DI container.<\/p>\n<h2>Step 4: Requiring approval before scripts run<\/h2>\n<p>Several scripts in this setup have real consequences: the <code>check-provisioning<\/code> script queries production infrastructure, and the <code>enroll<\/code> script writes to the HR system. All tools exposed by <code>AgentSkillsProvider<\/code> \u2013 <code>load_skill<\/code>, <code>read_skill_resource<\/code>, and <code>run_skill_script<\/code> \u2013 <strong>require approval by default<\/strong>. When the agent wants to call a skill tool, it pauses and returns an approval request instead of executing immediately.<\/p>\n<p>Your application collects a decision for each approval request and resumes the agent. When approved, the script executes and the agent continues its response. When rejected, the agent is informed and can explain why the action was not taken. For the full approval handling pattern, see <a href=\"https:\/\/learn.microsoft.com\/en-us\/agent-framework\/agents\/tools\/tool-approval?pivots=programming-language-csharp\">Tool approval<\/a> in the documentation.<\/p>\n<h2>What this makes possible<\/h2>\n<p><strong>Skills from multiple teams, in one provider.<\/strong> Teams can author and ship skills independently \u2013 as file directories in a shared repository or as packages on an internal NuGet feed \u2013 and you compose them with <code>AgentSkillsProviderBuilder<\/code> without coordinating between teams.<\/p>\n<p><strong>Incremental adoption.<\/strong> Start with a single skill and extend the agent&#39;s capabilities by adding more over time. Each addition is self-contained, and the agent figures out which to use.<\/p>\n<p><strong>Move fast with inline skills.<\/strong> When you need skill behavior that is not available yet, <code>AgentInlineSkill<\/code> lets you write it alongside your application code. It&#39;s easy to remove once the proper package ships \u2013 the agent doesn&#39;t know the difference between an inline skill and a class-based one.<\/p>\n<p><strong>Governed automation.<\/strong> Script approval gives you a human-in-the-loop checkpoint before any script with side effects runs. In regulated industries or sensitive production environments, this can be the difference between an agent that your organization trusts and one it doesn&#39;t.<\/p>\n<p><strong>Filtering from shared directories.<\/strong> If your organization maintains a shared library of skills and you only want a subset, <code>AgentSkillsProviderBuilder<\/code> supports predicate-based filtering:<\/p>\n<pre class=\"wp-block-code\"><code class=\"language-csharp\">var approvedSkills = new HashSet&lt;string&gt; { &quot;onboarding-guide&quot;, &quot;benefits-enrollment&quot; };\r\n\r\nvar skillsProvider = new AgentSkillsProviderBuilder()\r\n    .UseFileSkill(Path.Combine(AppContext.BaseDirectory, &quot;all-skills&quot;))\r\n    .UseFilter((skill, context) =&gt; approvedSkills.Contains(skill.Frontmatter.Name))\r\n    .Build();<\/code><\/pre>\n<h2>Putting it together<\/h2>\n<p>Agent Skills in .NET now support three ways to author and deliver skills, with a builder that composes them freely. Whether you&#39;re starting with file-based skills, integrating a class-based skill from an internal package feed, or writing a quick inline bridge while a proper package is on its way, the same provider wires everything together for the agent \u2013 and script approval gives you the oversight you need for actions that matter.<\/p>\n<ul>\n<li>\ud83d\udcd6 <a href=\"https:\/\/learn.microsoft.com\/en-us\/agent-framework\/agents\/skills\">Agent Skills documentation on Microsoft Learn<\/a><\/li>\n<li>\ud83d\udcbb <a href=\"https:\/\/github.com\/microsoft\/agent-framework\/tree\/main\/dotnet\/samples\/02-agents\/AgentSkills\">.NET samples on GitHub<\/a><\/li>\n<li>\ud83d\udde3\ufe0f <a href=\"https:\/\/github.com\/microsoft\/agent-framework\/discussions\">GitHub Discussions<\/a> \u2013 share feedback and connect with the community<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Your agents can now draw on skills authored in three different ways \u2013 as files on disk, as inline C# code, or as encapsulated classes \u2013 and combine them freely in a single provider. Add built-in script execution support and a human-approval mechanism for script calls, and you have a practical authoring model that fits [&hellip;]<\/p>\n","protected":false},"author":157200,"featured_media":5169,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[78,143,145],"tags":[],"class_list":["post-5246","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-net","category-agent-framework","category-agent-skills"],"acf":[],"blog_post_summary":"<p>Your agents can now draw on skills authored in three different ways \u2013 as files on disk, as inline C# code, or as encapsulated classes \u2013 and combine them freely in a single provider. Add built-in script execution support and a human-approval mechanism for script calls, and you have a practical authoring model that fits [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts\/5246","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/users\/157200"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/comments?post=5246"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts\/5246\/revisions"}],"predecessor-version":[{"id":5589,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/posts\/5246\/revisions\/5589"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/media\/5169"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/media?parent=5246"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/categories?post=5246"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/agent-framework\/wp-json\/wp\/v2\/tags?post=5246"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}