Your agents can now draw on skills authored in three different ways – as files on disk, as inline C# code, or as encapsulated classes – 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.
The scenario
You’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 NuGet package – 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 now but won’t be published for another sprint, you write a quick code-defined skill as a bridge. When their NuGet eventually ships, you remove the inline skill and replace it with theirs.
Each of these steps is a real, self-contained improvement to the agent. None of them requires changing how the others work.
Step 1: A file-based skill with script execution
The onboarding guide lives as a skill directory containing a SKILL.md, a Python provisioning-check script, and a reference document with the checklist:
skills/
└── onboarding-guide/
├── SKILL.md
├── scripts/
│ └── check-provisioning.py
└── references/
└── onboarding-checklist.md
---
name: onboarding-guide
description: >-
Walk new hires through their first-week setup checklist. Use when a new
employee asks about system access, required training, or onboarding steps.
---
## Instructions
1. Ask for the employee's name and start date if not already provided.
2. Run the `scripts/check-provisioning.py` script to verify their IT accounts are active.
3. Walk through the steps in the `references/onboarding-checklist.md` reference.
4. Follow up on any incomplete items.
Set up the provider with SubprocessScriptRunner.RunAsync to enable script execution, then wire it into an agent:
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using OpenAI.Responses;
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!;
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
var skillsProvider = new AgentSkillsProvider(
Path.Combine(AppContext.BaseDirectory, "skills"),
SubprocessScriptRunner.RunAsync);
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient()
.AsAIAgent(new ChatClientAgentOptions
{
Name = "HRAgent",
ChatOptions = new() { Instructions = "You are a helpful HR self-service assistant." },
AIContextProviders = [skillsProvider],
},
model: deploymentName);
AgentResponse response = await agent.RunAsync("I just started here. What are the onboarding steps I need to follow?");
Console.WriteLine(response.Text);
The agent discovers the skill, loads it when a new hire’s request matches the description, and runs the provisioning script when it needs to check account status.
SubprocessScriptRunner is responsible for script execution – 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.
Step 2: Adding a class-based skill from NuGet
The HR systems team published Contoso.Skills.HrEnrollment – a NuGet package containing a benefits enrollment skill. Under the hood, class-based skills derive from AgentClassSkill<T> and use [AgentSkillResource] and [AgentSkillScript] attributes so the framework discovers resources and scripts automatically via reflection:
// Inside the Contoso.Skills.HrEnrollment NuGet package
using System.ComponentModel;
using System.Text.Json;
using Microsoft.Agents.AI;
public sealed class BenefitsEnrollmentSkill : AgentClassSkill<BenefitsEnrollmentSkill>
{
public override AgentSkillFrontmatter Frontmatter { get; } = new(
"benefits-enrollment",
"Enroll an employee in health, dental, or vision plans. Use when asked about benefits sign-up, plan options, or coverage changes.");
protected override string Instructions => """
Use this skill when an employee asks about enrolling in or changing their benefits.
1. Read the available-plans resource to review current offerings and pricing.
2. Confirm the plan the employee wants to enroll in.
3. Use the enroll script to complete the enrollment.
""";
[AgentSkillResource("available-plans")]
[Description("Health, dental, and vision plan options with monthly pricing.")]
public string AvailablePlans => """
## Available Plans (2026)
- Health: Basic HMO ($0/month), Premium PPO ($45/month)
- Dental: Standard ($12/month), Enhanced ($25/month)
- Vision: Basic ($8/month)
""";
[AgentSkillScript("enroll")]
[Description("Enrolls an employee in the specified benefit plan. Returns a JSON confirmation.")]
private static string Enroll(string employeeId, string planCode)
{
bool success = HrClient.EnrollInPlan(employeeId, planCode);
return JsonSerializer.Serialize(new { success, employeeId, planCode });
}
}
[AgentSkillResource] 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 IServiceProvider to resolve application services. [AgentSkillScript] can only be applied to methods – use IServiceProvider as a parameter there too when the script needs injected services.
To use this skill alongside the existing file-based one, use AgentSkillsProviderBuilder:
// dotnet add package Contoso.Skills.HrEnrollment
var skillsProvider = new AgentSkillsProviderBuilder()
.UseFileSkill(Path.Combine(AppContext.BaseDirectory, "skills")) // file-based: onboarding guide
.UseSkill(new BenefitsEnrollmentSkill()) // class-based: benefits enrollment skill from NuGet package
.UseFileScriptRunner(SubprocessScriptRunner.RunAsync) // runner for file-based scripts
.Build();
No routing or special logic is needed. Both skills are advertised in the agent’s system prompt, and the agent decides which one to load based on what the employee is asking about.
Step 3: Bridging a gap with an inline code-defined skill
You hear that the HR systems team is also working on a time-off balance skill – but it won’t be published for another sprint and you need it now. Rather than waiting, you define the skill inline in your application code using AgentInlineSkill:
using System.Text.Json;
using Microsoft.Agents.AI;
var timeOffSkill = new AgentInlineSkill(
name: "time-off-balance",
description: "Calculate an employee's remaining vacation and sick days. Use when asked about available time off or leave balances.",
instructions: """
Use this skill when an employee asks how many vacation or sick days they have left.
1. Ask for the employee ID if not already provided.
2. Use the calculate-balance script to get the remaining balance.
3. Present the result clearly, showing both used and remaining days.
""")
.AddScript("calculate-balance", (string employeeId, string leaveType) =>
{
// Temporary implementation – replace with the NuGet skill when available
int totalDays = HrDatabase.GetAnnualAllowance(employeeId, leaveType);
int daysUsed = HrDatabase.GetDaysUsed(employeeId, leaveType);
int remaining = totalDays - daysUsed;
return JsonSerializer.Serialize(new { employeeId, leaveType, totalDays, daysUsed, remaining });
});
Add it to the provider alongside the other skills:
var skillsProvider = new AgentSkillsProviderBuilder()
.UseFileSkill(Path.Combine(AppContext.BaseDirectory, "skills")) // file-based: onboarding guide
.UseSkill(new BenefitsEnrollmentSkill()) // class-based: benefits enrollment skill from NuGet package
.UseSkill(timeOffSkill) // code-defined: temporary bridge
.UseFileScriptRunner(SubprocessScriptRunner.RunAsync) // runner for file-based scripts
.Build();
The agent treats the inline skill exactly like the others. When the NuGet package ships, remove timeOffSkill from the builder and add the class-based version – no other changes required.
AgentInlineSkill supports static and dynamic resources in addition to scripts. Pass a factory delegate to .AddResource() and it’s called each time the agent reads the resource, so you can serve content from a database or configuration system rather than a static string:
var hrPoliciesSkill = new AgentInlineSkill(
name: "hr-policies",
description: "Current HR policies on leave, remote work, and conduct.",
instructions: "Read the policies resource and answer the employee's question.")
.AddResource("policies", () =>
{
// Fetched live so the agent always sees the latest version
return PolicyRepository.GetActivePolicies();
});
The bridging scenario shown here is one use for AgentInlineSkill, but not the only one. Inline skills are also a natural permanent choice when skill definitions need to be generated at runtime from data – constructing a skill per business unit or region from a list, for example – or when a resource or script needs to close over call-site state rather than resolve a service from the DI container. In those cases, a class-based skill has no natural place to hold that state, and inline is simply the better fit.
Step 4: Requiring approval before scripts run
Several scripts in this setup have real consequences: the check-provisioning script queries production infrastructure, and the enroll script writes to the HR system. For a production deployment, requiring human review before any script executes is a reasonable safeguard. Enable it with UseScriptApproval:
var skillsProvider = new AgentSkillsProviderBuilder()
.UseFileSkill(Path.Combine(AppContext.BaseDirectory, "skills")) // file-based: onboarding guide
.UseSkill(new BenefitsEnrollmentSkill()) // class-based: benefits enrollment skill from NuGet package
.UseSkill(timeOffSkill) // code-defined: temporary time-off balance bridge
.UseFileScriptRunner(SubprocessScriptRunner.RunAsync) // runner for file-based scripts
.UseScriptApproval(true)
.Build();
When the agent wants to run a script, it pauses and returns an approval request instead of executing immediately. Your application collects a decision and resumes the agent. For the full approval handling pattern, see Tool approval in the documentation.
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.
What this makes possible
Skills from multiple teams, in one provider. Teams can author and ship skills independently – as file directories in a shared repository or as NuGet packages – and you compose them with AgentSkillsProviderBuilder without coordinating between teams.
Incremental adoption. Start with a single skill and extend the agent’s capabilities by adding more over time. Each addition is self-contained, and the agent figures out which to use.
Move fast with inline skills. When you need skill behavior that is not available yet, AgentInlineSkill lets you write it alongside your application code. It’s easy to remove once the proper package ships – the agent doesn’t know the difference between an inline skill and a class-based one.
Governed automation. 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’t.
Filtering from shared directories. If your organization maintains a shared library of skills and you only want a subset, AgentSkillsProviderBuilder supports predicate-based filtering:
var approvedSkills = new HashSet<string> { "onboarding-guide", "benefits-enrollment" };
var skillsProvider = new AgentSkillsProviderBuilder()
.UseFileSkill(Path.Combine(AppContext.BaseDirectory, "all-skills"))
.UseFilter(skill => approvedSkills.Contains(skill.Frontmatter.Name))
.Build();
Putting it together
Agent Skills in .NET now support three ways to author and deliver skills, with a builder that composes them freely. Whether you’re starting with file-based skills, integrating a third-party package from NuGet, or writing a quick inline bridge while a proper package is on its way, the same provider wires everything together for the agent – and script approval gives you the oversight you need for actions that matter.
The post Agent Skills in .NET: Three Ways to Author, One Provider to Run Them appeared first on Microsoft Agent Framework.