Deep Agents 6. Agent Skills、SkillsMiddleware 与渐进式披露


1. Agent Skills 解决什么问题

随着 Agent 能处理的任务增加,System Prompt 往往会不断堆入数据库规范、报告格式、异常处理、部署步骤和代码模板。把所有任务说明常驻上下文会带来三个问题:

  1. 当前任务根本用不到的大量指令也会消耗 Token;
  2. 不相关规则相互干扰,模型更难判断本次应该遵守哪一组流程;
  3. 同一套操作说明散落在多个 Prompt 中,很难版本化、测试和复用。

Agent Skills 把任务专用知识组织成独立目录。Agent 启动时只看到用于发现的简要信息;确认相关后再读取完整说明,需要时才读取参考资料、模板或脚本。这就是渐进式披露的基本思想。

1.1 Agent Skills 是开放的目录格式

Deep Agents 遵循 Agent Skills Specification。一个 Skill 至少包含 SKILL.md,也可以携带脚本、参考资料和静态资源。

skills/
└── sales-summary/
    ├── SKILL.md
    ├── scripts/
    │   └── summarize_sales.py
    ├── references/
    │   └── schema.md
    └── assets/
        └── report-template.md

这种文件系统型能力包可以进入 Git,可以进行代码审查,也可以被不同 Agent Harness 读取。Deep Agents 提供的是发现、Prompt 注入和 Backend 集成;目录规范本身并不只属于某一个框架。

1.2 Skill 不是 Tool 插件

Skill 用说明和资源告诉模型“遇到什么问题、应该遵循什么流程”。Tool 则向模型暴露一个可调用的动作及参数 Schema。

Skill 目录里存在 scripts/summarize_sales.py,不代表 Agent 自动获得一个 summarize_sales Tool。模型必须先读取说明,再通过已有的 execute 或受控业务 Tool 运行它。

同样,Frontmatter 中出现 allowed-tools,也不会让 Deep Agents 自动隐藏其他 Tool。可调用能力仍由 Agent、SubAgent、Middleware、权限策略和执行环境决定。

一些教学实现会手写 Skill 列表、load_skill Tool、State 和 Middleware,再在加载 Skill 时动态加入 Tool。这有助于解释动态工具机制,但不能与 Deep Agents 原生 SkillsMiddleware 混为一谈。

1.3 Skill 与 Tool 怎样配合

以销售汇总为例:

Skill:何时使用、字段要求、执行顺序、失败条件、报告格式
Script:用确定性程序计算收入、成本和利润
execute:为模型提供运行程序的入口
Sandbox:决定脚本在哪个隔离环境执行

Skill 不能替代动作接口,Tool 也不适合承载几百行任务说明。两者组合后,模型负责选择流程,确定性代码负责计算,执行环境负责限制副作用。

2. 渐进式披露怎样工作

2.1 三层内容不是同时进入上下文

Agent Skills 渐进式披露

层级 内容 加载者 进入上下文的时机
Level 1:Metadata 规范层主要是 name 和 description SkillsMiddleware 配置的 Skill 被发现时
Level 2:Instructions 完整 SKILL.md 正文 模型调用 read_file 模型判断任务可能匹配时
Level 3:Resources scripts/、references/、assets/ 模型按说明读取或执行 完成任务确实需要时

“按需”不是强制状态机。模型可以不选择任何 Skill,可以同时读取多个 Skill,也可以读取说明后决定不运行脚本。

2.2 State Metadata、实际 Prompt 和规范参考要区分

本文的 sales-summary Frontmatter 是:

---
name: sales-summary
description: 汇总销售 CSV,校验 region、revenue、cost 列并输出收入、成本、利润和数据行数。用户要求销售汇总、区域业绩分析或核对利润时使用。
license: MIT
compatibility: Requires Python 3.12; uses only the Python standard library.
metadata:
  author: deepagent-series
  version: "1.0"
allowed-tools: read_file execute
---

这里存在三个不同表示:

  1. skills_metadata State 保存完整解析对象,包括路径、license、compatibility、任意 metadata 和 allowed_tools;
  2. SkillsMiddleware 根据当前版本的模板生成真实 System Prompt;
  3. skills-ref.to_prompt() 是规范参考库的渲染结果,不等于 Deep Agents Runtime Prompt。

P06 使用 RecordingChatModel 捕获实际模型请求,确认真实 Prompt 包含 Skill 名称、描述和当前版本渲染的 Allowed tools: read_file, execute,但不包含正文中的“不重新心算”。同时断言实际 Prompt 与 skills-ref.to_prompt() 不相等。

因此,不能拿参考库的字符串格式证明 Runtime 一定使用相同 Prompt,也不能把 State 中的所有字段都称为“始终发给模型”。

2.3 一个典型生命周期由谁负责

SkillsMiddleware 与模型的职责泳道

阶段 执行方 是否固定
扫描配置 Source 的直接子目录 SkillsMiddleware
下载并解析 SKILL.md Frontmatter SkillsMiddleware
按名称处理多 Source 覆盖 SkillsMiddleware
更新 skills_metadata State SkillsMiddleware
将 Skill 列表加入模型请求 SkillsMiddleware
判断 Skill 是否相关 模型
调用 read_file 读取正文 模型
读取 references / assets 模型
调用 Tool 或执行脚本 模型与 Runtime
校验业务结果 Skill 指令或应用 取决于实现

对于纯写作规范 Skill,流程可能在读取正文后结束;对于数据 Skill,模型还可能读取 Schema、执行程序并套用模板。不要把包含脚本的示例画成框架内部强制执行的十步 Workflow。

2.4 Metadata 何时重新扫描

deepagents==0.6.12 的准确条件不是“同一 Session 永远只加载一次”,而是:

if "skills_metadata" in state:
    return None

只要传入 State 已经包含 skills_metadata,即使它是空列表,before_agent 也会跳过扫描。若 Checkpointer 在同一 Thread 保存了该 State,外观上就表现为跨 Turn 缓存。

更新磁盘 Skill 后,旧线程不一定立刻看到新 Metadata。可以创建新线程、显式清理对应 State,或者由应用实现受控刷新流程。没有 Checkpointer 的独立调用不应被笼统称为同一 Session。

3. 怎样设计高质量 Skill

3.1 目录职责与加载属性

Agent Skill 目录、上下文与执行属性

文件或目录 发现阶段自动读取 是否进入模型上下文 是否直接执行
SKILL.md Frontmatter Metadata 摘要
SKILL.md Body 模型按需读取
scripts/ 通常无需读取源码 需要 execute 或受控 Tool
references/ 按需读取
assets/ 按需读取或直接复制 通常否

关键资源最好能从 SKILL.md 直接发现,但这是提高可靠性的工程建议,不是规范要求必须逐项列出目录里的所有文件。

3.2 Frontmatter 字段

字段 性质 说明
name 必填 Skill 稳定标识,应与父目录名一致
description 必填 说明做什么以及什么时候使用
license 可选 SPDX 名称或随 Skill 提供的许可说明
compatibility 可选 产品、运行时和依赖要求
metadata 可选 客户端自定义字符串映射
allowed-tools 实验字段 声明预批准 Tool,实际强制程度取决于 Agent 实现

在 Deep Agents 0.6.12 中,allowed-tools 被解析进 Metadata 并进入当前 Prompt 模板,但不会仅凭这一字段创建强制 Tool 白名单。它是模型可见信息,不是安全权限。

3.3 name 和 description 决定身份与路由

name 应保持短小、稳定并符合规范:

sales-summary        good
Sales Summary        bad
-sales-summary       bad
sales--summary       bad

description 是发现阶段最重要的自然语言路由信息。只写“处理销售数据”过于模糊;更好的描述同时包含任务、输入和触发场景:

description: >-
  汇总销售 CSV,校验 region、revenue、cost 列并输出收入、成本、利润和
  数据行数。用户要求销售汇总、区域业绩分析或核对利润时使用。

如果同时存在 sales-summary、sales-forecast 和 sales-anomaly-review,每个描述必须说明自己的输入、输出和不适用边界,否则模型会犹豫或选错。

3.4 正文应该写成可执行流程

一个可维护的 SKILL.md Body 通常包含:

  • 使用条件与不适用条件;
  • 输入字段和前置校验;
  • 可复现的步骤;
  • 何时读取哪个 supporting file;
  • 失败时停止还是降级;
  • 输出格式与验收标准;
  • 不允许猜测或静默修复的边界。

本文 Skill 的关键指令是:

1. 阅读 references/schema.md,校验字段。
2. 运行 scripts/summarize_sales.py <csv-path>3. 使用 assets/report-template.md 输出报告。
4. 不重新心算脚本已经生成的数值。
5. 脚本失败时保留错误并停止生成结论。

3.5 长度限制要标明来源

规则 性质 来源
name 最多 64 字符 规范约束 Agent Skills Specification
description 最多 1024 字符 规范约束 Agent Skills Specification
compatibility 最多 500 字符 规范约束 Agent Skills Specification
SKILL.md 建议不超过 500 行 编写建议 Agent Skills Specification
Body 建议低于 5000 Token 编写建议 Deep Agents Skills 文档
大于 10 MiB 的 SKILL.md 被跳过 版本实现 deepagents==0.6.12 源码
description / compatibility 过长时截断 版本实现 deepagents==0.6.12 源码

运行时截断并不代表 Skill 符合规范。CI 中仍然应该使用严格校验器拒绝超限文件。10 MiB 只是防止异常文件拖垮发现过程的保护值,更不是“适合塞入上下文”的容量建议。

3.6 scripts:确定性逻辑应保持确定

本文脚本使用 Decimal 汇总金额,避免直接使用二进制 float 做财务计算:

from decimal import Decimal, InvalidOperation

def parse_money(value: str | None, *, row_number: int, column: str) -> Decimal:
    normalized = (value or "").strip().replace(",", "")
    if not normalized:
        raise ValueError(f"row {row_number}: {column} must not be empty")
    try:
        amount = Decimal(normalized)
    except InvalidOperation as exc:
        raise ValueError(
            f"row {row_number}: {column} must be a decimal number"
        ) from exc
    if not amount.is_finite():
        raise ValueError(f"row {row_number}: {column} must be finite")
    return amount

当前业务口径明确规定:

  • 空值、NaN、无穷大和非数字输入被拒绝;
  • 负数允许表示退款或冲销;
  • 重复行按独立业务记录计入,不自动去重;
  • 所有记录必须使用同一货币与单位;
  • 结果量化到两位小数。

脚本 CLI 还检查参数数量,以 JSON 输出结果。正式财务系统通常还需要币种字段、汇率日期、会计舍入规则和来源去重键。

3.7 references 与 assets

references/schema.md 保存字段口径、异常值和单位说明,避免把细节全部放入主文件。每个 Reference 应保持单一主题,并尽量只从 SKILL.md 深入一层,减少多次读取和路径迷失。

assets/report-template.md 是任务产物模板。某些 Asset 只需要复制,不需要全文进入 Prompt,例如 Word 模板、Logo、配置骨架或二进制样例。说明文件应写清何时使用、复制到哪里以及哪些占位符必须替换。

4. Skill Source 与 Backend

4.1 三种路径不能混用

Backend 虚拟路径、Host 路径与 Sandbox 路径

路径类型 示例 使用位置
Backend 虚拟路径 /skills/sales-summary/SKILL.md read_file、ls、glob 等文件 Tool
LocalShell 工作目录相对路径 skills/sales-summary/scripts/summarize_sales.py LocalShellBackend.execute
远程 Sandbox 内绝对路径 /workspace/skills/sales-summary/scripts/summarize_sales.py Sandbox execute

当 FilesystemBackend(root_dir=ROOT, virtual_mode=True) 处理 read_file(“/skills/…”) 时,/skills/ 是 Backend 虚拟路径。Shell 不会自动执行同样的路径映射。

本文 LocalShell 测试之所以能运行下面的命令,是因为 root_dir 同时是 Shell cwd:

python skills/sales-summary/scripts/summarize_sales.py data/sales.csv

远程 Sandbox 中必须先同步 Skill 和数据,再使用环境内部的真实路径:

python /workspace/skills/sales-summary/scripts/summarize_sales.py \
  /workspace/data/sales.csv

不要把 Backend VFS 路径直接交给 Shell,也不要假设 virtual_mode=True 会重写命令字符串。

4.2 SDK Source 与产品级自动发现

create_deep_agent(…, skills=[…]) 只加载显式配置的 Source。Deep Agents Code 或 CLI 可以有自己的用户级、Agent 级和项目级发现目录,但目录名称与优先级属于具体产品版本。

SDK 文章不应笼统宣称某四个目录永远会被扫描。需要模拟 CLI 分层时,应依据目标 CLI 版本取得 Source 顺序,再显式传给 skills。

4.3 Source 覆盖、Label 与非法 Skill

多个 Source 按声明顺序加载,解析成功的同名 Skill 使用 last-one-wins:

skills=[
    "/skills/base/",
    "/skills/team/",
    "/skills/project/",
]

P06 不仅测试 project 覆盖 base,还测试了后置 Source 中同名但缺少 description 的非法 Skill。0.6.12 会跳过非法项,因此早先加载的有效 Skill 保留,不会被无效内容覆盖。

以下 API 结论严格限定在 deepagents==0.6.12:

create_deep_agent(..., skills: list[str] | None)
SkillsMiddleware(..., sources: Sequence[str | tuple[str, str]], system_prompt=...)

直接构造 SkillsMiddleware 时可以用 (path, label) 区分同名来源;Label 影响当前 System Prompt 的来源展示,不改变覆盖顺序。create_deep_agent 的 skills 参数仍只接受 list[str]。自定义 Prompt 必须传入包含规定占位符的 system_prompt,或者编写版本特定 Middleware,并不是任意模板都能直接使用。

4.4 State 中已有 Metadata 时跳过扫描

State 是否包含 skills_metadata 才是缓存条件。磁盘文件变化、相同 thread_id 或新的 Run 本身都不是完整判断依据。

加载失败可能包含源路径或 Backend 错误等不可信文本。0.6.12 的实现会把错误放入 区域,使用 JSON 编码和 HTML escape,并将单条警告限制为 1000 字符、最多展示 20 条。P06 用恶意样式字符串验证了转义和截断。

这只是当前版本的防御性渲染,不等于加载错误“绝对安全”。应用仍应限制 Source、目录名和错误内容,不应把警告当成新的模型指令。

4.5 StateBackend 必须注入完整 Skill

默认 StateBackend 没有 Host 目录,需要在输入 State 中提供文件,并使用 create_file_data() 生成 Backend v2 数据结构:

from deepagents.backends.utils import create_file_data

files = {
    "/skills/sales-summary/SKILL.md": create_file_data(skill_text),
    "/skills/sales-summary/scripts/summarize_sales.py": create_file_data(script_text),
    "/skills/sales-summary/references/schema.md": create_file_data(schema_text),
    "/skills/sales-summary/assets/report-template.md": create_file_data(template_text),
}

result = agent.invoke({
    "messages": [{"role": "user", "content": "汇总销售数据"}],
    "files": files,
})

本文的 build_state_skill_files() 读取并注入全部四个文件,测试实际 Agent Prompt 能发现 Skill。

0.6.12 的 files State 使用 Delta reducer:新路径与旧路径合并,同名路径由右侧更新覆盖,值为 None 表示删除。初始化时重复注入正式路径可能覆盖线程里的已有版本,因此输入文件集合也需要版本、哈希和覆盖测试。大文件或跨线程共享库不适合长期放在 State 中。

4.6 StoreBackend 必须注入 Store 和可信身份

from dataclasses import dataclass

from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from langgraph.store.memory import InMemoryStore

@dataclass(frozen=True)
class TenantContext:
    tenant_id: str
    user_id: str

store = InMemoryStore()
backend = CompositeBackend(
    default=StateBackend(),
    routes={
        "/skills/": StoreBackend(
            namespace=lambda runtime: (
                runtime.context.tenant_id,
                runtime.context.user_id,
                "skills",
            ),
        )
    },
)

agent = create_deep_agent(
    model=model,
    backend=backend,
    skills=["/skills/"],
    context_schema=TenantContext,
    store=store,
)

本地程序必须显式传 store=InMemoryStore() 或持久 Store;部署平台可以由 Runtime 提供托管 Store。InMemoryStore 只适合测试和单进程开发,不提供跨进程持久化。

身份应由应用认证层写入 Runtime Context,不能从用户 Prompt 提取。P06 在同一个 Agent 和 Store 中写入 tenant A 与 tenant B 的不同描述,分别调用后捕获真实 System Prompt,确认两个 Namespace 不会串读。

Composite 会在转交 /skills/ route 时移除前缀,因此 Store 中的 Key 是 /sales-summary/SKILL.md,而不是再次保存 /skills/sales-summary/SKILL.md。

4.7 Skill 可读不等于脚本可执行

StateBackend、StoreBackend 和普通 FilesystemBackend 可以让文件 Tool 读取 Skill,但不一定提供可执行 Shell。

例如:

/skills/   -> StoreBackend
default    -> Daytona Sandbox

模型可以从 Store 读取脚本源码,但 Daytona 文件系统中并没有这个文件。应用必须将经过验证的 Skill 版本同步到 Sandbox,只读挂载或上传到固定路径,再执行 Sandbox 内路径。

这也是为什么“拒绝内置 write_file”不能证明脚本不会被复制。只要仍允许任意 execute,Shell 就可以通过 cp、重定向或 Python 写文件。强边界应该来自只读挂载、内容哈希、签名、固定入口包装器和隔离执行环境。

5. Skill、SubAgent、Tool 与 Memory 怎样选择

5.1 四种机制解决的问题不同

Skill、Tool、Memory 与 SubAgent 选型

机制 最适合保存或执行什么 是否按需进入上下文 是否独立上下文
Skill 可复用流程、领域规则、参考资料和脚本
Tool 参数化动作和外部系统能力 Tool Schema 常驻
Memory 用户偏好、项目约定和跨会话事实 取决于实现
SubAgent 独立角色、模型、Tool、上下文和并行任务 委派时

需要“如何完成”时考虑 Skill,需要“执行动作”时使用 Tool,需要“长期记住”时使用 Memory,需要“上下文隔离或角色分工”时使用 SubAgent。

5.2 Skills 与 Multi-Agent 的取舍

Skills 与 Multi-Agent 的关键差异不是哪个更高级,而是任务是否需要独立上下文、独立角色和独立工具配置。

同一个 Agent 偶尔需要 PDF、销售和发布流程时,可以配置多个互不重叠的 Skill;如果任务需要 Analyst 生成结论、Reviewer 独立质疑并返回结构化审核结果,SubAgent 更合适。

两者也可以组合:主 Agent 使用规划 Skill,Analyst SubAgent 使用数据分析 Skill,Reviewer 使用审核规范 Skill。

5.3 general-purpose 与自定义 SubAgent

Deep Agents 自动添加的 general-purpose SubAgent 会继承主 Agent Skills。普通自定义 SubAgent不会自动继承,必须显式配置:

researcher = {
    "name": "researcher",
    "description": "执行资料研究",
    "system_prompt": "只做资料检索和证据整理。",
    "tools": [web_search],
    "skills": ["/skills/research/"],
}

如果用自定义同名 general-purpose 替换默认实例,也不能继续假设自动继承,应检查并显式设置其 Skills。

每个带 Skills 的 Agent 图都有自己的 SkillsMiddleware 和 Metadata State。即使主 Agent 与 SubAgent 共享同一个文件 Backend,也不代表它们自动共享已加载 State。

5.4 按角色预配置 Source

SKILLS_BY_ROLE = {
    "engineering": ["/skills/code-review/", "/skills/testing/"],
    "data": ["/skills/sql-analysis/", "/skills/visualization/"],
    "support": ["/skills/ticket-triage/", "/skills/runbook/"],
}

这属于静态配置分流,不是 Middleware 在一次 Run 中动态改变 Source。生产应用通常在启动阶段构造并缓存少量角色 Agent,避免每个请求重复装配图与 Middleware。

真正按 Runtime Context 解析 Source 时,可以编写受控 Middleware 或使用 StoreBackend Namespace。动态逻辑必须从认证 Context 读取身份,不能信任 Prompt 自报角色。

6. 安全、治理与发布

6.1 allowed-tools 不是强制权限

Agent Skills 规范把 allowed-tools 定义为实验性的预批准 Tool 声明。Deep Agents 0.6.12 会展示它,但不会自动变成强制 Tool allowlist。

安全边界必须由实际 Tool 集合、SubAgent 配置、Middleware、FilesystemPermission、Backend Policy 和 Sandbox 建立。不能因为 Skill 写了:

allowed-tools: read_file execute

就认为其他 Tool 已经不可调用,或者 execute 只能执行 Skill 目录下的程序。

6.2 第三方 Skill 是供应链输入

导入第三方 Skill 等同于同时导入 Prompt、代码、模板和依赖声明。至少要检查:

  • 仓库来源、提交哈希和发布签名;
  • SKILL.md 是否包含越权、外传或关闭安全检查的指令;
  • scripts 内容哈希与静态扫描结果;
  • 依赖 Lockfile、SBOM 和安装脚本;
  • 网络访问域名和数据出口;
  • 需要的 Tool、目录、密钥与资源配额;
  • 运行产物的路径、类型与哈希。

不要允许 Skill 在运行时安装任意最新版本依赖。依赖应由镜像或锁文件提前固定。

6.3 正式 Source 只读,Agent 只生成 Draft

推荐分层:

/skills/system/    平台维护,只读
/skills/team/      审核后发布,只读消费
/skills/user/      受用户身份与审批约束
/draft-skills/     Agent 可以生成草稿

Agent 不应直接修改正式 Source。草稿发布流程至少经过规范校验、代码测试、安全扫描、内容哈希、独立审批和原子发布。

只拒绝 write_file 不足以实现只读,因为 execute 可以绕过文件 Tool。只读必须落实在 Store 授权、对象版本、只读挂载或独立发布服务中。

6.4 规范校验器的真实命令

本文安装的是:

skills-ref==0.1.1
console script: agentskills -> skills_ref.cli:main

因此本地实际命令是:

.venv_deepagent/bin/agentskills validate \
  p06_skills/skills/sales-summary

结果:

Valid skill: p06_skills/skills/sales-summary

Agent Skills 网站或其他版本可能展示 skills-ref validate。Console Script 名称应通过安装版本的 entry point 或 pip show -f skills-ref 核实,不能把某个版本的命令写成永久规范。

6.5 版本字段、哈希和发布策略

metadata.version 只是任意字符串 Metadata,不会自动提供 SemVer 解析、依赖求解、升级或兼容性管理。生产团队需要自行定义:

  • Skill 版本号与兼容范围;
  • 目录或包发布方式;
  • SKILL、脚本和依赖锁的内容哈希;
  • 发布签名和审批记录;
  • 回滚与废弃策略;
  • Agent / Runtime 兼容矩阵。

当前 summarize_sales.py 发布文件的 SHA-256 是:

b5674a03d16a6df6adcb1c994717c42dc45610ed52b830137c4bdc7e126da3ad

哈希需要与代码一起更新。在线执行时还应记录“实际执行文件”的哈希;只记录仓库文件哈希不能证明远程 Sandbox 中运行的是同一内容。

6.6 可观测性记录什么

Skill 激活并不是名为 skill 的独立 Tool Call。观测时应关联:

  1. 当前 State 加载的 Skill 名称、路径和来源;
  2. skills_load_errors 以及被截断的告警数量;
  3. 模型是否调用 read_file 读取 SKILL.md;
  4. 是否读取 references 与 assets;
  5. 实际执行的 Tool、命令、退出码与脚本哈希;
  6. 输入、输出和 Sandbox 产物哈希;
  7. 每次模型请求与整个 Agent Loop 的 Token、延迟和错误。

日志和 Trace 不能记录密钥或敏感文件全文。

7. 测试、真实模型结果与故障排查

7.1 当前离线验证

cd source/_posts/deepagent/examples
.venv_deepagent/bin/python -m ruff check p06_skills
.venv_deepagent/bin/pytest p06_skills -q
.venv_deepagent/bin/agentskills validate \
  p06_skills/skills/sales-summary

实际结果:

All checks passed!
.....................                                                    [100%]
21 passed
Valid skill: p06_skills/skills/sales-summary

覆盖范围包括:

  • Agent Skills 规范校验;
  • skills-ref 与真实 Runtime Prompt 的区别;
  • Metadata 加载与 State 缓存条件;
  • 有效 Source 覆盖和非法后置 Source;
  • Decimal 金额、缺失列、空值、非有限值和 CLI JSON;
  • 完整 StateBackend Skill 文件集合;
  • Runtime Store 注入与多租户 Prompt 隔离;
  • Source Label、加载告警转义与截断;
  • 锁定版本 agentskills Console Script;
  • 原生 Skills Agent 离线构造。

7.2 真实模型端到端记录

仓库中的 live_result.json 保存了一次真实在线记录:

项目
模型端点 第三方 OpenAI 兼容网关 www.juaiapi.com/v1
模型 ID gpt-5.5,属于网关别名,不作为官方模型身份保证
Deep Agents 0.6.12
Backend LocalShellBackend
Host 环境继承 关闭
延迟 15397 ms
累计输入 Token 35518
累计输出 Token 224

实际 Tool 顺序为:

[
  {"name": "read_file", "file_path": "/skills/sales-summary/SKILL.md"},
  {"name": "read_file", "file_path": "/skills/sales-summary/references/schema.md"},
  {"name": "read_file", "file_path": "/skills/sales-summary/assets/report-template.md"},
  {
    "name": "execute",
    "command": "python skills/sales-summary/scripts/summarize_sales.py data/sales.csv"
  }
]

结果是 3 行、收入 300、成本 190、利润 110,与本地确定性断言一致。

35518 个输入 Token 是整个多轮 Agent Loop 的累计值,不是一次三行 CSV 请求的单轮 Prompt。每次模型调用都会再次携带 System Prompt、Tool Schema 和不断增长的消息历史;四次 Tool Call 加最终回答导致累计输入明显放大。优化方向包括缩短全局 Prompt、减少无关 Tool Schema、避免无必要读取模板、使用更窄的 SubAgent,以及检查网关 Usage 统计语义。

本次记录只证明那次请求真实发生了四个 Tool Call。它不能证明所有模型都会按同一顺序工作,也不能证明 allowed-tools 形成强制权限,不能证明 LocalShell 没有其他副作用,更不能证明渐进式披露一定节省总 Token。

本轮将脚本改成 Decimal 并为后续 live_demo 增加执行前后 SHA-256 检查,但当前工作区没有在线凭据,因此没有把旧记录伪装成新代码的在线复验。当前 Decimal、哈希和租户隔离改动只完成了离线测试。执行前后哈希相同也只能发现持久修改,不能代替只读挂载和 Sandbox。

7.3 LocalShell 验证边界

在线记录中的 inherit_env=False 只减少环境变量泄露,不提供 OS 级隔离。LocalShellBackend 仍然拥有完整 Host Shell 能力,可以复制、改写或删除可访问文件。

处理不可信 Skill 必须使用隔离 Sandbox、只读 Skill 挂载、受控入口和网络策略。write_file deny 只能限制内置文件 Tool,不能限制 Shell。

7.4 常见错误

现象 原因 处理方式
Agent 没发现 Skill Source 未传入或 Backend 路径错误 检查 skills、root 和 Prompt Trace
看到 Metadata 但不读取正文 description 与任务不匹配 补充做什么、何时使用和关键词
read_file 成功但脚本路径失败 把 VFS 路径当成 Shell 路径 按执行环境映射实际路径
State Skill 激活后缺文件 只注入了 SKILL.md 注入完整 scripts/references/assets 集合
StoreBackend 缺少 Store Agent 没传 store= 本地注入 Store,部署核对平台 Runtime
租户看到别人的 Skill Namespace 身份不完整或来自 Prompt 使用可信 Runtime Context 并做隔离测试
修改 Skill 后仍显示旧描述 State 已含 skills_metadata 新线程或受控刷新 Metadata State
后置非法 Skill 没有覆盖 解析失败后被跳过 查看日志并运行规范校验器
自定义 SubAgent 看不到 Skill 自定义实例不自动继承 在 SubAgent 中显式配置 skills
禁止 write_file 后仍能写文件 execute 绕过文件 Tool 存储层只读或禁止任意 Shell
校验器命令不存在 Console Script 随版本不同 检查包 entry points

8. 总结

Agent Skills 是开放的文件系统型能力包规范。它用 Metadata、完整说明和 supporting resources 三层披露,把任务专用上下文从全局 Prompt 中移出。SkillsMiddleware 负责 Source 扫描、Frontmatter 解析、State 更新和 Prompt 注入;是否选择 Skill、读取哪些资源和执行什么动作仍然由模型与 Runtime 决定。

Skill 不会自动注册 Tool,allowed-tools 也不是 Deep Agents 0.6.12 中的强制安全白名单。尤其要区分 Backend 虚拟路径、LocalShell 工作目录路径和远程 Sandbox 内路径;禁止 write_file 无法阻止 Shell 写文件。

生产环境应把正式 Skill Source 设为只读,通过 Runtime Context 和 Store Namespace 隔离租户,并为脚本建立 Decimal 等确定性计算、依赖锁、内容哈希、签名、Sandbox、网络限制、发布审批和可观测性。StateBackend、StoreBackend 和 FilesystemBackend 只是不同的存储边界,执行安全仍然需要单独设计。

下一篇将比较 Skills 与 Memory,继续解释哪些信息应该按需加载、哪些项目约定应该始终进入上下文,以及如何使用 Offloading、Summarization 和长期 Store 控制上下文成本。


文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
  目录