Skill manifest
External skills are subprocess executables described by a skill.json file dropped under one of the manager's allow-list directories. The loader walks those directories at boot, parses each skill.json, and registers a SubprocessSkill in the global skill registry. The LLM then sees the skill alongside built-in tools.
Source of truth: internal/skill/loader.go.
Layout on disk
/etc/ongrid/skills/ ← one entry in $ONGRID_SKILLS_EXTERNAL_DIRS
└── disk-cleaner/
├── skill.json
└── run.sh ← the executableEach skill.json is at the top of its own subdirectory. The directory tree is walked recursively; any file named skill.json is treated as a manifest. Multiple skills can live under the same allow-list root.
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"
}| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Skill key. Must be lower_snake matching [a-z0-9_]+. Becomes the LLM-facing function name. |
description | string | yes | Shown to humans (UI) and to the LLM (function description). |
schema | JSON Schema | no | Raw JSON Schema for the args object. Missing = empty object schema. |
entry | string | yes | Path to the executable. Relative paths resolve against the directory holding skill.json. Absolute paths must lie under an allow-list root. |
env_allow | string[] | no | Explicit list of env-var names forwarded to the child. Empty list = no env at all (not even PATH). Add "PATH" to opt in to PATH. |
timeout_seconds | int | no | Subprocess timeout. Zero falls back to DefaultSubprocessTimeout (30s). |
class | enum | no | safe (default), mutating, dangerous. See class taxonomy. |
category | string | no | Free-form group label. Defaults to external. UI groups subprocess skills by this. |
Wire protocol
The subprocess is invoked with stdin = JSON args object, stdout = JSON result object, stderr = log lines for the manager.
$ cat /tmp/args.json
{"path": "/var/cache", "dry_run": true}
$ run.sh < /tmp/args.json
{"freed_bytes": 1048576, "files_deleted": 17}Non-zero exit code is treated as failure. Stderr is captured into the tool-call event timeline so the LLM can read partial progress.
The result object is returned verbatim to the LLM. The agent kernel formats it as the tool call's response; the LLM is expected to reason over the JSON shape.
Class taxonomy
The same {safe, mutating, dangerous} taxonomy applies to native skills and subprocess skills.
| Class | What it can do | Persona permission required |
|---|---|---|
safe | Read-only — no side effects on the host or any system. | read-only (any persona) |
mutating | Creates / updates state. Reversible. | mutating-with-confirm or dual-sign-required |
dangerous | Irreversible (deletes, restarts, exec-arbitrary). | dual-sign-required (SOP) |
The persona's permission_mode field gates which classes can run without confirmation. See Agent persona format.
Allow-list rules
Operators configure allow-list directories via ONGRID_SKILLS_EXTERNAL_DIRS (colon- or comma-separated absolute paths). The loader enforces them strictly:
- Each directory must be an absolute path. Relative paths are skipped with a log line.
- Non-existent paths are skipped (so a fresh install with no
/etc/ongrid/skillsboots fine). - Each manifest's
entryis canonicalised withfilepath.EvalSymlinksand checked to live under the allow-list root. A symlink that points outside the root is rejected (entry %s escapes allowlist root %s).
Sandboxing beyond that is the subprocess's responsibility. If the skill needs to be tightly contained, run it under a wrapper (bwrap, firejail, nsenter) referenced as the entry.
Loader behavior
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)- Per-manifest validation failures are logged and skipped. One broken pack should not block boot.
- Duplicate names are skipped rather than erroring (so a redeploy that drops a new manifest before removing the old one doesn't crash the manager).
- Loader returns the count of successfully registered skills. Manager startup is non-blocking on the skill loader: an empty external dir is a no-op.
Logging at startup
The manager logs one line per 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/skillsTail journalctl -u docker-compose@ongrid or docker compose logs ongrid to confirm what was picked up.
See also
- Capabilities → Skills — built-in skill catalogue.
- Agent persona format — how a persona picks which skills it can call.
- Marketplace — install a pack of skills as a unit.
- REST API →
/v1/skills— listing and direct execution.
Native skill metadata (SKILL.md frontmatter)
Built-in skills shipped under ./skills/ use a richer YAML-frontmatter SKILL.md format that interoperates with the openclaw / claude-code skill ecosystems. The schema is defined in 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 ...For subprocess skills written by third parties, prefer the simpler skill.json format above. SKILL.md is for skills that compile into the manager binary.