Skip to content
AstroPaper
Go back

Reading Notes: Skills Improvements in Deep Agents

Edit page
Note

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 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:

  1. Discovery: at startup, the system prompt lists only each skill’s name and description.
  2. Activation: when a task matches a skill, the agent reads the full SKILL.md with read_file.
  3. 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 typeExamplePortability
Pure knowledge or processWriting guide, review checklistWorks almost anywhere
Needs general capabilitiesReading files, running scripts/Works wherever the host can run code
Needs specific business toolsCalling the company’s call systemOnly 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:

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:

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:

For simple cases, listing tool names is enough. Use labels when you need dynamic or permission-based control.

Note

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:

  1. First model call: the model decides to read meeting-prep and calls read_file.
  2. 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:

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:

Summary

Each improvement solves one problem that appears as skill libraries scale:

ImprovementProblem solvedKey parameter
Tool bindingSkills and their tools drift apart, causing misuse or an extra searchmetadata.include_tools, SkillsMiddleware(tools=...)
Pinned skillsThe user names a skill, but the model needs an extra call and may pick wrongpinned_skills
ReloadingLong threads don’t see skill library updatesskills_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.


Edit page
Share this post:

Previous Post
How to Choose the Right Effort Level in Claude
Next Post
LeetCode 42. Trapping Rain Water: Four Approaches, from Brute Force to Two Pointers