Skip to main content

Agent Skills in the GitHub Copilot SDK

Up to this point in the series, we've extended the agent's capabilities in two ways: custom C# tools that execute code, and MCP servers that connect to external systems. Both are about what the agent can do. Skills are about what the agent knows and more specifically, how to apply that knowledge in a repeatable, composable way.

A skill is a folder containing a SKILL.md file with specialised instructions, and optionally supporting files like scripts, templates, or examples. You point the SDK at a directory, the agent scans for skills automatically, and when a task matches a skill's description, that skill's instructions are injected into the session context. The agent gains focused expertise — without you writing any C#, and without redeploying your application.

Why skills exist

Think about a development task you repeat across projects: scaffolding a solution, writing a particular kind of test, generating API documentation in a house style, following a specific migration pattern. The first time you do it with an AI assistant, you spend real effort crafting the right prompt, correcting the model, and iterating until it gets things right.

The second time, you do it all again.

Skills solve this by letting you encode that effort once, in a file, and make it available to any agent that loads the skill directory. They're reusable, versionable, shareable across teams, and can be used across agents. An open standard that works across multiple AI agents. A skill you write for the Copilot SDK works in the CLI, works in VS Code, and works in any future agent that adopts the standard.

This is the key distinction from custom instructions, which are applied globally to a session. Skills are selective: the agent decides when a skill is relevant based on the task at hand, and injects it only when needed. You can have dozens of skills without bloating every session's context.

Anatomy of a skill

Every skill lives in its own subdirectory. The only required file is SKILL.md. Supporting files — scripts, templates, example outputs — can live alongside it and be referenced from the instructions.

skills/
├── dotnet-clean-architecture/
│   ├── SKILL.md
│   ├── solution-template.md
│   └── scripts/
│       └── scaffold.sh
├── api-documentation/
│   ├── SKILL.md
│   └── examples/
│       └── endpoint-doc-example.md
└── security-review/
    └── SKILL.md

The SKILL.md file has two parts: a YAML frontmatter block that identifies the skill, and a body that contains the instructions the agent follows when the skill is active.

---
name: dotnet-clean-architecture
description: >
  Scaffolds .NET solutions using Clean Architecture. Use when creating new 
  .NET projects, solutions, or service templates. Triggers on: clean architecture, 
  solution scaffold, new .NET project, DDD, domain-driven design.
---

# .NET Clean Architecture Scaffolder

You are an expert .NET architect. When scaffolding a solution, always follow these conventions:

## Project Structure

Every solution must have these layers:
- `src/{SolutionName}.Domain` — entities, value objects, domain events, no dependencies
- `src/{SolutionName}.Application` — use cases, commands, queries, CQRS, depends only on Domain
- `src/{SolutionName}.Infrastructure` — EF Core, external services, implements Application interfaces
- `src/{SolutionName}.API` — ASP.NET Core, controllers or minimal API, depends on Application

## Conventions

- Use record types for value objects and DTOs
- Commands and queries must implement MediatR's `IRequest<T>`
- Repository interfaces live in Application, implementations in Infrastructure
- Never reference Infrastructure from Application or Domain

## Script

Run `./scripts/scaffold.sh {SolutionName}` to generate the folder structure before writing any code.

The frontmatter name is how you reference the skill explicitly (more on that below). The description is what the agent uses to decide whether a skill is relevant — think of it as a semantic index. The more precise your description, the more accurately the agent will match it to appropriate prompts. Put all "when to use" guidance in the description, never in the body — the body is for instructions the agent follows, not for routing decisions.

Loading skills

Skills are registered on SessionConfig via SkillDirectories. The SDK scans every subdirectory of each path for SKILL.md files automatically — you don't enumerate them individually:

var session = await client.CreateSessionAsync(new SessionConfig
{
    Model = "gpt-4.1",
    SkillDirectories = ["./skills"]  // loads all SKILL.md files found under ./skills
});

That's the entire integration. Every skill in the directory tree becomes available to the session.

You can load from multiple directories, which is useful for separating project-specific skills from shared organisational ones:

var session = await client.CreateSessionAsync(new SessionConfig
{
    Model = "gpt-4.1",
    SkillDirectories =
    [
        "./skills",                           // project-specific skills
        Path.Combine(homeDir, ".copilot/skills")  // personal skills, shared across projects
    ]
});

The SDK also supports system-level skill directories following the same conventions as the CLI: ~/.copilot/skills for personal skills shared across projects, or .github/skills within a repository for project-specific ones.

How the agent selects skills

When a user sends a prompt, the agent evaluates the prompt against the description of every loaded skill. If a skill's description is a semantic match, the skill's SKILL.md content is injected into the session context and the agent follows its instructions.

This happens automatically — the user doesn't need to know skills exist. A developer asking "scaffold a new service for order processing using clean architecture" will trigger the dotnet-clean-architecture skill without typing anything special.

If you want to invoke a skill explicitly, prefix the skill name with a forward slash in the prompt:

/dotnet-clean-architecture Create a new service for handling payment processing

This bypasses the automatic matching and forces the skill into context regardless of whether the description would have matched organically. It's useful for testing and for cases where users have learned which skill they want.

Disabling skills per session

A skill in the directory is available to every session. Sometimes you want to suppress a skill for a specific session — a different workflow, a different team, or a controlled experiment comparing behaviour with and without a skill active.

You can disable skills by name without deleting the file:

var session = await client.CreateSessionAsync(new SessionConfig
{
    Model = "gpt-4.1",
    SkillDirectories = ["./skills"],
    DisabledSkills = ["security-review"]  // suppressed for this session only
});

DisabledSkills takes the name from the skill's frontmatter. The file stays on disk; the skill simply isn't offered to the agent in this session.

Creating skills at runtime

Skills don't have to exist on disk when your application starts. Because skills are just files, you can generate them dynamically — from a database, a configuration API, or user input — and write them to a temporary directory before creating the session:

public async Task<CopilotSession> CreateSessionWithDynamicSkillAsync(
    string skillName,
    string skillDescription,
    string skillInstructions)
{
    // Write the skill to a temp directory
    var skillDir = Path.Combine(Path.GetTempPath(), "copilot-skills", skillName);
    Directory.CreateDirectory(skillDir);

    var skillContent = $"""
        ---
        name: {skillName}
        description: {skillDescription}
        ---

        {skillInstructions}
        """;

    await File.WriteAllTextAsync(Path.Combine(skillDir, "SKILL.md"), skillContent);

    return await _client.CreateSessionAsync(new SessionConfig
    {
        Model = "gpt-4.1",
        SkillDirectories = [Path.GetDirectoryName(skillDir)!]
    });
}

This opens up patterns like tenant-specific skills in a multi-tenant application, user-customisable agent behaviour stored in a database, or skills assembled from configuration without a redeployment. Because skills are files, they're composable, testable, and can be version-controlled independently of your application code.

What's next

Skills give your agent reusable, portable expertise that can be authored independently of application code, shared across teams, and updated without redeployment. Combined with custom C# tools and MCP servers, you now have three complementary ways to shape what your agent can do and how it behaves.

In the next post we’ll cover multi-agent patterns — how to use the Microsoft Agent Framework to compose Copilot SDK agents with other agents, orchestrate specialist agents for complex workflows, and think about the boundaries between what each agent owns.

Popular posts from this blog

Podman– Command execution failed with exit code 125

After updating WSL on one of the developer machines, Podman failed to work. When we took a look through Podman Desktop, we noticed that Podman had stopped running and returned the following error message: Error: Command execution failed with exit code 125 Here are the steps we tried to fix the issue: We started by running podman info to get some extra details on what could be wrong: >podman info OS: windows/amd64 provider: wsl version: 5.3.1 Cannot connect to Podman. Please verify your connection to the Linux system using `podman system connection list`, or try `podman machine init` and `podman machine start` to manage a new Linux VM Error: unable to connect to Podman socket: failed to connect: dial tcp 127.0.0.1:2655: connectex: No connection could be made because the target machine actively refused it. That makes sense as the podman VM was not running. Let’s check the VM: >podman machine list NAME         ...

Azure DevOps/ GitHub emoji

I’m really bad at remembering emoji’s. So here is cheat sheet with all emoji’s that can be used in tools that support the github emoji markdown markup: All credits go to rcaviers who created this list.

VS Code Planning mode

After the introduction of Plan mode in Visual Studio , it now also found its way into VS Code. Planning mode, or as I like to call it 'Hannibal mode', extends GitHub Copilot's Agent Mode capabilities to handle larger, multi-step coding tasks with a structured approach. Instead of jumping straight into code generation, Planning mode creates a detailed execution plan. If you want more details, have a look at my previous post . Putting plan mode into action VS Code takes a different approach compared to Visual Studio when using plan mode. Instead of a configuration setting that you can activate but have limited control over, planning is available as a separate chat mode/agent: I like this approach better than how Visual Studio does it as you have explicit control when plan mode is activated. Instead of immediately diving into execution, the plan agent creates a plan and asks some follow up questions: You can further edit the plan by clicking on ‘Open in Editor’: ...