LangChain Reference home pageLangChain ReferenceLangChain Reference
  • GitHub
  • Main Docs
Deep Agents
LangChain
LangGraph
Integrations
LangSmith
  • Overview
  • Agent
  • Middleware
  • Backends
  • Sandboxes
  • Skills
  • Subagents
  • Types
Modal
Daytona
Deno
Node VFS
Sandbox Standard Tests
  • Vitest
⌘I

LangChain Assistant

Ask a question to get started

Enter to send•Shift+Enter new line

Menu

OverviewAgentMiddlewareBackendsSandboxesSkillsSubagentsTypes
Modal
Daytona
Deno
Node VFS
Sandbox Standard Tests
Vitest
Language
Theme
JavaScriptdeepagentsnodecreateSkillsMiddleware
Functionā—Since v1.10

createSkillsMiddleware

Copy
createSkillsMiddleware<
  TContext = unknown
>(
  options
:
SkillsMiddlewareOptions
<
TContext
>
)
:
AgentMiddleware
<
StateSchema
<
__type
>
,
undefined
,
unknown
,
readonly
ClientTool
|
ServerTool
[
]
,
readonly
[
]
>
View source on GitHub

Create backend-agnostic middleware for loading and exposing agent skills.

This middleware loads skills from configurable backend sources and injects skill metadata into the system prompt. It implements the progressive disclosure pattern: skill names and descriptions are shown in the prompt, but the agent reads full SKILL.md content only when needed.

Skills are loaded once per thread and stored in state. To pick up skills added, edited, or deleted since then, set skillsMetadata to null; the next model call reloads every source:

await agent.updateState(config, { skillsMetadata: null });
// or as part of the next run's input
await agent.invoke({ messages, skillsMetadata: null }, config);

Loading happens in beforeModel, so the reload is served by the next model call rather than the next run. A middleware of your own can therefore invalidate from any hook — including mid-run, from afterModel — and see the fresh list on the following call. See skillsMetadataValue.

To guarantee the model gets a skill's instructions, for example when the user names it, pass its name in pinnedSkills. Before the next model call, each named skill's SKILL.md, without its frontmatter, is appended to the conversation as its own HumanMessage, with additional_kwargs.lc_source set to "pinned_skill" and the skill's name, path and description under additional_kwargs.skill:

await agent.invoke(
  {
    messages: [new HumanMessage("/write-tests /house-style for auth.py")],
    pinnedSkills: ["write-tests", "house-style"],
  },
  config,
);

Pins resolve against the skills loaded for the thread, so reset skillsMetadata in the same input to pin a skill added since. See pinnedSkillsValue.

Skill tools

A skill can list the tools its instructions use, separated by spaces, under metadata.include_tools in its SKILL.md frontmatter:

metadata:
  include_tools: create_customer_request list_customer_requests

Pass those tools as tools, either as an array or as a SkillToolResolver that looks them up by name when a skill is read or pinned, so one name can stand for a family of tools whose real names are only known at runtime. The model sees a skill tool only after it reads, with read_file, a skill that lists it, or the skill is pinned, and only while that read or pin stays in the conversation it is sent; compaction that drops the read or pin withdraws the tool. Until then, calling the tool fails with the standard invalid-tool error and the tool doesn't run. This controls what is in the model's context; it is not a security boundary, since the model can read any SKILL.md at any time.

include_tools can also name a tool passed to the agent rather than to this middleware. That tool wins over a skill tool of the same name. If it is deferred (extras: { defer_loading: true }), reading or pinning the skill discloses it early, and it stays deferred and searchable; if it is bound, nothing changes.

Parameters

NameTypeDescription
options*SkillsMiddlewareOptions<TContext>

Example

Configuration options

Copy
const middleware = createSkillsMiddleware({
  backend: new FilesystemBackend({ rootDir: "/" }),
  sources: ["/skills/user/", "/skills/project/"],
});