> ## Documentation Index
> Fetch the complete documentation index at: https://nvd-54.mintlify.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.
module: index.ts
---

# 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, answer the user's question using the relevant LangGraph docs you fetched.

In your response:

- Give a direct answer first.
- Include the minimum necessary context and any key steps or API names.
- Avoid quoting long passages. Paraphrase and link instead.

### 5. Provide the regular links for the used references

At the end of your response, include a **References** section listing the page URLs you used.

`llms.txt` uses Markdown link targets that typically end in `.md`. Use the helper from this skill module to resolve those into the actual page URLs before listing them as references.

```typescript
const { resolveLlmsUrl } = await import("@/skills/langgraph-docs");

// llms.txt uses Markdown link targets that typically end in `.md`.
// Convert those into the actual page URLs before fetching.
const llmsUrls = [
  "https://docs.langchain.com/oss/langgraph/concepts.md",
  "https://docs.langchain.com/oss/langgraph/concepts.md",
  "https://docs.langchain.com/oss/langgraph/tutorials.md",
];

const pageUrls = [...new Set(llmsUrls.map(resolveLlmsUrl))];
pageUrls;
```
````

引用的辅助代码放在 `index.ts` 中：

```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
// index.ts
export function resolveLlmsUrl(url: string) {
  return url.endsWith(".md") ? url.slice(0, -3) : url;
}
```

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

<Warning>
  **重要**

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

  * `description` 字段如果超过 1024 个字符将被截断。
  * 在深度智能体中，`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
module: index.ts
---

# 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, answer the user's question using the relevant LangGraph docs you fetched.

In your response:

- Give a direct answer first.
- Include the minimum necessary context and any key steps or API names.
- Avoid quoting long passages. Paraphrase and link instead.

### 5. Provide the regular links for the used references

At the end of your response, include a **References** section listing the page URLs you used.

`llms.txt` uses Markdown link targets that typically end in `.md`. Use the helper from this skill module to resolve those into the actual page URLs before listing them as references.

```typescript
const { resolveLlmsUrl } = await import("@/skills/langgraph-docs");

// llms.txt uses Markdown link targets that typically end in `.md`.
// Convert those into the actual page URLs before fetching.
const llmsUrls = [
  "https://docs.langchain.com/oss/langgraph/concepts.md",
  "https://docs.langchain.com/oss/langgraph/concepts.md",
  "https://docs.langchain.com/oss/langgraph/tutorials.md",
];

const pageUrls = [...new Set(llmsUrls.map(resolveLlmsUrl))];
pageUrls;
```
````

## 用法

创建深度智能体时传入技能目录：

<Tabs>
  <Tab title="StateBackend">
    ```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    import { createDeepAgent, StateBackend, type FileData } from "deepagents";
    import { MemorySaver } from "@langchain/langgraph";
    import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

    const checkpointer = new MemorySaver();
    const backend = new StateBackend();

    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({
      model: "openai:gpt-5.4",
      backend,
      checkpointer,
      // IMPORTANT: deepagents skill source paths are virtual (POSIX) paths relative to the backend root.
      skills: ["/skills/"],
      middleware: [createCodeInterpreterMiddleware({ skillsBackend: backend })],
    });

    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,
    );
    ```
  </Tab>

  <Tab title="StoreBackend">
    ```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    import { createDeepAgent, StoreBackend, type FileData } from "deepagents";
    import {
      InMemoryStore,
      MemorySaver,
    } from "@langchain/langgraph";
    import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

    const checkpointer = new MemorySaver();
    const store = new InMemoryStore();
    const backend = new StoreBackend();

    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({
      model: "openai:gpt-5.4",
      backend,
      store: store,
      checkpointer,
      // IMPORTANT: deepagents skill source paths are virtual (POSIX) paths relative to the backend root.
      skills: ["/skills/"],
      middleware: [createCodeInterpreterMiddleware({ skillsBackend: backend })],
    });

    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,
    );
    ```
  </Tab>

  <Tab title="FilesystemBackend">
    ```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    import { createDeepAgent, FilesystemBackend } from "deepagents";
    import { MemorySaver } from "@langchain/langgraph";
    import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

    const checkpointer = new MemorySaver();
    const backend = new FilesystemBackend({ rootDir: process.cwd() });

    const agent = await createDeepAgent({
      model: "openai:gpt-5.4",
      backend,
      skills: ["./examples/skills/"],
      interruptOn: {
        read_file: true,
        write_file: true,
        delete_file: true,
      },
      checkpointer, // Required!
      middleware: [createCodeInterpreterMiddleware({ skillsBackend: backend })],
    });

    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,
    );
    ```
  </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>

## 使用技能执行代码

技能以两种方式支持代码执行：

* 当智能体需要可复用、可导入的辅助函数用于解释器代码时，使用[解释器技能](#use-interpreter-skills)。
* 当智能体需要安装依赖、运行测试、调用 CLI 或使用操作系统文件系统时，在[沙箱中执行技能脚本](#execute-skill-scripts-in-a-sandbox)。

### 使用解释器技能

解释器技能是向[解释器](/oss/javascript/deepagents/interpreters)暴露代码模块的技能。常规技能为智能体提供指令和上下文。解释器技能还为智能体提供可从解释器代码调用的可导入函数。

这让您可以将领域特定逻辑打包一次，并在智能体的工作空间中作为确定性构建块使用。智能体无需要求模型从头重新创建解析器、评分器、规范化器、验证器或聚合例程，而是可以导入经过测试的辅助函数并将其与工具、子智能体和运行时状态组合使用。

解释器技能适用于以下代码：

* **可复用**——跨提示、智能体或项目复用。
* **确定性**——您希望每次都有相同的行为。
* **太详细**——不适合作为指令保留在模型上下文中。
* **在更大工作流中有用**——如评分搜索结果、规范化 API 响应、验证记录、分组行或将数据转换为报告就绪的格式。

要使技能可导入：

<Steps titleSize="h4">
  <Step title="添加 module 条目">
    在技能的 `SKILL.md` frontmatter 中添加 `module` 键。值是相对于技能目录的 JavaScript 或 TypeScript 文件路径。
  </Step>

  <Step title="正常配置技能">
    创建智能体时使用 `skills` 参数传入技能源路径。
  </Step>

  <Step title="使用相同的后端">
    使用与 `SkillsMiddleware` 加载技能文件相同的后端配置解释器中间件。
  </Step>

  <Step title="从解释器代码导入">
    智能体使用 `await import("@/skills/<name>")` 导入辅助模块。
  </Step>
</Steps>

最小技能布局：

```text theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
skills/
`-- order-helpers/
    |-- SKILL.md
    `-- index.ts
```

````md theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
---
name: order-helpers
description: Helper functions for normalizing and grouping order records.
module: index.ts
---

# order-helpers

Use this skill when order records need deterministic cleanup or aggregation.

Import these utilities into the REPL in order to interact with order data:

```typescript
const { groupByStatus } = await import("@/skills/order-helpers");
groupByStatus(...);
```
````

```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
// skills/order-helpers/index.ts
interface Order {
  id: string;
  status: string;
}

export function groupByStatus(orders: Order[]) {
  return orders.reduce((acc, order) => {
    acc[order.status] = acc[order.status] ?? [];
    acc[order.status].push(order);
    return acc;
  }, {});
}
```

然后配置智能体：

```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import { createDeepAgent, StateBackend } from "deepagents";
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const backend = new StateBackend();

const agent = createDeepAgent({
  model: "openai:gpt-5.4",
  backend,
  skills: ["/skills/"],
  middleware: [createCodeInterpreterMiddleware({ skillsBackend: backend })],
});
```

智能体现在可以从解释器代码导入模块：

```javascript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
const { groupByStatus } = await import("@/skills/order-helpers");

const grouped = groupByStatus(orders);
grouped;
```

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

技能可以在 `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";

  /** 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;
  });
  ```

  ```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";

  /** 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;
  });
  ```

  ```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";

  /** 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;
  });
  ```

  ```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";

  /** 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;
  });
  ```

  ```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";

  /** 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;
  });
  ```

  ```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";

  /** 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;
  });
  ```

  ```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";

  /** 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;
  });
  ```
</CodeGroup>

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

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

## 技能 vs. 记忆

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

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

## 何时使用技能和工具

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

* 当有大量上下文需要减少系统提示中的 Token 数量时使用技能。
* 使用技能将能力捆绑成更大的操作，并提供超越单个工具描述的额外上下文。
* 如果智能体无法访问文件系统，使用工具。

<Tip>
  使用 [LangSmith](https://smith.langchain.com?utm_source=docs\&utm_medium=cta\&utm_campaign=langsmith-signup\&utm_content=oss-deepagents-skills) 跟踪您的智能体如何发现和执行技能。按照[可观测性快速入门](/langsmith/observability-quickstart)进行设置。
</Tip>

***

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

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