《Deep Agents 实战笔记》第 8 篇 | 基于 deepagents (Python) 官方文档 原文:https://docs.langchain.com/oss/python/deepagents/memory 原文:https://docs.langchain.com/oss/python/deepagents/skills
上一篇我们解决了"长任务跑得完"的问题。但还有个更根本的缺憾:Agent 跑完就忘。你告诉它"我喜欢简洁的回复",下次对话它照样长篇大论;你们团队打磨出一套写作规范,每开一个新会话就得从头交代一遍。
Deep Agents 的答案朴素得让人意外:记忆和技能,都是文件系统里的 Markdown 文件。 记忆让 Agent 记住"你是谁",技能让它学会"怎么做"。没有向量数据库,没有微调,就是纯文本。这一篇拆开看这两套机制,最后动手写一个"团队写作规范"技能。
一、记忆的工作方式:读、写、沉淀
Deep Agents 把记忆作为一等公民,采用基于文件系统的记忆,三步循环:
- 指向记忆文件:创建代理时把路径传给
memory=,比如/memories/AGENTS.md; - 代理读取记忆:记忆文件始终加载进系统提示词(与技能的按需加载不同,记忆没有渐进式披露——所以务必精简);
- 代理更新记忆:学到新信息时,用内置的
edit_file工具更新文件,变更持久化,下次对话生效。
按更新时机分两种模式:
| 方式 | 优点 | 缺点 |
|---|---|---|
| 热路径(对话期间写入,默认) | 记忆立即可用,对用户透明 | 增加延迟,代理必须多任务处理 |
| 后台整合(对话之间写入) | 不产生面向用户的延迟,可跨多个对话综合 | 要到下次对话才可用,需要第二个代理 |
后台整合的做法很工程化:部署一个独立的整合代理,读取最近的对话历史、提取关键事实、合并进记忆 store,用 cron 按时间表触发。文档里有个值得抄的细节:cron 间隔必须与整合代理的回看窗口同步——示例里 cron 每 6 小时跑一次,工具就回看 6 小时;跑得太频繁会重复处理,跑得太疏会漏记忆。
另外还有情节记忆:深度代理本来就在用 checkpointer,每次对话作为一个被 checkpoint 的线程持久化——把线程搜索包装成工具,代理就能回看"这个问题当初是怎么解决的",而不只是"学到了什么结论"。
二、记忆放哪:文件态与 Store 态
记忆是文件,但文件存在哪,决定了它的生命周期。这是理解记忆机制的关键一刀:
- 默认
StateBackend(文件态):工作记忆文件存在代理状态里,状态只在单个线程内持久化——会话结束就没了,这是短期记忆; CompositeBackend+StoreBackend(Store 态):把特定路径(通常是/memories/)路由到 LangGraph Store,实现跨线程持久化——这才是长期记忆。
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from langgraph.store.memory import InMemoryStore
store = InMemoryStore() # 部署到 LangSmith 时使用平台 store
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
memory=["/memories/preferences.md"],
skills=["/skills/"],
backend=CompositeBackend(
default=StateBackend(),
routes={
"/memories/": StoreBackend(
namespace=lambda rt: (rt.server_info.user.identity,),
),
},
),
)
Store 的**命名空间(namespace)**就是记忆的作用域,不同的 namespace 函数划出完全不同的记忆边界:
flowchart LR
AG["同一个深度代理"] --> NS1
AG --> NS2
AG --> NS3
subgraph NS1["命名空间:assistant_id"]
M1["代理级记忆<br/>所有用户共享"]
end
subgraph NS2["命名空间:user_id"]
M2["用户 A 的记忆"]
M3["用户 B 的记忆"]
end
subgraph NS3["命名空间:org_id"]
M4["组织策略与合规规则<br/>通常设为只读"]
end
style AG fill:#eef4ff,color:#1a3a8b,stroke:#4a72d9
style NS1 fill:#f0f7f0,color:#1d4a1d,stroke:#4a9e4a
style NS2 fill:#fbf3e6,color:#6b4a12,stroke:#c99a3d
style NS3 fill:#fdeef0,color:#701d2a,stroke:#c94a5e
正在渲染图表…
代理级记忆(assistant_id)让代理拥有随时间演进的持久身份;用户级记忆(user_id)保证用户 A 的偏好绝不泄漏到用户 B 的对话;组织级记忆放合规规则,通常设为只读——这里有个安全考量值得记住:如果一个用户可以写入另一个用户会读取的记忆,恶意用户就能向共享状态注入指令。缓解手段是默认用户作用域、共享策略只读、写共享记忆前加人机协同审批。
还有个现实问题:多个线程并行写同一份记忆文件会"后写胜出"。文档的建议很务实:代理级或组织级记忆用后台整合来串行化写入,或者把记忆按主题拆成独立文件减少竞争。单次写入丢失也不用慌——LLM 通常足够聪明,能重试或优雅恢复。
三、技能:SKILL.md 与渐进式披露
如果说记忆是"始终在场的背景知识",技能就是"按需取用的操作手册"。一个技能是一个包含 SKILL.md 的目录,遵循 Agent Skills 规范:frontmatter 里写 name 和 description,正文是技能被激活时代理要遵循的指令,目录里还可以带脚本、参考文档和模板。
它解决的是上下文膨胀的老问题:把所有指令塞进系统提示词,会在与当前任务无关的信息上浪费 token。Deep Agents 的解法是渐进式披露(progressive disclosure)——分三级加载,每一级只在任务需要时才引入更多细节:
| 级别 | 加载内容 | 时机 |
|---|---|---|
| 1. 元数据 | frontmatter 中的 name 和 description | 代理启动时,对所有技能 |
| 2. 指令 | 完整的 SKILL.md 正文 | 技能被调用时 |
| 3. 资源 | scripts/、references/、assets/ 辅助文件 | 指令引用到它们时 |
flowchart TB
subgraph L1S["第 1 级:启动时"]
A["扫描技能路径,解析 frontmatter<br/>只注入 name 和 description"]
end
subgraph L2S["第 2 级:技能被激活"]
B["read_file 读取完整 SKILL.md 正文"]
end
subgraph L3S["第 3 级:指令需要时"]
C["按需读取 scripts / references / assets"]
end
A -->|"任务与某技能描述匹配"| B
B -->|"指令引用到辅助文件"| C
style A fill:#f0f7f0,color:#1d4a1d,stroke:#4a9e4a
style B fill:#fbf3e6,color:#6b4a12,stroke:#c99a3d
style C fill:#f4effb,color:#4a2170,stroke:#8b52c9
正在渲染图表…
启动时,系统提示词里只有每个技能的一行摘要;任务与某个技能的描述匹配时,代理才 read_file 读全文;辅助文件更是直到指令点名才加载。加载越深,细节越多,上下文却始终只装当前需要的东西。
这也解释了为什么描述字段是技能的生死线:发现阶段,代理对每个技能唯一能看到的就是 description。官方给的好坏对比非常直白:
# 好:明确说明做什么、何时使用,带可匹配的关键词
description: >-
Extract text and tables from PDF files, fill PDF forms, and merge
multiple PDFs. Use when working with PDF documents or when the user
mentions PDFs, forms, or document extraction.
# 差:过于模糊,难以可靠匹配
description: Helps with PDFs.
写技能的几条硬规矩(都来自官方建议):frontmatter 简洁,正文控制在 5000 token 以内(规范建议 500 行以内);描述相近的多个技能会让代理选错或犹豫,用途相近就合并;详细参考资料移入辅助文件、在正文中引用,引用距 SKILL.md 不超过一层,避免深层嵌套的引用链。
四、技能组合与动态技能列表
技能不是孤立的文件,它有一整套"组合拳":
多来源叠加,后者优先。 skills 数组可以传多个路径,同名技能靠后的来源覆盖靠前的——用项目专属版本覆盖基础技能,分层叠加:
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
skills=["/skills/org/", "/skills/team/", "/skills/request/"],
)
按上下文动态选择。 技能列表可以在创建代理之前由应用逻辑决定——按角色、租户或请求类型给不同的技能集:
SKILLS_BY_ROLE = {
"engineering": ["/skills/engineering/"],
"data": ["/skills/data/"],
"support": ["/skills/support/"],
}
def create_agent_for_user(user_role: str):
return create_deep_agent(
model="anthropic:claude-sonnet-4-6",
skills=SKILLS_BY_ROLE.get(user_role, []),
)
用 Backend 隔离技能库。 更彻底的做法是代理配置不动,让 CompositeBackend 把 /skills/ 路由到以当前身份为范围的 store——同一个 skills=["/skills/"],不同用户解析出不同的技能库。还可以组合共享库和个人库:/skills/approved/ 路由到组织级(配合权限规则设为只读),/skills/editable/ 路由到用户级(可写),让代理既能用审批技能、又能沉淀自己的技能。
线程内只加载一次。 技能在每个线程中只加载一次并保存在代理状态里,此后新增、修改、删除都不会到达模型。要让变更生效,把 "skills_metadata": None 作为运行输入传入(需要 deepagents>=0.7.16),或用 agent.update_state(config, {"skills_metadata": None}) 重置线程。注意是 None 而不是空列表——空列表意味着"来源已加载但没有任何技能",代理整个线程都看不到任何技能。
用户点名时固定技能。 当应用里用户输入 /langgraph-docs 这种点名语法,把名称解析出来传进 pinned_skills(需要 deepagents>=0.7.23),中间件会把该技能的指令直接追加进对话,代理无需自己选。
五、技能 × 工具 × 沙箱 × 子代理
技能真正的威力在于和其他机制联动。
技能自带工具。 在 frontmatter 的 metadata.include_tools 里列出工具名(注意是空格分隔的字符串,YAML 列表形式永远匹配不到),代理读了技能之后这些工具才可见——schema 在任务需要之前不进提示词(需要 deepagents>=0.7.22)。而且工具是受门控的:没读 SKILL.md 就调用会失败,即使模型猜对了名称和参数。还能传一个解析器函数做名称映射,比如把 linear 一个名字映射到 Linear MCP 服务器的全部工具,甚至按运行时上下文为每个用户返回不同的工具。
技能调用沙箱。 代理可以从任何 Backend 读取脚本,但要执行脚本就需要能访问 shell——只有沙箱 Backend 才提供。沙箱跑在隔离容器里,容器外的技能文件默认不可用,所以要配一个同步中间件:运行前把技能文件从 store 复制进沙箱,运行后把更新写回:
sequenceDiagram
participant App as 应用代码
participant ST as Store 技能库
participant MW as 同步中间件
participant SB as 沙箱
participant AG as 代理
App->>ST: 部署时播种共享技能
App->>MW: 每次运行前触发
MW->>ST: 读取共享技能文件
MW->>SB: 上传到沙箱
AG->>SB: 执行技能脚本
AG->>MW: 运行结束
MW->>ST: 回写更新或新建的技能文件
正在渲染图表…
子代理的技能隔离。 规则很简单:通用子代理自动继承主代理的技能;自定义子代理不会继承,需要在每个子代理定义里显式加 skills 参数。而且技能状态完全隔离——主代理的技能对子代理不可见,反之亦然。给研究员子代理配专属技能、写报告子代理配写作技能,互不干扰。
六、实战作业:写一个"团队写作规范"技能
把机制落到手上。假设你们团队有一套博客写作规范,每个新会话都要口头交代——这正是技能的教科书场景。建一个技能目录:
skills/
└── team-writing-style/
├── SKILL.md
└── references/
└── tone-examples.md
SKILL.md 的内容(注意 description 的写法——做什么、何时用、带关键词):
---
name: team-writing-style
description: >
Apply our team's Chinese tech-blog writing style when drafting,
reviewing, or editing any blog post or technical article. Use when
the user asks to write, revise, or polish a blog post, article,
or any publishable technical content.
---
# team-writing-style
写任何可发布的技术内容前,遵循以下规范:
## 硬性规则
1. 开头第一段不超过 3 句话,直接给出文章要解决什么问题;
2. 每个章节配一个可运行的代码示例,禁止只讲概念不给代码;
3. 全文使用"你"称呼读者,禁止使用"我们建议"这类句式;
4. 术语中英对照首次出现时标注英文原文,之后只用中文。
## 参考资料
- 历史优秀文风示例见 [tone-examples.md](references/tone-examples.md),
动笔前先读一遍校准语感。
然后用 FilesystemBackend 挂载并验证(技能存在磁盘上时无需上传即可加载):
from deepagents import create_deep_agent
from deepagents.backends.filesystem import FilesystemBackend
backend = FilesystemBackend(root_dir="./my-project")
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-6",
backend=backend,
skills=["./my-project/skills/"],
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "帮我写一篇介绍 LangGraph 的博客开头"}]},
config={"configurable": {"thread_id": "1"}},
)
怎么验证技能生效了? 三个观察点:第一,开一个 LangSmith trace,启动时的系统提示词里应该只有 team-writing-style 的 name 和 description,而没有正文——这就是第 1 级;第二,看 trace 里有没有一次针对 SKILL.md 的 read_file 调用——这是第 2 级被激活的标志;第三,输出应该符合规范(第一段三句话以内、用了"你"),而当你问一个和写作完全无关的问题(比如"今天天气怎么样")时,不应该出现那次 read_file——按需加载没有误触发。如果技能没被激活,先回头检查 description 是不是太模糊。
七、本篇踩坑点
- 记忆写得太肥:记忆没有渐进式披露,始终注入系统提示词。把流程细节塞进记忆而不是技能,等于每轮对话都在为它付 token。
- description 写成"Helps with X":发现阶段全靠它匹配,模糊描述 = 技能永远不被激活。技能没生效时,第一个该查的就是 description。
- 路径层级搞错:
skills的每一项要指向包含若干技能目录的目录。直接指向某个带SKILL.md的技能目录,什么也不会发生,而且不报错。 - 改了技能不生效:技能每线程只加载一次。部署环境有持久化 checkpointer,整个线程生命周期内都持有旧技能列表——记得重置
skills_metadata。 - include_tools 写成 YAML 列表:必须是空格分隔的字符串,这是文档里明确点名的高频错误。
记忆和技能都靠文件系统承载,但有一类工作它们罩不住:Agent 要写代码、跑数据分析、执行技能附带的脚本——这些操作放出去跑,怎么保证安全?下一篇我们讲代码执行:本地 shell、远程沙箱、轻量解释器三种姿势,以及各自的选型逻辑。
参考资料:
- Deep Agents 记忆(官方文档,本文素材之一):https://docs.langchain.com/oss/python/deepagents/memory
- Deep Agents 技能(官方文档,本文素材之二):https://docs.langchain.com/oss/python/deepagents/skills
- Agent Skills 规范:https://agentskills.io/specification
- Deep Agents 示例技能仓库:https://github.com/langchain-ai/deepagents/tree/main/libs/code/examples/skills