Skill
Agent Skills 把可复用的任务封装为“技能目录”,用 SKILL.md
描述目标与流程,并配套脚本与文档。在对话中,Agent 只注入
“低成本的概览”,在确有需要时再按需载入正文与文档,并在
隔离工作区中安全执行脚本,从而降低上下文占用与泄漏风险。
参考背景: - Anthropic 工程博客: https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills - 开源 Skills 示例库(结构与约定可借鉴): https://github.com/anthropics/skills
概览
🎯 能力一览
- 🧭 内置两种工具档位:
full(默认)与knowledge_only - 🔎 自动注入技能“概览”(名称与描述),引导模型选择
- 📥
skill_load按需注入SKILL.md正文与选定文档 - 📚
skill_select_docs增/改/清除文档选择 - 🧾
skill_list_docs列出可用文档 - 🏃
skill_run在full档位下于工作区执行命令,返回 stdout/stderr 与输出文件 - ⌨️
skill_exec与 session 工具在full档位下处理交互式 stdin/TTY 流程 - 🗂️ 按通配符收集输出文件并回传内容与 MIME 类型
- 🧩 可选择本地或容器工作区执行器(默认本地)
- 🧱 支持声明式
inputs/outputs:映射输入、 以清单方式收集/内联/保存输出
核心概念:三层信息模型
1) 初始“概览”层(极低成本)
- 仅注入 SKILL.md 的 name 与 description 到系统消息。
- 让模型知道“有哪些技能、各做什么”,但不占用正文篇幅。
2) 正文层(按需注入)
- 当任务确实需要某技能时,模型调用 skill_load,框架把该
技能的 SKILL.md 正文物化到下一次模型请求中(详见下文
Prompt Cache 小节)。
3) 文档/脚本层(精确选择 + 隔离执行)
- 关联文档按需选择(通过 skill_load 或 skill_select_docs),
仅把文本内容物化到提示词;脚本不会被内联,而是在工作区中
执行,并回传结果与输出文件。
Token 成本
如果把一个技能仓库的全部内容(所有 SKILL.md 正文与 docs)
一股脑塞进提示词,往往会让 prompt token 占用变得非常高,甚至
直接超过模型上下文窗口。
想要可复现、基于真实运行的 token 对比(渐进披露 vs 全量注入),
可参考 benchmark/anthropic_skills/README.md,并按其中说明运行
token-report 套件。
Prompt Cache
一些模型服务支持 prompt cache:如果后续一次模型请求的开头 (token 前缀)与之前某次请求完全一致,服务端可以复用这段共同 前缀,从而减少计算,并降低延迟和/或输入 token 成本(取决于服务商)。
对于 Skills,“已加载的 SKILL.md / docs”落在消息序列的哪里,会影响
连续模型调用之间可复用的前缀长度:
- 旧行为(默认):把已加载内容追加到 system message。
- 这会在 user/history 之前插入新 token,导致连续模型调用的共同前缀 变短。
- Tool-result 物化(可选):把已加载内容追加到对应的 tool result
消息(
skill_load/skill_select_docs)。- system message 更稳定,早期消息更不容易“后移”,prompt cache 往往能 命中更多前缀 token。
回退机制:如果对应的 tool result 消息不在本次请求的 history 里 (例如启用了 history suppression),框架可以回退为插入一条专用的 system message,确保模型仍能看到已加载内容。
Session summary 提醒:如果你启用了会话摘要注入
(WithAddSessionSummary(true)),并且本次请求里确实插入了摘要,
框架会尽量跳过这条回退 system message,避免把“已被 summary 掉的
内容”又塞回提示词里。如果对应的 tool result 仍在提示词里,回退会继续
保持关闭;如果 same-turn summary compaction 已经把这些 tool result
裁掉,回退会重新开启,保证模型仍能看到完整正文/文档。
启用方式:llmagent.WithSkillsLoadedContentInToolResults(true)。
如果你希望在 summary 场景恢复旧的回退行为:
llmagent.WithSkipSkillsFallbackOnSessionSummary(false)。
要在真实工具链路中测量提升,参见 benchmark/anthropic_skills 的
prompt-cache 套件。
与 SkillLoadMode 的关系(容易踩坑):
- 上面讨论的“缓存前缀变短/变长”,主要发生在同一次
Runner.Run里多次调用模型的场景(一次用户消息触发多个 tool call)。 - 如果你希望跨多轮对话继续复用“已加载技能的正文/文档”,需要把
SkillLoadMode设为session。默认turn会在下一轮开始前清空 该 agent 的 skill state key(temp:skill:loaded_by_agent:<agent>/<name>/temp:skill:docs_by_agent:<agent>/<name>),因此即使 history 里仍然 有上一轮的skill_loadtool result(通常是loaded: <name>这种短 stub),框架也不会再把正文/文档物化进去。
实践建议(尤其是 WithSkillsLoadedContentInToolResults(true) 时):
- 先确认你在讨论哪种“缓存”场景:
- 同一轮对话内(一次
Runner.Run里多次调用模型):turn与session基本等价,因为它们都会让“本轮已加载内容”在该轮内可见。 更关键的开关通常是“注入到 system 还是 tool result”。 - 跨多轮对话:
session可能更利于 prompt cache,因为你只需加载一次, 后续不必反复skill_load;但代价是上下文更大、需要更主动地管理清理。
- 同一轮对话内(一次
- 经验法则:
- 默认用
turn(最小权限、上下文更小、也更不容易触发截断/summary)。 - 仅对“整段会话都会反复用到”的少量技能用
session,并严格控制 docs。
- 默认用
- 严格控制 docs 选择(尽量不要
include_all_docs=true),否则很容易把 上下文塞爆,进而触发 history 截断/summary,导致回退为 system message, prompt cache 的收益会下降。
会话持久化
先区分两个概念:
- Session(持久化):保存事件流(用户消息、助手消息、工具调用/结果)
- 一份小的键值 state map。
- 模型请求(一次性的):本次发给模型的
[]Message,由 Session + 运行时配置拼出来。
skill_load 只会把“已加载/已选文档”的小状态写入 Session(例如
temp:skill:loaded_by_agent:<agent>/<name>、
temp:skill:docs_by_agent:<agent>/<name>;旧版 key 也仍被支持)。
随后由请求处理器在
下一次模型请求里,把对应的 SKILL.md 正文/已选 docs 物化
进去。
重要:物化不会把“扩展后的 tool result 内容”写回 Session。
所以如果你去看 Session 里保存的工具结果,skill_load 仍然通常是
一个很短的 stub(比如 loaded: internal-comms)。但模型在每次请求
里仍能看到完整正文/文档,因为它们是在构造请求时注入的。
补充:SkillLoadMode 控制的是这些 state key 的生命周期,所以也决定了
“下一轮对话”里是否还能继续物化正文/文档。
后续请求的稳定性:
- 在同一次工具链路里,每次模型调用前都会按同一套规则重新物化,
所以只要 skills 仓库内容和选择状态不变,模型看到的 skill 内容
就是稳定的。
- 如果本次请求的 history 里找不到对应的 skill_load /
skill_select_docs tool result(常见原因:history suppression、
会话摘要、或截断把这些 tool 消息移除),框架可以回退为插入一条专用
system message(Loaded skill context:),把缺失的 skill 正文/文档
补回来,确保模型仍能看到正确上下文。
- 但这会改变 system 内容,prompt cache 的收益可能变小;因此当本次请求
存在 session summary 时,该回退默认会被跳过(见上文)。
与业界实现对比
很多框架为了更友好地利用 prompt cache,会尽量避免在多步工具链路中 不断改写 system prompt,而是把动态上下文放到 tool 消息(工具结果) 里,让 system 更稳定。
一些例子:
- OpenClaw:system prompt 列出可用 skills,但选中 skill 的 SKILL.md
会要求通过工具读取(正文落在 tool result 里):
https://github.com/openclaw/openclaw/blob/0cf93b8fa74566258131f9e8ca30f313aac89d26/src/agents/system-prompt.ts
- OpenAI Codex:项目文档中渲染 skills 列表,并要求按需打开 SKILL.md
(正文来自读文件工具的 tool result):
https://github.com/openai/codex/blob/383b45279efda1ef611a4aa286621815fe656b8a/codex-rs/core/src/project_doc.rs
在 trpc-agent-go 中:
- 旧模式:把已加载的 skill 正文/文档追加到 system message
(简单、兼容旧语义,但可能缩短可缓存的前缀)。
- 新模式(可选):保持 system 更稳定,把已加载内容物化到 skill_load /
skill_select_docs 的 tool result 消息中(更接近“工具消息承载动态上下文”
的主流模式)。
目录结构
仓库与解析: skill/repository.go
快速开始
1) 环境准备
- Go 1.21+
- 一个模型服务的 API Key(OpenAI 兼容)
- 可选:Docker(使用容器执行器时)
常用环境变量:
2) 启用 Skills
在 LLMAgent 里提供技能仓库即可。
仅知识注入模式:
要点:
- 请求处理器注入概览与按需内容:
[internal/flow/processor/skills.go]
(https://github.com/trpc-group/trpc-agent-go/blob/main/internal/flow/processor/skills.go)
- WithSkills 会自动注册内置 skill 工具,无需手动添加。
- 默认 full 档位:skill_load、skill_select_docs、
skill_list_docs、skill_run、skill_exec、
skill_write_stdin、skill_poll_session、skill_kill_session。
- knowledge_only 档位:只注册 skill_load、
skill_select_docs 与 skill_list_docs。
- 执行器是否需要,也取决于档位:
full 通常还需要 WithCodeExecutor(...);
knowledge_only 则不需要。
- 注意:当你同时设置了 WithCodeExecutor 时,LLMAgent 默认会尝试执行
模型回复里的 Markdown 围栏代码块。如果你只是为了给 skill_run 提供运行时,
不希望自动执行代码块,可以加上
llmagent.WithEnableCodeExecutionResponseProcessor(false)。
- 默认提示指引:框架会在系统消息里,在 Available skills: 列表后追加一段
Tooling and workspace guidance: 指引文本。
- 关闭该指引(减少提示词占用):llmagent.WithSkillsToolingGuidance("")。
- 或用自定义文本替换:llmagent.WithSkillsToolingGuidance("...")。
- 如果你关闭它,请在自己的指令里明确当前档位下哪些 skill 工具可用。
- 加载器: tool/skill/load.go
- 运行器: tool/skill/run.go
3) 运行示例
交互式技能对话示例: examples/skillrun/main.go
这个 demo 以及其他以 skill 为中心的示例
(skill、skilldynamicschema、structuredoutputskills)
都显式设置了 llmagent.WithEnableCodeExecutionResponseProcessor(false),
避免在启用 skill_run 时自动执行 assistant 文本里的围栏代码块。
GAIA 基准示例(技能 + 文件工具): examples/skill/README.md
该示例包含数据集下载脚本,以及 whisper(音频)/ocr(图片)等
技能的 Python 依赖准备说明。
SkillLoadMode 演示(无需 API key): examples/skillloadmode/README.md
SkillToolProfile 演示(无需 API key): examples/skilltoolprofile/README.md
子代理 skill 隔离演示(AgentTool + Skills): examples/skillisolation/README.md
快速开始(下载数据集 JSON 到 examples/skill/data/):
如需同时下载引用到的附件文件:
示例技能(节选): [examples/skillrun/skills/python_math/SKILL.md] (https://github.com/trpc-group/trpc-agent-go/blob/main/examples/skillrun/skills/python_math/SKILL.md)
自然语言交互建议:
- 直接说明你要做什么;模型会根据概览判断是否需要某个技能。
- 当需要时,模型会先调用 skill_load 注入正文/文档,再调用
skill_run 执行命令并回传输出文件。
- 在 knowledge_only 档位下,模型仍可加载 skill 正文/文档,但只能将其
作为知识与操作指引,不能执行 skill 脚本。
SKILL.md 结构与示例
SKILL.md 采用 YAML 头信息 + Markdown 正文:
建议:
- 头信息的 name/description 要简洁,便于“概览注入”
- 正文给出“使用时机”“步骤/命令”“输出文件位置”等
- 把脚本放入 scripts/,命令中引用脚本路径而非内联源码
更多可参考 Anthropic 的开源库: https://github.com/anthropics/skills
工具用法详解
skill_load
输入:
- skill(必填):技能名
- docs(可选):要包含的文档文件名数组
- include_all_docs(可选):为 true 时包含所有文档
行为:
- 写入会话临时键(按 agent 隔离,生命周期由 SkillLoadMode 控制):
- temp:skill:loaded_by_agent:<agent>/<name> = "1"
- temp:skill:docs_by_agent:<agent>/<name> = "*" 或 JSON 字符串数组
- 旧版 key(temp:skill:loaded:<name>、temp:skill:docs:<name>)仍被支持,
并在读到时自动迁移。
- 多代理提示:transfer 调用子代理时通常共享同一个 Session。key 按 agent
隔离后,子代理的 skill_load 不会自动把技能正文/文档“塞进”主代理的提示词。
如果主代理确实需要该技能正文/文档,请让主代理自己调用一次 skill_load。
- 请求处理器读取这些键,把 SKILL.md 正文与文档物化到下一次模型请求中:
- 默认:追加到系统消息(兼容旧行为)
- 可选:追加到对应 tool result 消息
(llmagent.WithSkillsLoadedContentInToolResults(true))
在每次模型请求前获取“已加载技能列表”
如果你希望在每次模型请求前拿到当前 llmagent 已加载的 skill 列表
(包括一次 Runner.Run 内的 tool loop 里每一步),推荐使用
ModelCallbacks 的 BeforeModel,并通过 context 取出当前
Invocation。
从最基本的机制出发理解:
skill_load并不会把完整的SKILL.md正文写进 session 的事件记录里。- 它只会往 session state 写入一些很小的“标记键”(按 agent 隔离):
- Loaded 标记:前缀为
skill.LoadedPrefix(inv.AgentName)的 key - Docs 选择:前缀为
skill.DocsPrefix(inv.AgentName)的 key
- Loaded 标记:前缀为
- Skills 的请求处理器会读取这些键,把正文/文档物化到下一次 outbound 模型请求里。
因此,如果你只是想拿到“当前哪些 skills 已加载”,只需要读 session state 即可:
下面的代码片段中,m 表示你的模型实例,repo 表示 skills 仓库。
注意:
- 默认
SkillLoadModeTurn会在下一轮Runner.Run开始前清空这些 该 agent 的temp:skill:loaded_by_agent:*/temp:skill:docs_by_agent:*state key,所以“已加载列表”通常只在当前这轮 tool loop 里非空。 SkillLoadModeSession会跨轮保留这些 key,所以“已加载列表”会一直 非空,直到你手动清空(或会话过期)。
内置选项:限制已加载技能数量(TopK)
如果你的需求只是“最多保留最近 N 个已加载 skills”,可以直接使用内置 option:
llmagent.WithMaxLoadedSkills(N)
它会在每次模型请求前检查当前 loaded skills,并根据 session 中最近
的 skill_load / skill_select_docs tool result,清空更老的
temp:skill:* state key,从而把 loaded skills 数量控制在 N 以内。
示例:
自定义策略:限制已加载技能数量(比如最多保留最近 3 个)
SkillLoadMode 解决的是“驻留多久”(once/turn/session),不解决
“最多加载多少个”。如果你需要在 llmagent.WithMaxLoadedSkills 之外
做更细粒度的手动控制(比如自定义淘汰策略),推荐在 session service
上加 AppendEventHook,去修改 skill_load 写入的 state delta。
核心思路:
1) 识别“加载 skill”的事件(state delta 里包含
该 agent 的 loaded key,即 key 前缀为 skill.LoadedPrefix(ev.Author))。
2) 计算“应用该 delta 后”会有哪些 skills 处于 loaded 状态。
3) 如果数量超过阈值,在同一个 StateDelta 里把要淘汰的 skills 对应
key 置为 nil(清空)。
示例(inmemory session service,最多保留最近 3 个 loaded skills):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | |
AppendEventHook 的接口说明见 docs/mkdocs/zh/session.md,也可以参考
可运行示例 examples/session/hook。
说明:
- 建议采用“渐进式披露”:默认只传 skill 加载正文;需要文档时先
skill_list_docs 再 skill_select_docs,只选必要文档;除非确
实需要全部(或用户明确要求),避免 include_all_docs=true。
- 可多次调用以新增或替换文档。
- 工具会写入 session state,但正文/文档在提示词里驻留多久取决
于 SkillLoadMode:
- turn(默认):在当前一次 Runner.Run(处理一条用户消息)
的所有模型请求中驻留;下一次运行开始前自动清空。
- once:只在下一次模型请求中注入一次,随后自动 offload
并清空对应 state。
- session(兼容旧行为):跨多轮对话保留,直到手动清除或会话过期。
- 常见疑问:为什么你在 tool result 里只看到 loaded: <name>,没看到
[Loaded] <name> + 正文?
- 先确认你是否开启了 tool-result 物化:
llmagent.WithSkillsLoadedContentInToolResults(true)。
未开启时,正文/文档会被追加到 system message,而不是 tool result。
- 如果已开启,但你看到“第二轮对话”的请求里仍只有 stub,通常是因为
你使用了默认的 SkillLoadModeTurn:下一轮开始前框架会清空 state,
于是不会再物化正文/文档。需要跨轮保留时,改用:
多轮对话示例:复用同一个 sessionID 才能让“已加载状态”跨轮生效:
清空建议(SkillLoadModeSession 下很常见):
- 最简单:换一个新的
sessionID开启新对话。 - 或者由上层删除 session(以 inmemory 为例):
提示:tool-result 物化依赖本次请求的 history 中包含对应的 tool result
消息;如果 history 被截断/抑制,框架会回退为插入专用 system message
(Loaded skill context:)来保证正确性。
- 在 agent 上配置:
配置片段:更利于 prompt cache 的常用组合(system 更稳定 + 只在本轮驻留):
配置片段:一次性注入(只让下一次模型请求看到正文/文档):
skill_select_docs
输入:
- skill(必填)
- docs(可选数组)
- include_all_docs(可选布尔)
- mode(可选字符串):add | replace | clear
行为:
- 更新当前 agent 的 doc 选择 state key:
- temp:skill:docs_by_agent:<agent>/<name>:* 表示全选;数组表示显式列表
- 旧版 key temp:skill:docs:<name> 仍被支持,并在读到时自动迁移
skill_list_docs
输入:
- skill(必填)
输出: - 可用文档文件名数组
提示:这些会话键由框架自动管理;用户通常无需直接操作,仅需用 自然语言驱动对话即可。
skill_run
输入:
- skill(必填):技能名
- command(必填):Shell 命令(默认通过 bash -c 执行)
- cwd(可选):相对技能根目录的工作路径
- env(可选):环境变量映射
- stdin(可选):一次性写入命令的标准输入文本
- editor_text(可选):用于会拉起 $EDITOR 的 CLI 的文本内容
- output_files(可选,传统收集方式):通配符列表
(如 out/*.txt)。通配符以工作区根目录为准,也支持
$OUTPUT_DIR/*.txt 这类写法,会自动归一化为 out/*.txt。
- inputs(可选,声明式输入):把外部资源映射进工作区,
结构为对象数组,每项支持:
- from:来源,支持四类方案(scheme):
- artifact://name[@version] 从制品服务拉取文件
- host:///abs/path 从宿主机绝对路径复制/链接
- workspace://rel/path 从当前工作区相对路径复制/链接
- skill://<name>/rel/path 从已缓存的技能目录复制/链接
- to:目的路径(相对工作区)。未指定时默认写到
WORK_DIR/inputs/<basename>。为了方便,skill_run 也支持
to 以 inputs/ 开头的写法,会被视为 work/inputs/
(因为技能根目录下 inputs/ 是指向 work/inputs/ 的软链)。
- mode:copy(默认)或 link(在可行时建立符号链接)。
- pin:当 from=artifact://name 未指定 @version 时,
尝试复用同一 to 路径第一次解析到的版本(best effort)。
- 会话文件输入(自动 stage):
- 如果当前会话的用户消息中包含文件输入(
content_parts中type=file的条目),skill_run会在执行命令前自动把它们 写入work/inputs/(也会出现在inputs/)。 - 文件名会清洗为安全的 basename,并在重名时追加数字后缀。
- 若文件输入未提供文件名且
file_id为artifact://...引用, 框架会从制品名推断 basename;否则回退为upload_N。 - 若文件输入包含原始字节(
data),会直接写入工作区。 - 若文件输入仅包含
file_id:- 当
file_id以artifact://开头时,skill_run会通过制品 服务拉取对应内容并写入工作区(适用于用户上传文件已先保存 为制品,仅在消息中写入引用的场景)。 - 否则,框架会在模型支持下载时拉取内容并写入工作区。
- 当
- 如果当前会话的用户消息中包含文件输入(
outputs(可选,声明式输出):使用清单(manifest)收集输出。 字段:globs:通配符数组(相对工作区,支持**,也支持$OUTPUT_DIR/**这类写法并归一化为out/**)。inline:是否把文件内容内联返回。save:是否保存为制品(与制品服务协作)。name_template:保存为制品时的文件名前缀(如pref/)。max_files(默认 100)、max_file_bytes(默认 4 MiB/文件)、max_total_bytes(默认 64 MiB):上限控制。- 说明:
outputs同时兼容 snake_case(推荐)与旧版 Go 风格字段名 (例如MaxFiles)
timeout(可选):超时秒数(执行器有默认值)save_as_artifacts(可选,传统收集路径):把通过output_files收集到的文件保存为制品,并在结果中返回artifact_files。omit_inline_content(可选):为 true 时不返回output_files[*].content与primary_output.content(只返回元信息)。 非文本输出的content也会始终为空。需要文本内容时,可用output_files[*].ref配合read_file按需读取。artifact_prefix(可选):与save_as_artifacts配合的前缀。- 若未配置制品服务(Artifact service),
skill_run会继续 返回output_files,并在warnings中给出提示。
- 若未配置制品服务(Artifact service),
建议:
- 建议 skill_run 尽量只用于执行 Skill 文档里描述的流程
(例如 SKILL.md 明确要求的命令)。
- 不建议用 skill_run 做通用的 Shell 探索。
- 优先使用 skill_list_docs / skill_select_docs 读取 Skill 文档,
再用文件工具按需查看选中的内容。
可选的安全限制(白名单):
- 环境变量 TRPC_AGENT_SKILL_RUN_ALLOWED_COMMANDS:
- 逗号/空格分隔的命令名列表(如 python3,ffmpeg)
- 启用后 skill_run 会拒绝管道/重定向/分号等 Shell 语法,
并仅允许执行白名单中的“单条命令”
- 因为不再经过 Shell 解析,诸如 > out/x.txt、heredoc、
$OUTPUT_DIR 变量展开等写法将不可用;建议改为调用脚本,
或使用 outputs 收集输出文件
- 代码侧也可通过 llmagent.WithSkillRunAllowedCommands(...) 配置。
可选的安全限制(黑名单):
- 环境变量 TRPC_AGENT_SKILL_RUN_DENIED_COMMANDS:
- 逗号/空格分隔的命令名列表
- 启用后同样会拒绝 Shell 语法(仅允许“单条命令”),并拒绝
执行黑名单中的命令名
- 代码侧也可通过 llmagent.WithSkillRunDeniedCommands(...) 配置。
可选行为(强制保存到制品):
- 代码侧可配置强制让 skill_run 尽量把收集到的输出保存到制品服务,
即便模型未显式设置 save_as_artifacts / outputs.save:
- llmagent.WithSkillRunForceSaveArtifacts(true)
输出:
- stdout、stderr、exit_code、timed_out、duration_ms
- staged_inputs(可选):本次执行前从对话自动 stage 进
work/inputs/ 的文件列表
- primary_output(可选):包含 name、ref、content、mime_type、
size_bytes、truncated
- 便捷字段:指向“最合适的”小型文本输出文件(若存在)。当只有一个主要输出时
优先使用它。
- output_files:文件列表(name、ref、content、mime_type、
size_bytes、truncated)
- ref 是稳定的 workspace://<name> 引用,可传给其它工具使用
- 非文本文件的 content 会被省略。
- 当 omit_inline_content=true 时,所有文件的 content 会被省略。可用
ref 配合 read_file 按需读取文本内容。
- size_bytes 表示磁盘上的文件大小;truncated=true 表示收集内容触发了
内部上限(例如 4 MiB/文件)。
- 当命令失败或超时时,会省略 0 字节的收集结果,避免 shell 重定向先创建
空文件而造成误导。
- warnings(可选):非致命提示(例如制品保存被跳过)
- artifact_files:制品引用(name、version)。两种途径:
- 传统路径:设置了 save_as_artifacts 时由工具保存并返回
- 清单路径:outputs.save=true 时由执行器保存并附加到结果
典型流程:
1) 模型先调用 skill_load 注入正文/文档
- 当使用 llmagent.LLMAgent 时,这一步默认是必需的:
skill_run 会拒绝执行尚未通过 skill_load 加载过的技能。
如需关闭,可用:
llmagent.WithSkillRunRequireSkillLoaded(false)。
2) 随后调用 skill_run 执行命令并收集输出文件:
- 传统:用 output_files 指定通配符
- 声明式:用 outputs 统一控制收集/内联/保存
- 如需把上游文件带入,可用 inputs 先行映射
交互式 Skill 会话
声明: - tool/skill/exec.go
工具:
- skill_exec:在 Skill 工作区里启动一个可持续交互的命令
- skill_write_stdin:向运行中的会话追加 stdin
- skill_poll_session:继续拉取终端输出或最终结果
- skill_kill_session:终止并移除会话
建议:
- 一次性命令优先用 skill_run
- 如果命令会等待输入、展示编号选择,或需要跨多次工具调用继续交互,
优先用 skill_exec
- 对 $EDITOR 型流程,优先在 skill_run / skill_exec 上传
editor_text,不要试图通过 stdin 驱动全屏编辑器
skill_exec 复用 skill_run 的同一套工作区、inputs、outputs、
save_as_artifacts、omit_inline_content、artifact_prefix、
stdin 与 editor_text 语义,但会额外返回会话状态:
- status:running 或 exited
- session_id:后续继续交互使用的稳定 id
- output:本次调用观察到的最新终端输出
- interaction:best effort 的输入提示(例如 prompt / selection)
- result:当会话退出后,返回与 skill_run 风格一致的最终结果
典型交互流程:
1) 先调用 skill_exec
2) 查看 output / interaction
3) 持续调用 skill_write_stdin 或 skill_poll_session,直到
status=exited
4) 读取 result 与收集到的输出;若需要中止,则调用
skill_kill_session
示例:
映射外部输入文件,并收集一个小型文本输出:
元信息输出(避免把上下文塞满):
该调用会返回 output_files[*].ref(如 workspace://out/a.txt),
并省略 content,同时包含 size_bytes 与 truncated。
需要内容时再读取:
大文件建议保存为制品(不内联内容):
保存成功后,skill_run 会返回 artifact_files(name、version),
并可用 artifact://<name>[@<version>] 作为文件引用传给 read_file 等工具。
传统保存路径(当你使用 output_files 时):
运行环境与工作目录:
- 未提供 cwd 时,默认在技能根目录运行:/skills/<name>
- 相对 cwd 会被解析为技能根目录下的子路径
- cwd 也可以以 $WORK_DIR、$OUTPUT_DIR、$SKILLS_DIR、
$WORKSPACE_DIR、$RUN_DIR(或 ${...})开头,
工具会将其规范化为工作区内的相对目录
- 运行时注入环境变量:
- WORKSPACE_DIR、SKILLS_DIR、WORK_DIR、OUTPUT_DIR、
RUN_DIR(由执行器注入)
- SKILL_NAME(由工具注入)
- 便捷符号链接:在技能根目录下自动创建 out/、work/、
inputs/ 链接到工作区对应目录,方便按文档中的相对路径使用。
- .venv/:技能根目录下的可写目录,用于安装技能依赖
(例如 python -m venv .venv + pip install ...)。
- 文件工具在 base directory 下不存在真实 inputs/ 目录时,会将
inputs/<path> 视为 <path> 的别名
执行器
接口: codeexecutor/codeexecutor.go
实现: - 本地: [codeexecutor/local/workspace_runtime.go] (https://github.com/trpc-group/trpc-agent-go/blob/main/codeexecutor/local/workspace_runtime.go) - 容器(Docker): [codeexecutor/container/workspace_runtime.go] (https://github.com/trpc-group/trpc-agent-go/blob/main/codeexecutor/container/workspace_runtime.go)
容器模式说明:
- 运行目录挂载为可写;$SKILLS_ROOT(若存在)只读挂载
- 默认禁用网络(参见容器 HostConfig),更安全可重复
安全与资源:
- 本地/容器均限制读取与写入在工作区内
- 可通过超时、脚本权限(如只读挂载技能树)降低风险
- stdout/stderr 可能会被截断(见 warnings)
- 输出文件读取大小有限制,避免过大文件影响
事件与追踪
事件:工具响应以 tool.response 形式产出,可携带状态增量(见
skill_load)。合并多工具结果与并行执行逻辑参见:
[internal/flow/processor/functioncall.go]
(https://github.com/trpc-group/trpc-agent-go/blob/main/internal/flow/processor/functioncall.go)
追踪(常见 span 名):
- workspace.create、workspace.stage.*、workspace.run
- workspace.collect、workspace.cleanup、workspace.inline
原理与设计
- 动机:在真实任务中,技能说明与脚本往往内容较多,全部内联到 提示词既昂贵又易泄漏。三层信息模型让“知道有何能力”与“在 需要时获得细节/执行脚本”解耦,从而减少上下文开销并提升安全。
- 注入与状态:通过事件中的
StateDelta将加载选择以键值形式 写入会话状态的temp:*命名空间,后续每轮请求处理器据此拼接 提示词上下文(默认拼接系统消息;也可按需物化到 tool result), 形成“概览 → 正文/文档”的渐进式上下文。 - 执行隔离:脚本以工作区为边界,输出文件由通配符精确收集,避免 将脚本源码或非必要文件带入模型上下文。
故障排查
- “unknown skill”:确认技能名与仓库路径;调用
skill_load前 先检查“概览注入”是否包含该技能 - “executor is not configured”:为
LLMAgent配置WithCodeExecutor,或使用默认本地执行器 - 超时/非零退出码:检查命令、依赖与
timeout参数;容器模式下 网络默认关闭,避免依赖网络的脚本 - 输出文件未返回:检查
output_files通配符是否指向正确位置
参考与示例
- 背景:
- 工程博客: https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills
- 开源库:https://github.com/anthropics/skills
- 业界实践:
- OpenClaw:在 prompt 中要求模型用工具读取所选 skill 的
SKILL.md: https://github.com/openclaw/openclaw/blob/0cf93b8fa74566258131f9e8ca30f313aac89d26/src/agents/system-prompt.ts - OpenAI Codex:在项目文档里列出 skills,并要求按需打开
SKILL.md: https://github.com/openai/codex/blob/383b45279efda1ef611a4aa286621815fe656b8a/codex-rs/core/src/project_doc.rs
- OpenClaw:在 prompt 中要求模型用工具读取所选 skill 的
- 本仓库:
- 交互示例: [examples/skillrun/main.go] (https://github.com/trpc-group/trpc-agent-go/blob/main/examples/skillrun/main.go)
- 示例技能: [examples/skillrun/skills/python_math/SKILL.md] (https://github.com/trpc-group/trpc-agent-go/blob/main/examples/skillrun/skills/python_math/SKILL.md)