4. Skills

Questions

  • What is a skill, and what problem does it solve?

  • How does an agent decide when to load a skill?

  • How do skills differ from system prompts and MCP tools?

Objectives

  • Understand the structure of a SKILL.md file.

  • Understand which parts of a skill end up in the LLM’s context, and when.

  • Add a skill to the agent and write your own.

Why skills?

In the previous lessons we have covered tools and made them available via an MCP server. Our agent can now look up the date, search for flights, and book them with the book_flight tool from Exercise 3.2. However, as the MCP tools are general, they are not customized to how we want to use the tools. For example, company rules, conventions, and workflows are not included in the context of the LLM when interacting with it. There are multiple options for including these instructions in the context:

  1. Include everything in the system prompt of our harness but then the context will grow with every customization even if it is not relevant for the current interaction. For example, detailed train preferences are not relevant when searching flights.

  2. Another option is to write a new tool. But that is overkill for something that just includes natural language and everyone should be able to modify even when they do not have any coding experience.

Skills are a compromise between both: They are packaged instructions in natural language files, of which only the name and description are always loaded, while the full instructions are included only when they are needed.

Keeping the context small matters. LLMs are stateless, so the whole context is sent again with every request, including every round trip for a tool call. A larger context takes longer to process before the first token is generated, and with providers that bill per token, every extra token in the system prompt is paid for again on each request. Prompt caching can reduce this cost for a context that stays the same. Very long contexts can also make the model worse at picking out the instructions that matter.

Structure of a skill

A skill is a folder containing a SKILL.md file (plus optional scripts, references, assets). Skills follow an open standard and are supported by several harnesses (coding agents, frameworks), which makes them portable in a similar way as the MCP server in the earlier example.

Let’s look at a concrete example: For our running example of a flight booking agent, we could imagine that our company has some travel policy that we need to follow. While the MCP server could be provided by the travel agency, the skills are custom to our company. Let us explore the travel-policy skill.

The folder layout in our example is as follows:

agent-skills/
└── travel-policy/
    └── SKILL.md

Where the SKILL.md file consists of a YAML frontmatter and some markdown body. The frontmatter includes the name and description fields that are standardized. The name and description are what is included in the context of the LLM to select different skills from all available skills if they seem likely to be relevant. This is comparable to the tool docstring in the previous lesson. The markdown body includes natural language. See the travel-policy/SKILL.md:

---
name: travel-policy
description: Corporate travel policy to apply when searching for or recommending flights (cost-per-km cap, aircraft restrictions, and the user's time-of-day preference).
---

# Corporate Travel Policy

Rules:

1. Cost per km must not exceed 0.25 EUR.
2. Wide aircraft are prohibited below 1000 km.
3. Prefer the cheapest compliant option.
4. If no compliant option exists, recommend the least severe violation.
5. When searching for flights, always ask the user whether they prefer morning or afternoon flights — unless that preference is already known from the conversation history, in which case do not ask again and apply it silently.

Instructor note

Highlight rule 5: Skills can also tell the agent to ask the user something.

How skills work

As described earlier, skills work following the progressive disclosure idea: At first only the name and description are loaded into the context, then later the whole skill. You can compare this using the tabs. The Agent Skills specification describes progressive disclosure in more detail.

Only the frontmatter of every skill is added to the context:

name: travel-policy
description: Corporate travel policy to apply when searching for or recommending flights (cost-per-km cap, aircraft restrictions, and the user's time-of-day preference).

The main steps are as follows. You can compare them with the MCP log walk-through in Exercise 3.1:

  1. Harness scans the skills folder and extracts the name and descriptions.

  2. Name and description are appended into the context, together with a tool to load a skill (load_capability).

  3. The LLM detects that a skill is relevant and generates a tool call to load_capability. The inference server parses it into a structured tool call.

  4. The harness executes load_capability and returns the skill body as tool result.

  5. The skill body is added to the context of the LLM, and LLM (hopefully) follows the instructions.

Key insight

You may have noticed the “hopefully” in step 5. Nothing actually forces the model to load a skill, or to follow it once it is loaded. A skill is more of a nudge than a rule. That is the flip side of being able to write skills in plain language: it is easy, but you give up some control, and the same skill may be followed in one run and ignored in the next. We will come back to this at the end of the lesson.

Because of the progressive disclosure, many skills can be available while costing only a few tokens each.

Discussion

Why does the description matter so much? What would happen with a description like “Some rules”?

Adding skills to the agent

How do we include the skills into the framework? Skills are another capability besides our MCP server. We first give the harness file access, rooted at the script’s directory using LocalWorkspace and add the skill using Skills("./agent-skills", include=[...]). We can see that the MCP server from the previous lesson is still attached. Through the instructions block we nudge the model to load relevant skills before answering.

For the model, the instructions are the system prompt: Pydantic AI sends them as a system message at the start of every request. Pydantic AI also has a separate system_prompt argument. The difference only matters for the conversation history: a system_prompt is stored in the message history and re-sent from there, while the instructions are always taken from the current agent. The Skills capability uses the same mechanism: the list of skill names and descriptions is added to the instructions.

agent = Agent(
    model,
    name="travel_agent",
    instructions="You are a helpful travel assistant. Before answering, load any capability or skill relevant to the request.",
    capabilities=[
        MCP(url=f"http://127.0.0.1:{os.getenv('MCP_PORT', '8000')}/mcp"),
        # `Skills` reads SKILL.md files through the run's workspace, so attach one rooted
        # at this script's directory: "./agent-skills" then resolves to hands-on/agent-skills
        # no matter where the lesson is launched from.
        LocalWorkspace(Path(__file__).parent),
        Skills("./agent-skills", include=["travel-policy"]),
    ],
)


Exercise 4.1: Try out the skill

Exercise

Try out the skill. The agent uses the tools from the MCP server, so we first need to start it. In this lesson we use the model solution of Exercise 3.2, which also has the book_flight tool. If an MCP server from the previous lesson is still running, stop it first. Then start the model solution MCP server in a separate terminal from the hands-on directory:

uv run 03_mcp_server_solution.py

Run the agent with the skill with the following command.

uv run 04_skills.py

There are multiple prompts you could try. For example, start with:

Are there any flights from Paris to Rome tomorrow?

Once the agent has answered, follow up with:

Does it comply with the travel policy?

Observe in the log of your agent. Is load_capability called? Before or after search_flights?

If the skill is not loaded automatically, you can nudge the agent by naming the skill in your prompt. Note that /travel-policy is not a special command in our harness, unlike the slash commands in many coding agents. It is ordinary text that matches the skill name in the list of available skills, which makes the model more likely to call load_capability.

/travel-policy

Does the agent ask whether you prefer morning or afternoon flights, as rule 5 of the skill demands?

Next, look at what the model actually received. Find the load_capability call and its tool result in the log. The skill body comes back as an ordinary tool result, just like the MCP tool results in Exercise 3.1.

Finally, check the agent’s recommendation by hand against data/flights.csv, which contains the columns price_eur, distance_km and aircraft_type. Is the cost per km really at most 0.25 EUR? Is a wide aircraft used on a flight below 1000 km? Did the agent get the arithmetic right? The Paris–Rome flights all comply, so also try a route where some flights violate the policy:

I want to fly from Munich to Zurich next Tuesday afternoon.

Exercise 4.2: Write your own skill

Exercise

Let us now write our own skill. It should be a skill that tells the agent to always confirm before booking a flight.

The MCP server that you started in Exercise 4.1 already has the book_flight tool.

The folder for the new skill already exists: agent-skills/booking-confirmation/. Create a SKILL.md in it and use booking-confirmation as the name in the frontmatter, since the name must match the folder name. Then add the skill to include=[...] in 04_skills.py and restart the agent.

Try out the skill, e.g., by trying to book a flight. Is it working as you intended?

What can skills be used for?

Our travel policy is just one example. Skills are useful whenever an agent needs to know how you do things, not just how things are done in general. Some ideas:

  • A well-known tool on your system. The model already knows Slurm or the OpenShift CLI (oc), but it doesn’t know that on LUMI you need a specific --account and partition, or which project you deploy to on Rahti, CSC’s OpenShift-based container cloud. A skill can fill in those details.

  • Code snippets and templates, for example how your group usually sets up a job script or a new project.

  • Things that have nothing to do with code. sciwrite by Lorena Barba is a skill for reviewing scientific manuscripts, based on the Writing in the Sciences course.

Where to find skills

If you want to see what others have written, these are good places to start:

As with any skill, read it before you use it: whatever is in there ends up in your agent’s context.

Discussion

What would you write a skill for in your own work?

Summary

A skill is a folder containing a SKILL.md file, which consists of a YAML frontmatter with name and description and a markdown body with instructions in natural language. Skills follow an open standard, so the same skill can be used in different harnesses.

Skills follow the idea of progressive disclosure: the name and description of every skill are always in the context, while the body is only loaded when the LLM decides that the skill is relevant, by calling the load_capability tool. This keeps the context small even with many skills available, but it also means that a skill with a vague description may never be loaded.

In Pydantic AI, we added skills with the Skills(...) capability, next to the MCP server from the previous lesson. Skills complement tools and MCP and keep the system prompt small. However, skills only guide the agent and do not enforce rules: hard constraints belong in code.

Rule 5 of our travel policy relies on the time-of-day preference being known from the conversation history. What happens when you restart the agent? The preference is gone and the agent asks again. In the next lesson, we will look at how to give the agent memory.