Skills require
deepagents>=1.7.0.What are skills
Skills are a directory of folders, where each folder has one or more files that contain context the agent can use:- A
SKILL.mdfile containing instructions and metadata about the skill - Additional scripts (optional)
- Additional reference info, such as docs (optional)
- Additional assets, such as templates and other resources (optional)
Any additional assets (scripts, docs, templates, or other resources) must be referenced in the
SKILL.md file with information on what the file contains and how to use it so the agent can decide when to use them.How skills work
When you create a deep agent, you can pass in a list of directories containing skills. As the agent starts, it reads through the frontmatter of eachSKILL.md file.
When the agent receives a prompt, the agent checks if it can use any skills while fulfilling the prompt. If it finds a matching prompt, it then reviews the rest of the skill files. This pattern of only reviewing the skill information when needed is called progressive disclosure.
Example
You might have a skills folder that contains a skill to use a docs site in a certain way, as well as another skill to search the arXiv preprint repository of research papers: skills/
├── langgraph-docs
│ └── SKILL.md
└── arxiv_search
├── SKILL.md
└── arxiv_search.ts # code for searching arXiv
SKILL.md file always follows the same pattern, starting with metadata in the frontmatter and followed by the instructions for the skill.
The following example shows a skill that gives instructions on how to provide relevant langgraph docs when prompted:
---
name: langgraph-docs
description: Use this skill for requests related to LangGraph in order to fetch relevant documentation to provide accurate, up-to-date guidance.
---
# langgraph-docs
## Overview
This skill explains how to access LangGraph Python documentation to help answer questions and guide implementation.
## Instructions
### 1. Fetch the Documentation Index
Use the fetch_url tool to read the following URL:
https://docs.langchain.com/llms.txt
This provides a structured list of all available documentation with descriptions.
### 2. Select Relevant Documentation
Based on the question, identify 2-4 most relevant documentation URLs from the index. Prioritize:
- Specific how-to guides for implementation questions
- Core concept pages for understanding questions
- Tutorials for end-to-end examples
- Reference docs for API details
### 3. Fetch Selected Documentation
Use the fetch_url tool to read the selected documentation URLs.
### 4. Provide Accurate Guidance
After reading the documentation, complete the user's request.
ImportantRefer to the full Agent Skills Specification for information on constraints and best practices when authoring skill files. Notably:
- The
descriptionfield is truncated to 1024 characters if it exceeds that length. - In Deep Agents,
SKILL.mdfiles must be under 10 MB. Files exceeding this limit are skipped during skill loading.
Full example
The following example shows aSKILL.md file using all available frontmatter fields:
---
name: langgraph-docs
description: Use this skill for requests related to LangGraph in order to fetch relevant documentation to provide accurate, up-to-date guidance.
license: MIT
compatibility: Requires internet access for fetching documentation URLs
metadata:
author: langchain
version: "1.0"
allowed-tools: fetch_url
---
# langgraph-docs
## Overview
This skill explains how to access LangGraph Python documentation to help answer questions and guide implementation.
## Instructions
### 1. Fetch the documentation index
Use the fetch_url tool to read the following URL:
https://docs.langchain.com/llms.txt
This provides a structured list of all available documentation with descriptions.
### 2. Select relevant documentation
Based on the question, identify 2-4 most relevant documentation URLs from the index. Prioritize:
- Specific how-to guides for implementation questions
- Core concept pages for understanding questions
- Tutorials for end-to-end examples
- Reference docs for API details
### 3. Fetch selected documentation
Use the fetch_url tool to read the selected documentation URLs.
### 4. Provide accurate guidance
After reading the documentation, complete the user's request.
Usage
Pass the skills directory when creating your deep agent:- StateBackend
- StoreBackend
- FilesystemBackend
import { createDeepAgent, type FileData } from "deepagents";
import { MemorySaver } from "@langchain/langgraph";
const checkpointer = new MemorySaver();
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
const skillsFiles: Record<string, FileData> = {};
const skillUrl =
"https://raw.githubusercontent.com/langchain-ai/deepagentsjs/refs/heads/main/examples/skills/langgraph-docs/SKILL.md";
const response = await fetch(skillUrl);
const skillContent = await response.text();
skillsFiles["/skills/langgraph-docs/SKILL.md"] = createFileData(skillContent);
const agent = await createDeepAgent({
checkpointer,
// IMPORTANT: deepagents skill source paths are virtual (POSIX) paths relative to the backend root.
skills: ["/skills/"],
});
const config = {
configurable: {
thread_id: `thread-${Date.now()}`,
},
};
const result = await agent.invoke(
{
messages: [
{
role: "user",
content: "what is langraph? Use the langgraph-docs skill if available.",
},
],
files: skillsFiles,
},
config,
);
import { createDeepAgent, StoreBackend, type FileData } from "deepagents";
import {
InMemoryStore,
MemorySaver,
} from "@langchain/langgraph";
const checkpointer = new MemorySaver();
const store = new InMemoryStore();
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
const skillUrl =
"https://raw.githubusercontent.com/langchain-ai/deepagentsjs/refs/heads/main/examples/skills/langgraph-docs/SKILL.md";
const response = await fetch(skillUrl);
const skillContent = await response.text();
const fileData = createFileData(skillContent);
await store.put(["filesystem"], "/skills/langgraph-docs/SKILL.md", fileData);
const agent = await createDeepAgent({
backend: new StoreBackend(),
store: store,
checkpointer,
// IMPORTANT: deepagents skill source paths are virtual (POSIX) paths relative to the backend root.
skills: ["/skills/"],
});
const config = {
recursionLimit: 50,
configurable: {
thread_id: `thread-${Date.now()}`,
},
};
const result = await agent.invoke(
{
messages: [
{
role: "user",
content: "what is langraph? Use the langgraph-docs skill if available.",
},
],
},
config,
);
import { createDeepAgent, FilesystemBackend } from "deepagents";
import { MemorySaver } from "@langchain/langgraph";
const checkpointer = new MemorySaver();
const backend = new FilesystemBackend({ rootDir: process.cwd() });
const agent = await createDeepAgent({
backend,
skills: ["./examples/skills/"],
interruptOn: {
read_file: true,
write_file: true,
delete_file: true,
},
checkpointer, // Required!
});
const config = {
configurable: {
thread_id: `thread-${Date.now()}`,
},
};
const result = await agent.invoke(
{
messages: [
{
role: "user",
content: "what is langraph? Use the langgraph-docs skill if available.",
},
],
},
config,
);
list[str]
List of skill source paths.Paths must be specified using forward slashes and are relative to the backend’s root.
- If omitted, no skills are loaded.
- When using
StateBackend(default), provide skill files withinvoke(files={...}). - With
FilesystemBackend, skills are loaded from disk relative to the backend’sroot_dir.
The SDK only loads the sources you pass in
skills. It does not automatically scan CLI directories such as ~/.deepagents/... or ~/.agents/....For CLI storage conventions, see App data.Emulating CLI source order in SDK
Emulating CLI source order in SDK
If you want CLI-style layering in SDK code, pass all desired sources explicitly in lowest-to-highest precedence order:Then pass that ordered list as
[
"<user-home>/.deepagents/{agent}/skills/",
"<user-home>/.agents/skills/",
"<project-root>/.deepagents/skills/",
"<project-root>/.agents/skills/",
]
skills when creating your agent.Source precedence
When multiple skill sources contain a skill with the same name, the skill from the source listed later in theskills array takes precedence (last one wins). This lets you layer skills from different origins.
// If both sources contain a skill named "web-search",
// the one from "/skills/project/" wins (loaded last).
const agent = await createDeepAgent({
skills: ["/skills/user/", "/skills/project/"],
...
});
Skills for subagents
When you use subagents, you can configure which skills each type has access to:- General-purpose subagent: Automatically inherits skills from the main agent when you pass
skillstocreate_deep_agent. No additional configuration is needed. - Custom subagents: Do not inherit the main agent’s skills. Add a
skillsparameter to each subagent definition with that subagent’s skill source paths.
const researchSubagent = {
name: "researcher",
description: "Research assistant with specialized skills",
systemPrompt: "You are a researcher.",
tools: [webSearch],
skills: ["/skills/research/", "/skills/web-search/"], // Subagent-specific skills
};
const agent = await createDeepAgent({
model: "google_genai:gemini-3.1-pro-preview",
skills: ["/skills/main/"], // Main agent and GP subagent get these
subagents: [researchSubagent], // Researcher gets only its own skills
});
What the agent sees
When skills are configured, a “Skills System” section is injected into the agent’s system prompt. The agent uses this information to follow a three-step process:- Match—When a user prompt arrives, the agent checks whether any skill’s description matches the task.
- Read—If a skill applies, the agent reads the full
SKILL.mdfile using the path shown in its skills list. - Execute—The agent follows the skill’s instructions and accesses any supporting files (scripts, templates, reference docs) as needed.
Write clear, specific descriptions in your
SKILL.md frontmatter. The agent decides whether to use a skill based on the description alone—detailed descriptions lead to better skill matching.Execute skill scripts in a sandbox
Skills can include scripts alongside theSKILL.md file, such as, for example, a Python file that performs a search or data transformation. The agent can read these scripts from any backend, but to execute them, the agent needs access to a shell — which only sandbox backends provide.
When you use a CompositeBackend that routes skills to a StoreBackend for persistence while using a sandbox as the default backend, skill files live in the store rather than in the sandbox is where code runs. For sandboxes to be able to use the scripts, you must use custom middleware to upload skill scripts into the sandbox before the agent starts:
import { readFile, readdir } from "node:fs/promises";
import { join, posix, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createMiddleware } from "langchain";
import {
CompositeBackend,
createDeepAgent,
type FileData,
StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
import { DaytonaSandbox } from "@langchain/daytona";
/** Identical skill bundles for every user: one shared store namespace. */
const SKILLS_SHARED_NAMESPACE = ["skills", "builtin"] as const;
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
function normalizeSkillsStoreKey(key: string): string {
const k = String(key);
if (k.includes("..") || /[*?]/.test(k)) {
throw new Error(`Invalid key: ${key}`);
}
return k.startsWith("/") ? k : `/${k}`;
}
async function walkFiles(dir: string): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await walkFiles(fullPath)));
} else if (entry.isFile()) {
files.push(fullPath);
}
}
return files.sort((a, b) => a.localeCompare(b));
}
/** Load canonical skill files from disk into the shared store namespace (run once at deploy).
* You can retrieve skills from any source (local filesystem, remote URL, etc.).
*/
async function seedSkillStore(store: InMemoryStore) {
const moduleDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
const skillsDir = resolve(moduleDir, "skills");
const filePaths = await walkFiles(skillsDir);
for (const filePath of filePaths) {
const rel = relative(skillsDir, filePath);
// StoreBackend keys are paths *relative to the routed backend root*.
// CompositeBackend strips the route prefix (`/skills/`) before delegating,
// so store keys should look like "/<skillname>/SKILL.md".
const key = `/${posix.normalize(rel.split("\\").join("/"))}`;
const content = await readFile(filePath, "utf8");
await store.put([...SKILLS_SHARED_NAMESPACE], key, createFileData(content));
}
}
/** Copy shared skill files from the store into the sandbox before each agent run. */
function createSkillSandboxSyncMiddleware(backend: CompositeBackend) {
return createMiddleware({
name: "SkillSandboxSyncMiddleware",
beforeAgent: async (state, runtime) => {
const store = (runtime as any).store;
if (!store) {
throw new Error(
"Store is required for syncing skills into the sandbox. " +
"Pass `store` to createDeepAgent and ensure your runtime provides it.",
);
}
const encoder = new TextEncoder();
const files: Array<[string, Uint8Array]> = [];
for (const item of await store.search([...SKILLS_SHARED_NAMESPACE])) {
const normalized = normalizeSkillsStoreKey(String(item.key));
const data = item.value as FileData;
// CompositeBackend routes paths and batches uploads to the right backend.
files.push([
`/skills${normalized}`,
encoder.encode(data.content.join("\n")),
]);
}
if (files.length > 0) await backend.uploadFiles(files);
return state;
},
});
}
async function main() {
const store = new InMemoryStore();
await seedSkillStore(store);
const sandbox = await DaytonaSandbox.create({
language: "python",
timeout: 300,
});
const backend = new CompositeBackend(sandbox, {
"/skills/": new StoreBackend({
store,
namespace: () => [...SKILLS_SHARED_NAMESPACE],
} as any),
});
try {
const agent = await createDeepAgent({
model: "google-genai:gemini-3.1-pro-preview",
backend,
skills: ["/skills/"],
store,
middleware: [createSkillSandboxSyncMiddleware(backend)],
});
} finally {
await sandbox.close();
}
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
import { readFile, readdir } from "node:fs/promises";
import { join, posix, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createMiddleware } from "langchain";
import {
CompositeBackend,
createDeepAgent,
type FileData,
StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
import { DaytonaSandbox } from "@langchain/daytona";
/** Identical skill bundles for every user: one shared store namespace. */
const SKILLS_SHARED_NAMESPACE = ["skills", "builtin"] as const;
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
function normalizeSkillsStoreKey(key: string): string {
const k = String(key);
if (k.includes("..") || /[*?]/.test(k)) {
throw new Error(`Invalid key: ${key}`);
}
return k.startsWith("/") ? k : `/${k}`;
}
async function walkFiles(dir: string): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await walkFiles(fullPath)));
} else if (entry.isFile()) {
files.push(fullPath);
}
}
return files.sort((a, b) => a.localeCompare(b));
}
/** Load canonical skill files from disk into the shared store namespace (run once at deploy).
* You can retrieve skills from any source (local filesystem, remote URL, etc.).
*/
async function seedSkillStore(store: InMemoryStore) {
const moduleDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
const skillsDir = resolve(moduleDir, "skills");
const filePaths = await walkFiles(skillsDir);
for (const filePath of filePaths) {
const rel = relative(skillsDir, filePath);
// StoreBackend keys are paths *relative to the routed backend root*.
// CompositeBackend strips the route prefix (`/skills/`) before delegating,
// so store keys should look like "/<skillname>/SKILL.md".
const key = `/${posix.normalize(rel.split("\\").join("/"))}`;
const content = await readFile(filePath, "utf8");
await store.put([...SKILLS_SHARED_NAMESPACE], key, createFileData(content));
}
}
/** Copy shared skill files from the store into the sandbox before each agent run. */
function createSkillSandboxSyncMiddleware(backend: CompositeBackend) {
return createMiddleware({
name: "SkillSandboxSyncMiddleware",
beforeAgent: async (state, runtime) => {
const store = (runtime as any).store;
if (!store) {
throw new Error(
"Store is required for syncing skills into the sandbox. " +
"Pass `store` to createDeepAgent and ensure your runtime provides it.",
);
}
const encoder = new TextEncoder();
const files: Array<[string, Uint8Array]> = [];
for (const item of await store.search([...SKILLS_SHARED_NAMESPACE])) {
const normalized = normalizeSkillsStoreKey(String(item.key));
const data = item.value as FileData;
// CompositeBackend routes paths and batches uploads to the right backend.
files.push([
`/skills${normalized}`,
encoder.encode(data.content.join("\n")),
]);
}
if (files.length > 0) await backend.uploadFiles(files);
return state;
},
});
}
async function main() {
const store = new InMemoryStore();
await seedSkillStore(store);
const sandbox = await DaytonaSandbox.create({
language: "python",
timeout: 300,
});
const backend = new CompositeBackend(sandbox, {
"/skills/": new StoreBackend({
store,
namespace: () => [...SKILLS_SHARED_NAMESPACE],
} as any),
});
try {
const agent = await createDeepAgent({
model: "openai:gpt-5.4",
backend,
skills: ["/skills/"],
store,
middleware: [createSkillSandboxSyncMiddleware(backend)],
});
} finally {
await sandbox.close();
}
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
import { readFile, readdir } from "node:fs/promises";
import { join, posix, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createMiddleware } from "langchain";
import {
CompositeBackend,
createDeepAgent,
type FileData,
StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
import { DaytonaSandbox } from "@langchain/daytona";
/** Identical skill bundles for every user: one shared store namespace. */
const SKILLS_SHARED_NAMESPACE = ["skills", "builtin"] as const;
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
function normalizeSkillsStoreKey(key: string): string {
const k = String(key);
if (k.includes("..") || /[*?]/.test(k)) {
throw new Error(`Invalid key: ${key}`);
}
return k.startsWith("/") ? k : `/${k}`;
}
async function walkFiles(dir: string): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await walkFiles(fullPath)));
} else if (entry.isFile()) {
files.push(fullPath);
}
}
return files.sort((a, b) => a.localeCompare(b));
}
/** Load canonical skill files from disk into the shared store namespace (run once at deploy).
* You can retrieve skills from any source (local filesystem, remote URL, etc.).
*/
async function seedSkillStore(store: InMemoryStore) {
const moduleDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
const skillsDir = resolve(moduleDir, "skills");
const filePaths = await walkFiles(skillsDir);
for (const filePath of filePaths) {
const rel = relative(skillsDir, filePath);
// StoreBackend keys are paths *relative to the routed backend root*.
// CompositeBackend strips the route prefix (`/skills/`) before delegating,
// so store keys should look like "/<skillname>/SKILL.md".
const key = `/${posix.normalize(rel.split("\\").join("/"))}`;
const content = await readFile(filePath, "utf8");
await store.put([...SKILLS_SHARED_NAMESPACE], key, createFileData(content));
}
}
/** Copy shared skill files from the store into the sandbox before each agent run. */
function createSkillSandboxSyncMiddleware(backend: CompositeBackend) {
return createMiddleware({
name: "SkillSandboxSyncMiddleware",
beforeAgent: async (state, runtime) => {
const store = (runtime as any).store;
if (!store) {
throw new Error(
"Store is required for syncing skills into the sandbox. " +
"Pass `store` to createDeepAgent and ensure your runtime provides it.",
);
}
const encoder = new TextEncoder();
const files: Array<[string, Uint8Array]> = [];
for (const item of await store.search([...SKILLS_SHARED_NAMESPACE])) {
const normalized = normalizeSkillsStoreKey(String(item.key));
const data = item.value as FileData;
// CompositeBackend routes paths and batches uploads to the right backend.
files.push([
`/skills${normalized}`,
encoder.encode(data.content.join("\n")),
]);
}
if (files.length > 0) await backend.uploadFiles(files);
return state;
},
});
}
async function main() {
const store = new InMemoryStore();
await seedSkillStore(store);
const sandbox = await DaytonaSandbox.create({
language: "python",
timeout: 300,
});
const backend = new CompositeBackend(sandbox, {
"/skills/": new StoreBackend({
store,
namespace: () => [...SKILLS_SHARED_NAMESPACE],
} as any),
});
try {
const agent = await createDeepAgent({
model: "anthropic:claude-sonnet-4-6",
backend,
skills: ["/skills/"],
store,
middleware: [createSkillSandboxSyncMiddleware(backend)],
});
} finally {
await sandbox.close();
}
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
import { readFile, readdir } from "node:fs/promises";
import { join, posix, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createMiddleware } from "langchain";
import {
CompositeBackend,
createDeepAgent,
type FileData,
StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
import { DaytonaSandbox } from "@langchain/daytona";
/** Identical skill bundles for every user: one shared store namespace. */
const SKILLS_SHARED_NAMESPACE = ["skills", "builtin"] as const;
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
function normalizeSkillsStoreKey(key: string): string {
const k = String(key);
if (k.includes("..") || /[*?]/.test(k)) {
throw new Error(`Invalid key: ${key}`);
}
return k.startsWith("/") ? k : `/${k}`;
}
async function walkFiles(dir: string): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await walkFiles(fullPath)));
} else if (entry.isFile()) {
files.push(fullPath);
}
}
return files.sort((a, b) => a.localeCompare(b));
}
/** Load canonical skill files from disk into the shared store namespace (run once at deploy).
* You can retrieve skills from any source (local filesystem, remote URL, etc.).
*/
async function seedSkillStore(store: InMemoryStore) {
const moduleDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
const skillsDir = resolve(moduleDir, "skills");
const filePaths = await walkFiles(skillsDir);
for (const filePath of filePaths) {
const rel = relative(skillsDir, filePath);
// StoreBackend keys are paths *relative to the routed backend root*.
// CompositeBackend strips the route prefix (`/skills/`) before delegating,
// so store keys should look like "/<skillname>/SKILL.md".
const key = `/${posix.normalize(rel.split("\\").join("/"))}`;
const content = await readFile(filePath, "utf8");
await store.put([...SKILLS_SHARED_NAMESPACE], key, createFileData(content));
}
}
/** Copy shared skill files from the store into the sandbox before each agent run. */
function createSkillSandboxSyncMiddleware(backend: CompositeBackend) {
return createMiddleware({
name: "SkillSandboxSyncMiddleware",
beforeAgent: async (state, runtime) => {
const store = (runtime as any).store;
if (!store) {
throw new Error(
"Store is required for syncing skills into the sandbox. " +
"Pass `store` to createDeepAgent and ensure your runtime provides it.",
);
}
const encoder = new TextEncoder();
const files: Array<[string, Uint8Array]> = [];
for (const item of await store.search([...SKILLS_SHARED_NAMESPACE])) {
const normalized = normalizeSkillsStoreKey(String(item.key));
const data = item.value as FileData;
// CompositeBackend routes paths and batches uploads to the right backend.
files.push([
`/skills${normalized}`,
encoder.encode(data.content.join("\n")),
]);
}
if (files.length > 0) await backend.uploadFiles(files);
return state;
},
});
}
async function main() {
const store = new InMemoryStore();
await seedSkillStore(store);
const sandbox = await DaytonaSandbox.create({
language: "python",
timeout: 300,
});
const backend = new CompositeBackend(sandbox, {
"/skills/": new StoreBackend({
store,
namespace: () => [...SKILLS_SHARED_NAMESPACE],
} as any),
});
try {
const agent = await createDeepAgent({
model: "openrouter:anthropic/claude-sonnet-4-6",
backend,
skills: ["/skills/"],
store,
middleware: [createSkillSandboxSyncMiddleware(backend)],
});
} finally {
await sandbox.close();
}
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
import { readFile, readdir } from "node:fs/promises";
import { join, posix, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createMiddleware } from "langchain";
import {
CompositeBackend,
createDeepAgent,
type FileData,
StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
import { DaytonaSandbox } from "@langchain/daytona";
/** Identical skill bundles for every user: one shared store namespace. */
const SKILLS_SHARED_NAMESPACE = ["skills", "builtin"] as const;
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
function normalizeSkillsStoreKey(key: string): string {
const k = String(key);
if (k.includes("..") || /[*?]/.test(k)) {
throw new Error(`Invalid key: ${key}`);
}
return k.startsWith("/") ? k : `/${k}`;
}
async function walkFiles(dir: string): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await walkFiles(fullPath)));
} else if (entry.isFile()) {
files.push(fullPath);
}
}
return files.sort((a, b) => a.localeCompare(b));
}
/** Load canonical skill files from disk into the shared store namespace (run once at deploy).
* You can retrieve skills from any source (local filesystem, remote URL, etc.).
*/
async function seedSkillStore(store: InMemoryStore) {
const moduleDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
const skillsDir = resolve(moduleDir, "skills");
const filePaths = await walkFiles(skillsDir);
for (const filePath of filePaths) {
const rel = relative(skillsDir, filePath);
// StoreBackend keys are paths *relative to the routed backend root*.
// CompositeBackend strips the route prefix (`/skills/`) before delegating,
// so store keys should look like "/<skillname>/SKILL.md".
const key = `/${posix.normalize(rel.split("\\").join("/"))}`;
const content = await readFile(filePath, "utf8");
await store.put([...SKILLS_SHARED_NAMESPACE], key, createFileData(content));
}
}
/** Copy shared skill files from the store into the sandbox before each agent run. */
function createSkillSandboxSyncMiddleware(backend: CompositeBackend) {
return createMiddleware({
name: "SkillSandboxSyncMiddleware",
beforeAgent: async (state, runtime) => {
const store = (runtime as any).store;
if (!store) {
throw new Error(
"Store is required for syncing skills into the sandbox. " +
"Pass `store` to createDeepAgent and ensure your runtime provides it.",
);
}
const encoder = new TextEncoder();
const files: Array<[string, Uint8Array]> = [];
for (const item of await store.search([...SKILLS_SHARED_NAMESPACE])) {
const normalized = normalizeSkillsStoreKey(String(item.key));
const data = item.value as FileData;
// CompositeBackend routes paths and batches uploads to the right backend.
files.push([
`/skills${normalized}`,
encoder.encode(data.content.join("\n")),
]);
}
if (files.length > 0) await backend.uploadFiles(files);
return state;
},
});
}
async function main() {
const store = new InMemoryStore();
await seedSkillStore(store);
const sandbox = await DaytonaSandbox.create({
language: "python",
timeout: 300,
});
const backend = new CompositeBackend(sandbox, {
"/skills/": new StoreBackend({
store,
namespace: () => [...SKILLS_SHARED_NAMESPACE],
} as any),
});
try {
const agent = await createDeepAgent({
model: "fireworks:accounts/fireworks/models/qwen3p5-397b-a17b",
backend,
skills: ["/skills/"],
store,
middleware: [createSkillSandboxSyncMiddleware(backend)],
});
} finally {
await sandbox.close();
}
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
import { readFile, readdir } from "node:fs/promises";
import { join, posix, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createMiddleware } from "langchain";
import {
CompositeBackend,
createDeepAgent,
type FileData,
StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
import { DaytonaSandbox } from "@langchain/daytona";
/** Identical skill bundles for every user: one shared store namespace. */
const SKILLS_SHARED_NAMESPACE = ["skills", "builtin"] as const;
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
function normalizeSkillsStoreKey(key: string): string {
const k = String(key);
if (k.includes("..") || /[*?]/.test(k)) {
throw new Error(`Invalid key: ${key}`);
}
return k.startsWith("/") ? k : `/${k}`;
}
async function walkFiles(dir: string): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await walkFiles(fullPath)));
} else if (entry.isFile()) {
files.push(fullPath);
}
}
return files.sort((a, b) => a.localeCompare(b));
}
/** Load canonical skill files from disk into the shared store namespace (run once at deploy).
* You can retrieve skills from any source (local filesystem, remote URL, etc.).
*/
async function seedSkillStore(store: InMemoryStore) {
const moduleDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
const skillsDir = resolve(moduleDir, "skills");
const filePaths = await walkFiles(skillsDir);
for (const filePath of filePaths) {
const rel = relative(skillsDir, filePath);
// StoreBackend keys are paths *relative to the routed backend root*.
// CompositeBackend strips the route prefix (`/skills/`) before delegating,
// so store keys should look like "/<skillname>/SKILL.md".
const key = `/${posix.normalize(rel.split("\\").join("/"))}`;
const content = await readFile(filePath, "utf8");
await store.put([...SKILLS_SHARED_NAMESPACE], key, createFileData(content));
}
}
/** Copy shared skill files from the store into the sandbox before each agent run. */
function createSkillSandboxSyncMiddleware(backend: CompositeBackend) {
return createMiddleware({
name: "SkillSandboxSyncMiddleware",
beforeAgent: async (state, runtime) => {
const store = (runtime as any).store;
if (!store) {
throw new Error(
"Store is required for syncing skills into the sandbox. " +
"Pass `store` to createDeepAgent and ensure your runtime provides it.",
);
}
const encoder = new TextEncoder();
const files: Array<[string, Uint8Array]> = [];
for (const item of await store.search([...SKILLS_SHARED_NAMESPACE])) {
const normalized = normalizeSkillsStoreKey(String(item.key));
const data = item.value as FileData;
// CompositeBackend routes paths and batches uploads to the right backend.
files.push([
`/skills${normalized}`,
encoder.encode(data.content.join("\n")),
]);
}
if (files.length > 0) await backend.uploadFiles(files);
return state;
},
});
}
async function main() {
const store = new InMemoryStore();
await seedSkillStore(store);
const sandbox = await DaytonaSandbox.create({
language: "python",
timeout: 300,
});
const backend = new CompositeBackend(sandbox, {
"/skills/": new StoreBackend({
store,
namespace: () => [...SKILLS_SHARED_NAMESPACE],
} as any),
});
try {
const agent = await createDeepAgent({
model: "baseten:zai-org/GLM-5",
backend,
skills: ["/skills/"],
store,
middleware: [createSkillSandboxSyncMiddleware(backend)],
});
} finally {
await sandbox.close();
}
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
import { readFile, readdir } from "node:fs/promises";
import { join, posix, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createMiddleware } from "langchain";
import {
CompositeBackend,
createDeepAgent,
type FileData,
StoreBackend,
} from "deepagents";
import { InMemoryStore } from "@langchain/langgraph";
import { DaytonaSandbox } from "@langchain/daytona";
/** Identical skill bundles for every user: one shared store namespace. */
const SKILLS_SHARED_NAMESPACE = ["skills", "builtin"] as const;
function createFileData(content: string): FileData {
const now = new Date().toISOString();
return {
content: content.split("\n"),
created_at: now,
modified_at: now,
};
}
function normalizeSkillsStoreKey(key: string): string {
const k = String(key);
if (k.includes("..") || /[*?]/.test(k)) {
throw new Error(`Invalid key: ${key}`);
}
return k.startsWith("/") ? k : `/${k}`;
}
async function walkFiles(dir: string): Promise<string[]> {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await walkFiles(fullPath)));
} else if (entry.isFile()) {
files.push(fullPath);
}
}
return files.sort((a, b) => a.localeCompare(b));
}
/** Load canonical skill files from disk into the shared store namespace (run once at deploy).
* You can retrieve skills from any source (local filesystem, remote URL, etc.).
*/
async function seedSkillStore(store: InMemoryStore) {
const moduleDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
const skillsDir = resolve(moduleDir, "skills");
const filePaths = await walkFiles(skillsDir);
for (const filePath of filePaths) {
const rel = relative(skillsDir, filePath);
// StoreBackend keys are paths *relative to the routed backend root*.
// CompositeBackend strips the route prefix (`/skills/`) before delegating,
// so store keys should look like "/<skillname>/SKILL.md".
const key = `/${posix.normalize(rel.split("\\").join("/"))}`;
const content = await readFile(filePath, "utf8");
await store.put([...SKILLS_SHARED_NAMESPACE], key, createFileData(content));
}
}
/** Copy shared skill files from the store into the sandbox before each agent run. */
function createSkillSandboxSyncMiddleware(backend: CompositeBackend) {
return createMiddleware({
name: "SkillSandboxSyncMiddleware",
beforeAgent: async (state, runtime) => {
const store = (runtime as any).store;
if (!store) {
throw new Error(
"Store is required for syncing skills into the sandbox. " +
"Pass `store` to createDeepAgent and ensure your runtime provides it.",
);
}
const encoder = new TextEncoder();
const files: Array<[string, Uint8Array]> = [];
for (const item of await store.search([...SKILLS_SHARED_NAMESPACE])) {
const normalized = normalizeSkillsStoreKey(String(item.key));
const data = item.value as FileData;
// CompositeBackend routes paths and batches uploads to the right backend.
files.push([
`/skills${normalized}`,
encoder.encode(data.content.join("\n")),
]);
}
if (files.length > 0) await backend.uploadFiles(files);
return state;
},
});
}
async function main() {
const store = new InMemoryStore();
await seedSkillStore(store);
const sandbox = await DaytonaSandbox.create({
language: "python",
timeout: 300,
});
const backend = new CompositeBackend(sandbox, {
"/skills/": new StoreBackend({
store,
namespace: () => [...SKILLS_SHARED_NAMESPACE],
} as any),
});
try {
const agent = await createDeepAgent({
model: "ollama:devstral-2",
backend,
skills: ["/skills/"],
store,
middleware: [createSkillSandboxSyncMiddleware(backend)],
});
} finally {
await sandbox.close();
}
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
beforeAgent hook runs before each agent invocation, reading skill files from that shared namespace and uploading them into the sandbox filesystem. Once synced, the agent can execute scripts with the execute tool just like any other file in the sandbox.
For a more complete example that also syncs memories bidirectionally, see syncing skills and memories with custom middleware.
Skills vs. memory
Skills and memory (AGENTS.md files) serve different purposes:
| Skills | Memory | |
|---|---|---|
| Purpose | On-demand capabilities discovered through progressive disclosure | Persistent context always loaded at startup |
| Loading | Read only when the agent determines relevance | Always injected into system prompt |
| Format | SKILL.md in named directories | AGENTS.md files |
| Layering | User → project (last wins) | User → project (combined) |
| Use when | Instructions are task-specific and potentially large | Context is always relevant (project conventions, preferences) |
When to use skills and tools
These are a few general guidelines for using tools and skills:- Use skills when there is a lot of context to reduce the number of tokens in the system prompt.
- Use skills to bundle capabilities together into larger actions and provide additional context beyond single tool descriptions.
- Use tools if the agent does not have access to the file system.
Connect these docs to Claude, VSCode, and more via MCP for real-time answers.

