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.mdfile.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:
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.
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 full body is added to the context:
# 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.
The main steps are as follows. You can compare them with the MCP log walk-through in Exercise 3.1:
Harness scans the skills folder and extracts the name and descriptions.
Name and description are appended into the context, together with a tool to load a skill (
load_capability).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.The harness executes
load_capabilityand returns the skill body as tool result.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.
Solution
A typical run calls load_capability with {"id": "travel-policy"}, get_current_datetime to resolve
“tomorrow”, and search_flights. The order of load_capability relative to the other tools can differ.
If the skill is loaded before search_flights, the agent may first ask whether you prefer morning or
afternoon flights. The tool result of load_capability contains the body of SKILL.md.
Whether and when the skill is loaded depends on the model and on how well the description matches your
prompt. The same prompt can load the skill in one run and skip it in the next. If the skill is not loaded,
the agent will answer without applying the policy.
This is not a bug in our setup. Getting models to call the right tool or skill at the right time is a big part of how frontier models are trained today, so the developers have a lot of say in how this works. Try a different model, or even a newer version of the same one, and it may load the skill earlier, later or not at all.
The Paris–Rome flights cost between 0.10 and 0.13 EUR/km and none uses a wide aircraft, so all of them comply. For Munich–Zurich (242 km) on a Tuesday, there are only two flights: LH199 at 06:45 (58 EUR, 0.24 EUR/km) complies, but LH200 at 11:45 (71 EUR, 0.29 EUR/km) exceeds the limit. There is no afternoon flight on Tuesdays. The agent should say so, not recommend LH200 without pointing out the violation, and suggest LH199 as the compliant alternative. Since your prompt already says “afternoon”, the agent should not ask for your preference again (rule 5). Check whether your agent did this and whether its numbers are correct.
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?
Solution
Create agent-skills/booking-confirmation/SKILL.md:
---
name: booking-confirmation
description: Rules to follow before booking a flight with the book_flight tool. Use whenever the user asks to book, reserve or buy a flight.
---
# Booking confirmation
Before calling `book_flight`:
1. Summarize the booking: flight number, origin, destination, date, departure time, price and passenger name.
2. Ask the user to explicitly confirm the booking.
3. Only call `book_flight` after the user has confirmed in their latest message. If they decline or change details, do not book.
4. If the passenger name is missing, ask for it. Never make one up.
Then include it next to the travel policy in 04_skills.py:
Skills("./agent-skills", include=["travel-policy", "booking-confirmation"]),
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--accountand 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:
CSC skills, for working with CSC’s services
huggingface/skills, for the Hugging Face Hub, datasets and training
pydantic/skills, for Pydantic, Logfire and building Pydantic AI agents
skills.sh and obra/superpowers, popular collections, mostly written by individuals
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.