Source: Sydney Runkle, Revamping Skills in Deep Agents, 2026-10-08. These are my own notes, not a translation. Besides the main points, I wrote down a few concepts that confused me at first. All code is illustrative; see the official deepagents skills docs for exact usage.
Table of contents
Open Table of contents
A few concepts first
Skills and tools are different things
I mixed these two up at first. The easiest way to tell them apart:
- A tool is an action the agent can take. It’s a function that actually runs, like “search call records” or “update the budget”. The model decides to call it, your code executes it, and the result goes back to the model. Without tools, a model can only talk.
- A skill is a manual that teaches the agent how to do something. It’s a
SKILL.mdfile describing a process, house rules, and lessons learned. Reading it is how the model learns “how our team does this”. A skill doesn’t execute anything.
A human analogy: tools are the system access an employee has; skills are the team’s SOPs. Access without the SOP means the work gets done, but maybe not the right way. The SOP without access means nothing gets done.
They complement each other. Some skills are pure guidance and need no tools; some tools are simple enough to need no manual. But very often a skill exists mainly to explain how to use a few specific tools. That’s where the first improvement in the article comes from.
Schema: a tool’s interface description
The model never sees a tool’s code. It sees the tool’s schema: its name, what it does, which parameters it takes, their types, and which are required. Schemas are usually written in JSON Schema:
{
"name": "search_calls",
"description": "Search call records by customer name and date range",
"parameters": {
"type": "object",
"properties": {
"customer": { "type": "string" },
"start_date": { "type": "string", "description": "YYYY-MM-DD" }
},
"required": ["customer"]
}
}
A schema describes structure, not implementation. But it has to be sent as text in every request. At a few hundred tokens per tool, a hundred tools add up to tens of thousands of tokens. That’s why mechanisms like tool search exist: load a schema only when it’s needed.
How skills save context: progressive disclosure
A skill is a folder. The frontmatter of SKILL.md holds a name and description, the body holds the instructions, and the folder can also include scripts/, references/, and assets/.
Loading happens in three levels:
- Discovery: at startup, the system prompt lists only each skill’s name and description.
- Activation: when a task matches a skill, the agent reads the full
SKILL.mdwithread_file. - Execution: the agent follows the instructions and reads scripts or reference files only when needed.
An unused skill costs one line in the system prompt, so the library can be large without crowding the context.
The “open standard” covers the format, not the tools
The article says Agent Skills is an open standard that works across models and products. My question was: do different products actually have the tools a skill mentions?
Not necessarily. The standard defines the folder layout, the frontmatter format, and how skills are loaded, so any product that supports it can pick up the same skill package. And since a skill is just text, any model can read it. But the specific tools a skill mentions have to be provided by the environment:
| Skill type | Example | Portability |
|---|---|---|
| Pure knowledge or process | Writing guide, review checklist | Works almost anywhere |
| Needs general capabilities | Reading files, running scripts/ | Works wherever the host can run code |
| Needs specific business tools | Calling the company’s call system | Only where the host has those tools wired up |
Tools are commonly connected through MCP, which is another open standard. Roughly: skills cover “how to do it”, MCP covers “what to do it with”.
Background: skill libraries keep growing
The article notes that enterprise skill registries have grown to thousands of skills, shared across teams and agents. At that scale new needs show up, and Deep Agents added three improvements for them.
The running example is a sales (GTM) agent built on Deep Agents, with 50+ skills such as meeting-prep, call-transcripts, and competitive-intel-card. I’ll use the same example below.
Improvement 1: binding tools to skills
The old problem
Skills and tools used to be two independent mechanisms. Nothing told the system which skill explained which tools, which led to two kinds of mismatch:
- Calling a tool without reading its skill. Asked “what did we discuss with Acme last week?”, the agent finds
search_callsand calls it right away, skipping the parameter formats and caveats incall-transcripts, and gets it wrong. - Reading the skill, then still having to find the tool. The agent reads the skill and knows it should use
search_calls, but the tool’s schema isn’t in context yet, so it needs another tool search round trip.
The new approach
In the skill’s frontmatter, declare which tools it covers under metadata.include_tools:
---
name: call-transcripts
description: Find and read customer call transcripts
metadata:
include_tools: search_calls get_transcript
---
Then hand those tools to SkillsMiddleware instead of the agent:
agent = create_deep_agent(
model=...,
# no tools passed here
middleware=[
SkillsMiddleware(
sources=["/skills/"],
tools=[search_calls, get_transcript],
),
],
)
This is the key step. Whoever holds the tools decides when they appear:
- Pass them to the agent, and they’re visible from the first turn onward.
- Pass them to
SkillsMiddleware, and the middleware holds them. Middleware sits between the agent loop and the model call, so every request passes through it. It already tracks which skills have been read, which makes it the natural place to decide when to release the tools.
The result: before the agent reads call-transcripts, the two tools don’t exist, and calling one fails as an unknown tool. After reading it, the tools show up automatically, with no extra search. Both mismatches are gone.
Think of it as a manager holding the equipment keys and handing them out only after checking that you’ve read the manual.
Keeping the prompt cache intact
Some background: model providers cache the prefix of a request. If the beginning of the next request is identical, that part doesn’t need to be recomputed, which is faster and cheaper. Change a single character in the prefix and everything after it misses the cache.
Adding tools mid-conversation used to mean editing the request’s tool list, which broke the cache. Newer models from Anthropic and OpenAI can accept tools mid-conversation, so Deep Agents adds them in a new system message after the skill is read, leaving everything before it untouched. Models without that support fall back to the old behavior.
Going further: labels and a resolver function
Instead of tool names, include_tools can hold a label, such as call_tools. You then pass SkillsMiddleware a function that turns the label into actual tools:
def resolve(name, runtime):
if name == "call_tools":
return all tools on the calls MCP server
if name == "budget_tools":
if current user is in finance:
return [view_budget, edit_budget]
return [view_budget]
return []
SkillsMiddleware(sources=["/skills/"], tools=resolve)
The signature is (name, runtime) -> list[BaseTool]: given a label and the current runtime context (such as who the user is), return the tools to unlock this time.
This is useful in two ways:
- One label for a whole group of tools, such as everything on an MCP server. When the server adds or removes tools, the skill file doesn’t change.
- Tools based on permissions. The same skill can let finance edit the budget while everyone else can only view it. That decision can’t live in a static
SKILL.md, because who the user is only becomes known at runtime.
For simple cases, listing tool names is enough. Use labels when you need dynamic or permission-based control.
include_tools is a Deep Agents extension stored in the standard’s metadata field. Other products will most likely ignore it: the skill body still reads fine, just without the binding behavior.
Improvement 2: pinned skills
The old problem
A user types /meeting-prep get me ready for tomorrow's Acme call, already naming the skill they want. But the model starts out seeing only skill names and descriptions, so the flow is:
- First model call: the model decides to read
meeting-prepand callsread_file. - Second model call: with the full instructions in hand, it finally starts working.
The first call only fetches the manual, adding latency. And the model may not realize /meeting-prep names a skill at all. It could pick the wrong one, or skip reading it because it thinks it already knows how.
The new approach
Your app detects which skills the user named and passes them as pinned_skills:
pinned = extract_skill_names(message) # e.g. ["meeting-prep"]
agent.invoke({
"messages": [{"role": "user", "content": message}],
"pinned_skills": pinned,
})
The middleware adds those skills’ full instructions to the conversation before calling the model, so the model can start working on the very first call.
Key points:
- You choose the syntax. Deep Agents doesn’t parse messages; it just receives the list. A regex for
/skill-nameworks, and so does a picker in your UI. - Bound tools come along. Pinning a skill also unlocks the tools in its
include_tools. - The cache is unaffected. The skill instructions are appended as a new message, and each skill is added only once, so the system prompt and earlier messages never change.
- Friendlier UI. The message is tagged, so a chat UI can show a small “meeting-prep loaded” label instead of hundreds of lines of instructions.
An analogy: without pinning, your assistant goes to the filing cabinet to find the manual; with pinning, you open the manual to the right page and hand it over.
Improvement 3: reloading skills mid-thread
The old problem
A thread is one ongoing conversation, which can stretch over days. By default, SkillsMiddleware scans the skill directories once when a thread starts, stores the result (skills_metadata) in agent state, and reuses it every turn after. That’s fast and keeps the system prompt stable, so the cache keeps hitting.
The catch is that old threads never see library updates. Say you start a thread on Monday, a teammate adds competitive-intel-card on Wednesday, and on Thursday you ask the agent in that thread for a competitor card. It still has Monday’s list and doesn’t know the skill exists. Previously the only fix was a new thread, losing the earlier context.
The new approach
Set skills_metadata to None when invoking the agent:
agent.invoke({
"messages": messages,
"skills_metadata": None, # discard the cached list
})
Seeing an empty list, the middleware rescans every skill source and picks up the latest set. The conversation history stays as it is.
The cost, and why it’s usually fine
If the skill list changes, the system prompt changes. Since the system prompt sits at the very start of the request, the whole cache is invalidated. (If the rescan finds no changes, there’s no cost.)
But caches expire anyway: providers typically drop them after minutes to an hour of inactivity. The typical reason to reload is coming back to an old thread after a break, when the cache is already gone, so refreshing the list costs almost nothing extra. What to avoid is reloading repeatedly during an active conversation, which keeps knocking out a cache that was still warm.
When to trigger it
A reload is just one extra input field, so your app decides the timing:
- A
/reloadcommand that users trigger themselves; - LangGraph’s
update_stateto clear the list outside an agent run; - Logic in middleware, such as reloading after a thread has been idle for a while or when the skill library version changes.
Summary
Each improvement solves one problem that appears as skill libraries scale:
| Improvement | Problem solved | Key parameter |
|---|---|---|
| Tool binding | Skills and their tools drift apart, causing misuse or an extra search | metadata.include_tools, SkillsMiddleware(tools=...) |
| Pinned skills | The user names a skill, but the model needs an extra call and may pick wrong | pinned_skills |
| Reloading | Long threads don’t see skill library updates | skills_metadata: None |
The common thread is the prompt cache: tools arrive in a new system message, a pinned skill is appended once, and reloads happen when the cache has most likely expired anyway. Leave the prefix alone whenever possible.
All of this ships in the latest deepagents. Credit from the article: Rich Scarrott led development, and Hunter Lovell reviewed the features and the post.