SKILL.md(说明用途、给 agent 看的指令),可以再带一些参考文档、脚本或样例。写好后丢给 agent,它会在合适的时候自己用。
Harness 让你从两个地方装 skill:
- 技能市场 —— Git 仓库、Nacos、MySQL、classpath、自定义后端
- 工作区 ——
workspace/skills/下大家共用;<userId>/skills/下按用户隔离
一个例子
把团队的 skill 仓库接进来,agent 立刻就能用:load_skill_through_path 加载详情。
接技能市场
skillRepository(...) 是统一入口,传什么后端都可以。
Git
skills/ 子目录会优先读它,否则读根目录。想自己控制同步节奏:new GitSkillRepository(url, false),然后手动 repo.sync()。
Nacos
market 是 AutoCloseable,应用退出时关掉以释放订阅。
MySQL
writeable(true) 后可以从 agent 侧写回;只读分发就传 false。
Classpath
把 skill 跟 JAR 一起发:接多个
skillRepository(...) 可以重复调用;后注册的优先级更高:
把 skill 放到工作区
工作区里的 skill 不用任何注册,把目录放好就生效。大家共用
单个用户用
如果想给某个用户单独装一个 skill,或给他覆盖一个共用版本,放到他userId 命名的子目录下:
RuntimeContext.userId 传了”alice”。
这里的 workspace/<userId>/skills/ 是一个逻辑路径,不等于”一定是本机磁盘上的目录”。技能文件的读写统一走 AbstractFilesystem 抽象,实际落在哪儿由你配的文件系统模式决定,所以”按用户隔离 skill”这个能力跟具体存储后端解耦:
- 本机 + shell —— 就是宿主磁盘上的
workspace/alice/skills/...; - 共享存储(remote filesystem) ——
skills/前缀被路由到 KV,用户隔离体现为命名空间键agents/<agentId>/users/alice/skills/...,多副本之间一致;管理台改完下一轮推理即可生效; - 沙箱(sandbox filesystem) —— 宿主侧的用户目录在沙箱启动时通过 workspace projection 注入容器的
/workspace,agent 在沙箱里读到的是同一份。
<userId>/skills/ 都按同样的优先级覆盖共用版。各模式下的隔离键、物理表现以及 userId 的作用,详见文件系统。
同名冲突谁说了算
四个来源都可能给出同名 skill。优先级从低到高:
下层独有的 skill 仍然保留,只在重名时被上层覆盖。
举例:团队 Git 上有通用
code-reviewer,项目 workspace/skills/code-reviewer/ 写了项目专属版本,那 agent 看到的就是项目版;Alice 又在自己目录覆盖了一份,那 Alice 调用时拿到的是她自己的版本,其他用户还是项目版。
常用 Builder 选项
子 agent 自动继承父的市场列表和项目全局目录,不用重复配。
什么时候用
disableDynamicSkills():单次任务,跑完就退出;或市场后端慢、不想每轮拉。平时不用动这个开关。
自学习闭环(可选)
Harness 拼了一套”让 agent 自己起草 / 沉淀 / 整理 skill”的闭环。各阶段独立可开,按需启用:第一步:让 agent 能自己写 skill
propose_skill—— 把新 skill 写成草稿到skills/_drafts/<name>/,等审核skill_manage—— 编辑已有 skill(创建 / 修改 / 添加附属文件 / 删除)
.enableSkillManageTool(true)(autoPromote=true)。生产场景不建议。
同时 agent 每次调 load_skill_through_path / read_skill 时,框架自动记一笔使用计数,存到 skills/.usage.json——为后面的清理、灰度发布提供数据。
第二步:加审核闸门 + 可见性过滤
- 闸门 —— 草稿要变正式 skill 必须经过它。内置三种:直接拒绝(默认)、本地人工确认(stdin 等)、推消息后等。
- 可见性过滤 —— 决定 agent 在推理时能看到哪些”agent 自己创建”的 skill。可按部署环境、灰度比例、白名单组合。
第三步:后台周期性整理
skills/.archive/。可选叠加一个 LLM “伞合并”扫描(默认只 dry-run,输出报告,不实际改)。
程序化触发
业务层可以用:Agent 是怎么读取和执行 skill 的
每轮推理时,agent 会在 system prompt 里看到一个<available_skills> 块,列出当前可见的所有 skill:
<files-root>(如果有)是 agent 通过 shell 执行该 skill 脚本时使用的绝对路径,详见下面。
读 SKILL.md 和资源文件
加载某个 skill 时 agent 会调用内置工具load_skill_through_path:
load_skill_through_path(skillId, path="SKILL.md")返回 markdown 正文load_skill_through_path(skillId, path="references/style-guide.md")返回该 skill 目录下的任意文件
agent 感知不到这种差异,
load_skill_through_path 调起来都一样。底层查找顺序是”内存命中 → 文件系统读取 → 找不到时返回所有真正可用的路径列表”,所以传错 path 也只会拿到清单而不是死路。
<files-root> 和 shell 执行
当一个 skill 自带脚本(例如 scripts/run-checks.sh),agent 需要绝对路径才能通过 execute_shell_command 调用它。这个绝对路径就是 skill 条目里的 <files-root>。它怎么算出来取决于文件系统模式:
所以 agent 发出来的 shell 命令永远是
execute_shell_command("python3 <files-root>/scripts/foo.py")——不用猜路径,不用记每种来源对应哪个前缀。
市场 skill 文件实际落在哪儿
市场 skill 的资源最初只在内存里。要让 shell 能跑它们,harness 在每轮推理前把它们物化到<wsRoot>/.skills-cache/<source>/<name>/:
- 文件级 SHA-256 去重,只重写变化过的文件
- 已经下架的 skill(或被从 builder 中移除的整个仓库)留下的孤儿目录,会在同一轮顺手清掉
- Sandbox 模式下,
.skills-cache默认包含在 workspace projection roots 里,沙箱启动时(以及内容变化时)会跟workspace/skills/一起 hydrate 进沙箱
getSource(),第二个会自动加后缀(<source>_2、<source>_3 …),并打 warning log,所以路径和 skill-id 不会撞。
在沙箱里运行 skill
沙箱模式下,文件操作和 shell 都在隔离容器里执行,宿主完全不受影响。这就带来一个问题:skill 的脚本(scripts/run-checks.sh、scripts/foo.py 之类)写在宿主侧,agent 却要在容器里把它们跑起来。harness 用”物化 → 投影 → 容器内执行”三步把这件事做成透明的,下面拆开讲。
哪些 skill 会进沙箱
容器里能跑的 skill 分两类,进沙箱前的落点不同:第一步:把市场 skill 物化到宿主
市场 skill 的资源拿到时只是内存里的字节,shell 没法直接执行。每轮推理前,MarketplaceStager 把它们写到宿主的 <wsRoot>/.skills-cache/<source>/<name>/:
- 文件级 SHA-256 去重 —— 只重写变化过的文件,没变的跳过;
- 孤儿清理 —— 已下架的 skill、或从 builder 里移除的整个仓库,留下的目录在同一轮顺手删掉;
- 恢复执行位 —— 资源在入库时被转成字符串,POSIX 权限丢了,所以 stager 用启发式补回
+x:文件开头是 shebang(#!),或后缀是已知脚本类型(.sh/.bash/.py/.rb/.pl/.js/.mjs),就加上可执行位(按chmod +x的语义,只给本来有读权限的位加执行位)。纯静态资产(.json/.md/.txt)保持 644。
第二步:把工作区投影进沙箱(Workspace Projection)
沙箱start() 时,harness 把宿主工作区里的”静态资产”打成 tar,hydrate 进容器的 /workspace。默认投影的根(workspaceProjectionRoots)正好覆盖 skill 需要的两个目录:
workspace/skills/(含 <userId>/skills/)和上一步物化出来的 .skills-cache/ 会一起进沙箱。投影对所有被包含的文件按内容算一个整体 SHA-256,跟上次一样就跳过 hydrate,所以反复 call() 不会重复传一样的文件;只有内容变了才重新注入。
可调项(在 DockerFilesystemSpec / KubernetesFilesystemSpec 等沙箱 spec 上):
第三步:在容器里执行脚本
<available_skills> 块里每个 skill 的 <files-root> 在沙箱模式下用容器内前缀渲染:
于是 agent 直接发:
如果沙箱后端把工作区挂在非默认位置(比如 AgentRun 是/home/agentscope/workspace),<files-root>前缀会跟着换,agent 拿到的依然是正确的绝对路径。
跨调用保留脚本副作用
脚本如果装了依赖、生成了产物(npm install、pip install、编译输出),想下次 call() 还在,就给沙箱配快照(snapshotSpec(...))。快照保存整个 /workspace,下次同一 scope key 的调用先恢复快照、再叠加投影,所以装过的东西不用重装。
注意:读 SKILL.md 不需要沙箱
容易混淆的一点:读 skill(load_skill_through_path 取 SKILL.md / references/)走的是内存或宿主文件系统,跟沙箱无关;只有用 shell 跑脚本才需要文件真的进到容器里。所以即便关了投影、或某个 skill 根本没带脚本,agent 依然能正常读它的说明和参考资料。
一些建议
description 决定 agent 用不用这个 skill。 agent 一开始只看得到 name 和 description,觉得相关才会 load 详情。写”数据分析工具”远不如写”当用户要算统计、出报表、做趋势图时使用”有效。
SKILL.md 保持精简。 控制在 2k tokens 上下,详细参考资料放 references/,脚本放 scripts/。agent 需要时会自己读。
SKILL.md 和脚本中只使用相对路径。 由于抽象文件系统多层隔离的特殊性,SKILL.md 中引用资源和脚本时请使用相对于 SKILL.md 的路径(如 scripts/run.py、references/guide.md),不要硬编码绝对路径(如 /workspace/scripts/run.py)。框架会根据当前文件系统模式自动为每个 skill 生成正确的 <files-root> 绝对路径前缀,agent 在 shell 执行时会用 <files-root> 拼出完整路径。硬编码绝对路径会导致 skill 只能在特定文件系统模式下工作。
通用能力放市场,项目特有的写工作区。 代码评审、表格分析这种放团队 Git 上集中维护;公司内部 RPC 规范、本项目的命名约定写到 workspace/skills/ 里跟着代码版本走。
用户目录用来”覆盖+补充”,不要拿来当主存放。 关键能力请放在所有用户都能看到的层。
自学习按顺序启用:没人写新 skill 之前开 curator 没意义。先开 enableSkillManageTool,再加 promotion gate 让审核流程介入,最后用 curator 处理”老的不再用”。