Skill manifest
外部技能是子进程可执行文件,由放在 manager 一个白名单目录下的 skill.json 描述。加载器启动时走这些目录,解析每份 skill.json,在全局技能 registry 里注册一个 SubprocessSkill。LLM 然后看到这个技能跟内置工具并列。
真理之源:internal/skill/loader.go。
磁盘布局
/etc/ongrid/skills/ ← one entry in $ONGRID_SKILLS_EXTERNAL_DIRS
└── disk-cleaner/
├── skill.json
└── run.sh ← the executable每份 skill.json 在自己的子目录顶部。目录树递归走;任何叫 skill.json 的 文件被当 manifest 处理。多个技能可以住在同一个白名单根下。
manifest schema
{
"name": "disk_cleaner",
"description": "Free up disk by clearing stale build caches and journals.",
"schema": {
"type": "object",
"properties": {
"path": { "type": "string", "description": "Root path to clean." },
"dry_run": { "type": "boolean", "default": true }
},
"required": ["path"]
},
"entry": "run.sh",
"env_allow": ["PATH", "HOME"],
"timeout_seconds": 60,
"class": "mutating",
"category": "ops"
}| 字段 | 类型 | 必填 | 描述 |
|---|---|---|---|
name | string | 是 | 技能 key。必须是匹配 [a-z0-9_]+ 的 lower_snake。成为 LLM 面的函数名。 |
description | string | 是 | 显示给人类(UI)和给 LLM(函数描述)。 |
schema | JSON Schema | 否 | 参数对象的裸 JSON Schema。缺失 = 空对象 schema。 |
entry | string | 是 | 可执行文件路径。相对路径相对于含 skill.json 的目录解析。绝对路径必须在白名单根下。 |
env_allow | string[] | 否 | 转给子进程的 env 变量名显式列表。空列表 = 没有 env(连 PATH 都没)。加 "PATH" opt in PATH。 |
timeout_seconds | int | 否 | 子进程超时。零回退到 DefaultSubprocessTimeout(30s)。 |
class | enum | 否 | safe(默认)、mutating、dangerous。见 class 分类。 |
category | string | 否 | 自由分组 label。默认 external。UI 按这个对子进程技能分组。 |
协议
子进程被调用时stdin = JSON 参数对象、stdout = JSON 结果对象、 stderr = 给 manager 的日志行。
$ cat /tmp/args.json
{"path": "/var/cache", "dry_run": true}
$ run.sh < /tmp/args.json
{"freed_bytes": 1048576, "files_deleted": 17}非零退出码被当失败处理。stderr 被捕获到 tool 调用事件 timeline 里,LLM 能 读到部分进展。
结果对象逐字返给 LLM。agent kernel 把它格式化成 tool 调用的响应;LLM 被 期望对 JSON 形状推理。
class 分类
同样的 {safe, mutating, dangerous} 分类适用于原生技能和子进程技能。
| Class | 能做什么 | 需要的 persona 权限 |
|---|---|---|
safe | 只读 —— 不影响 host 或任何系统。 | read-only(任何 persona) |
mutating | 创建 / 更新状态。可逆。 | mutating-with-confirm 或 dual-sign-required |
dangerous | 不可逆(删、重启、执行任意命令)。 | dual-sign-required(SOP) |
persona 的 permission_mode 字段卡哪些 class 能不确认就跑。见 Agent persona 格式。
白名单规则
运维通过 ONGRID_SKILLS_EXTERNAL_DIRS(冒号或逗号分隔的绝对路径)配白名 单目录。加载器严格执行:
- 每个目录必须是绝对路径。相对路径跳过并打日志。
- 不存在的路径跳过(新装没
/etc/ongrid/skills也能干净启动)。 - 每个 manifest 的
entry用filepath.EvalSymlinks规范化,并检查住在 白名单根下。指出根的符号链接被拒(entry %s escapes allowlist root %s)。
超出这个的沙箱化是子进程自己的责任。技能需要严格封闭就用 wrapper(bwrap、 firejail、nsenter)作为 entry 引用。
加载器行为
LoadDirs(cfg)
for each dir in cfg.Dirs:
filepath.Walk(dir)
for each skill.json found:
parseManifest(path)
buildSubprocessSkill(manifest, path, root)
if skill already registered: skip (log line)
else Register(skill)- 按 manifest 的校验失败记日志并跳过。一个坏包不该阻止启动。
- 重名跳过而不是报错(让你在删旧 manifest 前放新 manifest 的重部署不会让 manager 崩)。
- loader 返回成功注册的技能数。manager 启动对技能 loader 非阻塞:空的外部 目录 no-op。
启动时日志
manager 每个 manifest 打一行:
skill loader: registered subprocess skill "disk_cleaner" from /etc/ongrid/skills/disk-cleaner/skill.json
skill loader: skill "broken_one" already registered, skipping /etc/ongrid/skills/broken-one/skill.json
skill loader: parse /etc/ongrid/skills/typo/skill.json: invalid character ',' looking for beginning of object key
skill loader: build "bad_path": entry /tmp/escape.sh escapes allowlist root /etc/ongrid/skillsjournalctl -u docker-compose@ongrid 或 docker compose logs ongrid 确 认捡到了什么。
另见
- 能力 → 技能 —— 内置技能目录。
- Agent persona 格式 —— persona 怎么挑 能调的技能。
- Marketplace —— 把技能 包作为一个单元装。
- REST API →
/v1/skills—— 列出和直接执行。
原生技能 metadata(SKILL.md frontmatter)
在 ./skills/ 下发的内置技能用更丰富的 YAML frontmatter SKILL.md 格式, 跟 openclaw / claude-code 技能生态互操作。schema 在 internal/manager/biz/aiops/chatruntime/types.go 定义:
---
name: query_promql
description: Run a PromQL query and return the result matrix.
when_to_use: When the user asks for current or recent metric values.
activation:
mode: always
metadata:
os: [linux, darwin]
requires:
bins: []
config: []
ongrid:
scope: manager
activation:
mode: always
tools:
- name: query_promql
impl: builtin:prom.QueryPromQL
class: read
description: Execute a PromQL query and return the matrix.
---
# query_promql
PromQL query tool ...第三方写的子进程技能优先用上面更简单的 skill.json 格式。SKILL.md 给编进 manager 二进制的技能用。