On 7 July 2026, Microsoft shipped the stable release of Agent Skills for .NET in the Microsoft Agent Framework. The [Experimental] attribute is gone, the API surface is production-ready, and the feature that was previewed at Build 2026 (3 June) is now something you can put in front of an architecture review board without an asterisk.
If you have been bolting domain knowledge onto agents by stuffing it into system prompts — or by writing yet another C# function tool that returns a wall of policy text — Agent Skills is the piece you were missing. In Microsoft’s words, it gives you “a standard way to package, distribute, and govern domain expertise for your agents.” This post covers what skills actually are, the three ways to author them in C#, and the governance controls (human approval included) that make them viable in an enterprise.
If you are new to the framework itself, start with the Microsoft Agent Framework developer’s guide first — this post assumes you know what an AIAgent is.
What Agent Skills are (and what they are not)
An agent skill is an open format for packaging domain expertise that agents discover and use on demand. Concretely, a skill bundles three things:
- Instructions — a markdown document telling the agent how to perform a task in your domain.
- Resources — reference documents, templates, and data files the agent can read when needed.
- Scripts — executable code the agent can run, under your control.
The format follows the open Agent Skills specification, so skills you author are not locked to one framework or vendor.
The critical distinction from function tools: a tool is a single callable capability (“get the weather”), while a skill is a procedure — the checklist, the policy, the edge cases, and the helper scripts that let an agent do a real job like “file an expense report correctly.” Skills do not replace tool calling or MCP; they sit above them, teaching the agent when and how to use everything else.
Progressive disclosure: why this beats prompt stuffing
The reason skills scale where system prompts do not is a four-stage progressive disclosure pattern:
- Advertise (~100 tokens per skill) — only skill names and descriptions are injected into the system prompt at the start of a run.
- Load (under 5,000 tokens recommended) — when a task matches, the agent calls the
load_skilltool to pull in the full instructions. - Read resources — the agent calls
read_skill_resourceto fetch reference files only when required. - Run scripts — the agent calls
run_skill_scriptto execute bundled code.
An agent with thirty skills pays roughly 3,000 tokens of standing overhead, not 150,000. That is a direct lever on latency and spend — the same principle behind every technique in cost control for production agents.
Anatomy of a file-based skill
The simplest skill is a directory with a SKILL.md:
expense-report/
├── SKILL.md # Required - frontmatter + instructions
├── scripts/
│ └── validate.py # Executable code agents can run
├── references/
│ └── POLICY_FAQ.md # Reference documents loaded on demand
└── assets/
└── expense-report-template.md # Templates and static resources
SKILL.md is YAML frontmatter plus markdown instructions:
---
name: expense-report
description: File and validate employee expense reports according to company policy. Use when asked about expense submissions, reimbursement rules, or spending limits.
license: Apache-2.0
compatibility: Requires python3
metadata:
author: contoso-finance
version: "2.1"
---
Two fields are required. name (max 64 characters, lowercase letters, numbers, and hyphens, matching the directory name) and description (max 1,024 characters). The description does double duty as your “routing prompt” — it is what the model sees at the advertise stage, so write it the way you would write a good tool description: what the skill does and when to use it. Keep the instruction body under 500 lines and push detail into references/.
By default the provider discovers resources with extensions like .md, .json, .yaml, .csv, and .txt, and scripts with .py, .js, .sh, .ps1, .cs, and .csx, searching up to two levels deep per skill directory.
Wiring skills into an agent
AgentSkillsProvider is a context provider: it advertises available skills in the system prompt and registers the three skill tools. Point it at a directory, hand it a script runner, and add it to AIContextProviders:
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 = "SkillsAgent",
ChatOptions = new() { Instructions = "You are a helpful assistant." },
AIContextProviders = [skillsProvider],
},
model: deploymentName);
AgentResponse response = await agent.RunAsync("Help me with onboarding.");
Console.WriteLine(response.Text);
Every subdirectory containing a SKILL.md is discovered automatically, and you can pass multiple root paths (say, company-skills and team-skills) to one provider. Skills are resolved once and cached for reuse across runs.
Note the second constructor argument: file-based scripts do not execute inside your process by magic. They delegate to a script runner you provide — SubprocessScriptRunner.RunAsync is the built-in option, and the seam where you plug in your own sandboxing. That design choice matters more than it looks; more on it below.
Three ways to author, one provider to run them
The stable release supports three authoring styles, and the agent treats them identically at runtime.
1. File-based skills
The directory format above. Best for skills owned by domain experts — finance, HR, support — who can edit markdown in a shared repo without touching your codebase. This is also the interoperable format: it is portable across anything that speaks the agentskills.io spec.
2. Class-based skills
C# classes deriving from AgentClassSkill<T>, with resources and scripts declared as attributed members:
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 enrollment or plan changes.");
protected override string Instructions => """
1. Ask which plan type the employee wants.
2. Read the 'available-plans' resource to list options.
3. Run the 'enroll' script with the employee ID and plan code.
""";
[AgentSkillResource("available-plans")]
[Description("Health, dental, and vision plan options.")]
public string AvailablePlans => LoadPlansFromConfig();
[AgentSkillScript("enroll")]
[Description("Enrolls an employee in the specified benefit plan.")]
private static string Enroll(string employeeId, string planCode)
=> EnrollmentService.Enroll(employeeId, planCode);
}
This is the style I would push for platform teams: skills become ordinary .NET types you can unit test, code review, version with SemVer, and ship on an internal NuGet feed. Distribution and governance ride on infrastructure you already have.
3. Code-defined skills
AgentInlineSkill for skills built inline — handy for dynamic or configuration-driven scenarios:
var timeOffSkill = new AgentInlineSkill(
name: "time-off-balance",
description: "Calculate an employee's remaining vacation and sick days.",
instructions: "Use this skill when an employee asks how many vacation or sick days they have left.")
.AddScript("calculate-balance", (string employeeId, string leaveType) =>
{
int remaining = GetTotalDays(employeeId, leaveType) - GetDaysUsed(employeeId, leaveType);
return JsonSerializer.Serialize(new { remaining });
});
Composing sources with the builder
AgentSkillsProviderBuilder aggregates all three into a single provider, with deduplication and caching applied:
var skillsProvider = new AgentSkillsProviderBuilder()
.UseFileSkill(Path.Combine(AppContext.BaseDirectory, "skills"))
.UseSkill(new BenefitsEnrollmentSkill())
.UseSkill(timeOffSkill)
.UseFileScriptRunner(SubprocessScriptRunner.RunAsync)
.Build();
This is the distribution story in one snippet: the finance team maintains markdown in a repo, the platform team ships a NuGet package, your app adds an inline skill — and no one coordinates a release with anyone else.
Governance: approvals are the default, not an add-on
Here is the design decision that tells you this feature was built for enterprises: all three skill tools — load_skill, read_skill_resource, and run_skill_script — require human approval by default. An agent cannot silently load instructions, read bundled files, or execute a script. When it tries, the run pauses and returns a ToolApprovalRequestContent instead of executing.
You then loosen the policy deliberately, in one of three ways.
Auto-approval rules for read-only tools
The pragmatic production default: auto-approve the read-only tools, keep a human on script execution.
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient()
.AsAIAgent(new ChatClientAgentOptions
{
Name = "SkillsAgent",
ChatOptions = new() { Instructions = "You are a helpful assistant." },
AIContextProviders = [skillsProvider],
},
model: deploymentName)
.AsBuilder()
.UseToolApproval(new ToolApprovalAgentOptions
{
// Auto-approve load_skill and read_skill_resource.
// run_skill_script still requires explicit user approval.
AutoApprovalRules = [AgentSkillsProvider.ReadOnlyToolsAutoApprovalRule],
})
.Build();
AgentSkillsProvider.AllToolsAutoApprovalRule exists if you want everything auto-approved. Usefully, these rules are scoped so they never auto-approve a same-named hosted tool from another provider.
Opting tools out entirely
AgentSkillsProviderOptions removes individual tools from the approval flow altogether:
var skillsProvider = new AgentSkillsProvider(
Path.Combine(AppContext.BaseDirectory, "skills"),
SubprocessScriptRunner.RunAsync,
options: new AgentSkillsProviderOptions
{
DisableLoadSkillApproval = true,
DisableReadSkillResourceApproval = true,
// DisableRunSkillScriptApproval remains false - scripts still gated
});
The human-in-the-loop approval loop
When approval is required, you surface the request, collect a decision, and resume the session:
AgentSession session = await agent.CreateSessionAsync();
AgentResponse response = await agent.RunAsync("Convert 26.2 miles to kilometers", session);
List<ToolApprovalRequestContent> approvalRequests = response.Messages
.SelectMany(m => m.Contents)
.OfType<ToolApprovalRequestContent>()
.ToList();
while (approvalRequests.Count > 0)
{
List<ChatMessage> userInputResponses = approvalRequests
.ConvertAll(request =>
{
var toolCall = (FunctionCallContent)request.ToolCall;
Console.WriteLine($"Approve {toolCall.Name}? (Y/N)");
bool approved = Console.ReadLine()?.Equals("Y", StringComparison.OrdinalIgnoreCase) ?? false;
return new ChatMessage(ChatRole.User, [request.CreateResponse(approved)]);
});
response = await agent.RunAsync(userInputResponses, session);
approvalRequests = response.Messages
.SelectMany(m => m.Contents)
.OfType<ToolApprovalRequestContent>()
.ToList();
}
In a real service, that Console.ReadLine becomes a Teams card, a ServiceNow task, or an approval endpoint — the pattern is the same. This slots directly into the broader threat model covered in securing AI agents in .NET.
Filtering: curate what agents can even see
Governance is not only about execution — it is also about exposure. The builder supports predicate-based filtering so a directory full of skills can be narrowed per agent or per tenant:
var approvedSkills = new HashSet<string> { "onboarding-guide", "benefits-enrollment" };
var skillsProvider = new AgentSkillsProviderBuilder()
.UseFileSkill(Path.Combine(AppContext.BaseDirectory, "all-skills"))
.UseFilter((skill, context) => approvedSkills.Contains(skill.Frontmatter.Name))
.Build();
Combine that with the pluggable script runner and you have three independent control planes: what skills exist, which the agent can see, and what it may execute.
Skills and MCP
Skills complement rather than compete with the Model Context Protocol. In fact, the framework can discover skills from MCP servers via UseMcpSkills in the Microsoft.Agents.AI.Mcp package (this MCP-skills API is still experimental, unlike the core release). One security detail worth knowing: scripts bundled inside archive-type skills pulled from remote MCP servers are never executed — executable content from a remote server requires explicit trust. That is exactly the right default.
Rule of thumb: use MCP servers to give agents access to systems, and skills to give them competence in your domain — the procedures, policies, and judgment for using that access well.
My take: what to do with this
Agent Skills is the first Microsoft Agent Framework feature aimed squarely at the organizational problem of agents, not the technical one. Prompts do not scale across teams; NuGet packages and markdown directories in a repo do. If I were rolling this out:
- Start file-based. Extract the domain guidance currently buried in your system prompts into two or three
SKILL.mdfiles owned by the people who own the knowledge. - Promote shared skills to class-based packages on your internal feed once they stabilize, so they get versioning, review, and tests like any other dependency.
- Keep
run_skill_scriptbehind approval until you have evaluation coverage proving the agent uses scripts correctly — then relax per skill, not globally. Bake the approval transcript into your audit logs, and treat skill changes as behavior changes worth a regression pass in your agent testing pipeline.
The official samples live in the framework repo under dotnet/samples/02-agents/AgentSkills, and the format specification is at agentskills.io. The API is stable as of 7 July 2026 — this is a good week to try it.
Have a correction or a topic you want covered? Email mani.bc72@gmail.com.