> ## Documentation Index
> Fetch the complete documentation index at: https://cndoc-langchain.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 技能

> 学习如何通过技能扩展深度代理的能力

技能是可复用的代理能力，提供专门的工作流程和领域知识。

<Note>
  技能需要 `deepagents>=1.7.0`。
</Note>

你可以使用 [Agent Skills](https://agentskills.io/) 为你的深度代理提供新的能力和专业知识。要获取可直接使用的技能以提升代理在 LangChain 生态系统任务上的表现，请参阅 [LangChain Skills](https://github.com/langchain-ai/langchain-skills) 仓库。

深度代理技能遵循 [Agent Skills 规范](https://agentskills.io/specification)。

## 什么是技能

技能是一个文件夹目录，其中每个文件夹包含一个或多个文件，这些文件包含代理可以使用的上下文：

* 一个 `SKILL.md` 文件，包含关于该技能的说明和元数据
* 额外的脚本（可选）
* 额外的参考信息，例如文档（可选）
* 额外的资源，例如模板和其他资源（可选）

<Note>
  任何额外的资源（脚本、文档、模板或其他资源）必须在 `SKILL.md` 文件中被引用，并说明文件包含的内容以及如何使用它，以便代理能够决定何时使用它们。
</Note>

## 技能如何工作

当你创建一个深度代理时，你可以传入一个包含技能的目录列表。当代理启动时，它会读取每个 `SKILL.md` 文件的 frontmatter。

当代理收到提示时，它会检查在完成提示时是否可以使用任何技能。如果找到匹配的提示，它会审查技能文件的其余部分。这种仅在需要时才审查技能信息的模式称为*渐进式披露*。

## 示例

你可能有一个技能文件夹，其中包含一个以特定方式使用文档站点的技能，以及另一个用于搜索 arXiv 预印本研究论文库的技能：

```plaintext theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    skills/
    ├── langgraph-docs
    │   └── SKILL.md
    └── arxiv_search
        ├── SKILL.md
        └── arxiv_search.ts # 搜索 arXiv 的代码
```

`SKILL.md` 文件始终遵循相同的模式，以 frontmatter 中的元数据开始，后跟技能的说明。

以下示例展示了一个技能，它提供了在收到提示时如何提供相关 langgraph 文档的说明：

```md theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
---
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

## 概述

本技能说明如何访问 LangGraph Python 文档，以帮助回答问题并指导实施。

## 说明

### 1. 获取文档索引

使用 fetch_url 工具读取以下 URL：
https://docs.langchain.com/llms.txt

这提供了所有可用文档的结构化列表及其描述。

### 2. 选择相关文档

根据问题，从索引中识别 2-4 个最相关的文档 URL。优先考虑：

- 针对实施问题的具体操作指南
- 针对理解问题的核心概念页面
- 针对端到端示例的教程
- 针对 API 详情的参考文档

### 3. 获取选定的文档

使用 fetch_url 工具读取选定的文档 URL。

### 4. 提供准确的指导

阅读文档后，完成用户的请求。
```

更多示例技能，请参阅 [Deep Agents 示例技能](https://github.com/langchain-ai/deepagentsjs/tree/main/examples/skills)。

<Warning>
  **重要**

  有关编写技能文件时的约束和最佳实践，请参阅完整的 [Agent Skills 规范](https://agentskills.io/specification)。值得注意的是：

  * `description` 字段如果超过 1024 个字符，将被截断。
  * 在 Deep Agents 中，`SKILL.md` 文件必须小于 10 MB。超过此限制的文件在技能加载时将被跳过。
</Warning>

### 完整示例

以下示例展示了一个使用所有可用 frontmatter 字段的 `SKILL.md` 文件：

```md expandable theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
---
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

## 概述

本技能说明如何访问 LangGraph Python 文档，以帮助回答问题并指导实施。

## 说明

### 1. 获取文档索引

使用 fetch_url 工具读取以下 URL：
https://docs.langchain.com/llms.txt

这提供了所有可用文档的结构化列表及其描述。

### 2. 选择相关文档

根据问题，从索引中识别 2-4 个最相关的文档 URL。优先考虑：

- 针对实施问题的具体操作指南
- 针对理解问题的核心概念页面
- 针对端到端示例的教程
- 针对 API 详情的参考文档

### 3. 获取选定的文档

使用 fetch_url 工具读取选定的文档 URL。

### 4. 提供准确的指导

阅读文档后，完成用户的请求。
```

## 用法

在创建深度代理时传入技能目录：

<Tabs>
  <Tab title="状态后端">
    ```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    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,
      // 重要：deepagents 技能源路径是相对于后端根目录的虚拟（POSIX）路径。
      skills: ["/skills/"],
    });

    const config = {
      configurable: {
        thread_id: `thread-${Date.now()}`,
      },
    };

    const result = await agent.invoke(
      {
        messages: [
          {
            role: "user",
            content: "什么是 langraph？如果可用，请使用 langgraph-docs 技能。",
          },
        ],
        files: skillsFiles,
      },
      config,
    );
    ```
  </Tab>

  <Tab title="存储后端">
    ```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    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,
      // 重要：deepagents 技能源路径是相对于后端根目录的虚拟（POSIX）路径。
      skills: ["/skills/"],
    });

    const config = {
      recursionLimit: 50,
      configurable: {
        thread_id: `thread-${Date.now()}`,
      },
    };

    const result = await agent.invoke(
      {
        messages: [
          {
            role: "user",
            content: "什么是 langraph？如果可用，请使用 langgraph-docs 技能。",
          },
        ],
      },
      config,
    );
    ```
  </Tab>

  <Tab title="文件系统后端">
    ```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    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, // 必需！
    });

    const config = {
      configurable: {
        thread_id: `thread-${Date.now()}`,
      },
    };

    const result = await agent.invoke(
      {
        messages: [
          {
            role: "user",
            content: "什么是 langraph？如果可用，请使用 langgraph-docs 技能。",
          },
        ],
      },
      config,
    );
    ```
  </Tab>
</Tabs>

<ParamField body="skills" type="list[str]" optional>
  技能源路径列表。

  路径必须使用正斜杠指定，并且相对于后端的根目录。

  * 如果省略，则不加载任何技能。
  * 使用 `StateBackend`（默认）时，通过 `invoke(files={...})` 提供技能文件。
  * 使用 `FilesystemBackend` 时，技能从相对于后端 `root_dir` 的磁盘加载。

  对于同名技能，后面的源会覆盖前面的源（后者优先）。
</ParamField>

<Note>
  SDK 仅加载你在 `skills` 中传入的源。它不会自动扫描 CLI 目录，如 `~/.deepagents/...` 或 `~/.agents/...`。

  有关 CLI 存储约定，请参阅 [应用数据](/oss/javascript/deepagents/data-locations)。

  <Accordion title="在 SDK 中模拟 CLI 源顺序">
    如果你想在 SDK 代码中实现类似 CLI 的分层，请按从低到高的优先级顺序显式传入所有所需的源：

    ```text theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    [
    "<user-home>/.deepagents/{agent}/skills/",
    "<user-home>/.agents/skills/",
    "<project-root>/.deepagents/skills/",
    "<project-root>/.agents/skills/",
    ]
    ```

    然后在创建代理时将该有序列表作为 `skills` 传入。
  </Accordion>
</Note>

## 源优先级

当多个技能源包含同名技能时，`skills` 数组中列出较后的源中的技能优先（后者优先）。这允许你分层来自不同来源的技能。

```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
// 如果两个源都包含名为 "web-search" 的技能，
// 则来自 "/skills/project/" 的那个优先（最后加载）。
const agent = await createDeepAgent({
  skills: ["/skills/user/", "/skills/project/"],
  ...
});
```

## 子代理的技能

当你使用[子代理](/oss/javascript/deepagents/subagents)时，你可以配置每种类型可以访问哪些技能：

* **通用子代理**：当你向 `create_deep_agent` 传入 `skills` 时，会自动继承主代理的技能。无需额外配置。
* **自定义子代理**：不继承主代理的技能。在每个子代理定义中添加一个 `skills` 参数，指定该子代理的技能源路径。

技能状态完全隔离：主代理的技能对子代理不可见，子代理的技能对主代理也不可见。

```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
const researchSubagent = {
  name: "researcher",
  description: "Research assistant with specialized skills",
  systemPrompt: "You are a researcher.",
  tools: [webSearch],
  skills: ["/skills/research/", "/skills/web-search/"],  // 子代理特定的技能
};

const agent = await createDeepAgent({
  model: "google_genai:gemini-3.1-pro-preview",
  skills: ["/skills/main/"],  // 主代理和通用子代理获得这些技能
  subagents: [researchSubagent],  // 研究员只获得自己的技能
});
```

有关子代理配置和技能继承的更多信息，请参阅[子代理](/oss/javascript/deepagents/subagents)。

## 代理看到的内容

配置技能后，代理的系统提示中会注入一个“技能系统”部分。代理使用此信息遵循三步流程：

1. **匹配**—当用户提示到达时，代理检查是否有任何技能的描述与任务匹配。
2. **读取**—如果适用某个技能，代理会使用其技能列表中显示的路径读取完整的 `SKILL.md` 文件。
3. **执行**—代理遵循技能的说明，并根据需要访问任何支持文件（脚本、模板、参考文档）。

<Tip>
  在 `SKILL.md` frontmatter 中编写清晰、具体的描述。代理仅根据描述决定是否使用技能——详细的描述能带来更好的技能匹配。
</Tip>

## 在沙箱中执行技能脚本

技能可以包含与 `SKILL.md` 文件一起的脚本，例如，执行搜索或数据转换的 Python 文件。代理可以从任何后端\_读取\_这些脚本，但要\_执行\_它们，代理需要访问 shell——只有[沙箱后端](/oss/javascript/deepagents/sandboxes)提供此功能。

当你使用 [CompositeBackend](https://reference.langchain.com/javascript/deepagents/backends/CompositeBackend) 将技能路由到 [StoreBackend](https://reference.langchain.com/javascript/deepagents/backends/StoreBackend) 进行持久化，同时使用沙箱作为默认后端时，技能文件存储在存储中，而不是代码运行的沙箱中。为了让沙箱能够使用这些脚本，你必须使用[自定义中间件](/oss/javascript/langchain/middleware/custom)在代理启动前将技能脚本上传到沙箱：

<CodeGroup>
  ```ts Google theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  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";

  /** 每个用户相同的技能包：一个共享的存储命名空间。 */
  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));
  }

  /** 从磁盘加载规范技能文件到共享存储命名空间（部署时运行一次）。
   *  您可以从任何来源（本地文件系统、远程 URL 等）检索技能。
   */
  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 的键是*相对于路由后端根目录*的路径。
      // CompositeBackend 在委托前会剥离路由前缀 (`/skills/`)，
      // 因此存储键应类似于 "/<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));
    }
  }

  /** 在每次代理运行前，将共享技能文件从存储复制到沙箱中。 */
  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 路由路径并将批量上传到正确的后端。
          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;
  });
  ```

  ```ts OpenAI theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  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";

  /** 每个用户相同的技能包：一个共享的存储命名空间。 */
  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));
  }

  /** 从磁盘加载规范技能文件到共享存储命名空间（部署时运行一次）。
   *  您可以从任何来源（本地文件系统、远程 URL 等）检索技能。
   */
  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 的键是*相对于路由后端根目录*的路径。
      // CompositeBackend 在委托前会剥离路由前缀 (`/skills/`)，
      // 因此存储键应类似于 "/<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));
    }
  }

  /** 在每次代理运行前，将共享技能文件从存储复制到沙箱中。 */
  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 路由路径并将批量上传到正确的后端。
          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;
  });
  ```

  ```ts Anthropic theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  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";

  /** 每个用户相同的技能包：一个共享的存储命名空间。 */
  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));
  }

  /** 从磁盘加载规范技能文件到共享存储命名空间（部署时运行一次）。
   *  您可以从任何来源（本地文件系统、远程 URL 等）检索技能。
   */
  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 的键是*相对于路由后端根目录*的路径。
      // CompositeBackend 在委托前会剥离路由前缀 (`/skills/`)，
      // 因此存储键应类似于 "/<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));
    }
  }

  /** 在每次代理运行前，将共享技能文件从存储复制到沙箱中。 */
  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 路由路径并将批量上传到正确的后端。
          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;
  });
  ```

  ```ts OpenRouter theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  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";

  /** 每个用户相同的技能包：一个共享的存储命名空间。 */
  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));
  }

  /** 从磁盘加载规范技能文件到共享存储命名空间（部署时运行一次）。
   *  您可以从任何来源（本地文件系统、远程 URL 等）检索技能。
   */
  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 的键是*相对于路由后端根目录*的路径。
      // CompositeBackend 在委托前会剥离路由前缀 (`/skills/`)，
      // 因此存储键应类似于 "/<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));
    }
  }

  /** 在每次代理运行前，将共享技能文件从存储复制到沙箱中。 */
  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 路由路径并将批量上传到正确的后端。
          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;
  });
  ```

  ```ts Fireworks theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  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";

  /** 每个用户相同的技能包：一个共享的存储命名空间。 */
  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));
  }

  /** 从磁盘加载规范技能文件到共享存储命名空间（部署时运行一次）。
   *  您可以从任何来源（本地文件系统、远程 URL 等）检索技能。
   */
  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 的键是*相对于路由后端根目录*的路径。
      // CompositeBackend 在委托前会剥离路由前缀 (`/skills/`)，
      // 因此存储键应类似于 "/<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));
    }
  }

  /** 在每次代理运行前，将共享技能文件从存储复制到沙箱中。 */
  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 路由路径并将批量上传到正确的后端。
          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;
  });
  ```

  ```ts Baseten theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  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";

  /** 每个用户相同的技能包：一个共享的存储命名空间。 */
  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));
  }

  /** 从磁盘加载规范技能文件到共享存储命名空间（部署时运行一次）。
   *  您可以从任何来源（本地文件系统、远程 URL 等）检索技能。
   */
  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 的键是*相对于路由后端根目录*的路径。
      // CompositeBackend 在委托前会剥离路由前缀 (`/skills/`)，
      // 因此存储键应类似于 "/<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));
    }
  }

  /** 在每次代理运行前，将共享技能文件从存储复制到沙箱中。 */
  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 路由路径并将批量上传到正确的后端。
          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;
  });
  ```

  ```ts Ollama theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  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";

  /** 每个用户相同的技能包：一个共享的存储命名空间。 */
  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));
  }

  /** 从磁盘加载规范技能文件到共享存储命名空间（部署时运行一次）。
   *  您可以从任何来源（本地文件系统、远程 URL 等）检索技能。
   */
  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 的键是*相对于路由后端根目录*的路径。
      // CompositeBackend 在委托前会剥离路由前缀 (`/skills/`)，
      // 因此存储键应类似于 "/<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));
    }
  }

  /** 在每次代理运行前，将共享技能文件从存储复制到沙箱中。 */
  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 路由路径并将批量上传到正确的后端。
          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;
  });
  ```
</CodeGroup>

中间件的 `beforeAgent` 钩子在每次代理调用前运行，从该共享命名空间读取技能文件并将其上传到沙箱文件系统。同步后，代理可以使用 `execute` 工具执行脚本，就像沙箱中的任何其他文件一样。

有关更完整的示例，该示例还双向同步[记忆](/oss/javascript/deepagents/memory)，请参阅[使用自定义中间件同步技能和记忆](/oss/javascript/deepagents/going-to-production#example-syncing-skills-and-memories-with-custom-middleware)。

## 技能与记忆

技能和[记忆](/oss/javascript/deepagents/customization#memory)（`AGENTS.md` 文件）服务于不同的目的：

|          | 技能                | 记忆               |
| -------- | ----------------- | ---------------- |
| **目的**   | 通过渐进式披露发现的按需能力    | 启动时始终加载的持久上下文    |
| **加载**   | 仅在代理确定相关性时读取      | 始终注入系统提示         |
| **格式**   | 命名目录中的 `SKILL.md` | `AGENTS.md` 文件   |
| **分层**   | 用户 → 项目（后者优先）     | 用户 → 项目（合并）      |
| **使用场景** | 说明是任务特定的且可能很大     | 上下文始终相关（项目约定、偏好） |

## 何时使用技能和工具

以下是使用工具和技能的一些通用指南：

* 当有大量上下文时使用技能，以减少系统提示中的令牌数量。
* 使用技能将能力捆绑成更大的操作，并提供超出单个工具描述的额外上下文。
* 如果代理无法访问文件系统，则使用工具。

***

<div className="source-links">
  <Callout icon="terminal-2">
    [将这些文档](/use-these-docs)通过 MCP 连接到 Claude、VSCode 等，以获取实时答案。
  </Callout>

  <Callout icon="edit">
    [在 GitHub 上编辑此页面](https://github.com/langchain-ai/docs/edit/main/src/oss/deepagents/skills.mdx) 或 [提交问题](https://github.com/langchain-ai/docs/issues/new/choose)。
  </Callout>
</div>
