mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 20:35:39 -06:00
44736749eb
* feat(skills): add custodian-only skill library * docs: document custodian skill library * refactor(skills): make custodian skills concrete and non-interactive Replace docs-link-first playbooks with verified openclaw config/message/infer one-liners; drop interactive onboard references; encode the in-session config-write policy boundary (models.*/secrets.* via trusted shell). * fix(skills): corrections from live A/B testing of custodian skills --agent required for models list/auth list in multi-agent rosters; drop hanging channels capabilities probe; telegram target is chatId; roster-safe prove via agent turn (infer model run has no --agent and dead-ends multi-agent setups); note expected not-found on pre-setup config get. * fix(skills): front-load harness plugin check in add-model-provider Gather A/B timing showed the codex plugin dependency surfacing mid-Prove, at the most expensive point (approval gate + turn boundary). Checking and remediating during Gather removes the stall. * fix(skills): keep status inventory unfiltered while scoping custodian source buildWorkspaceSkillStatus forwarding agentId activated the loader's agent allowlist filter, dropping excluded skills from the workshop's status view (collection-review regression on CI). New closed agentSkillFilter mode lets agentId scope custodian-source discovery without filtering the entry list, per the documented status invariant.
516 lines
19 KiB
Markdown
516 lines
19 KiB
Markdown
---
|
|
title: "Skills config"
|
|
sidebarTitle: "Skills config"
|
|
summary: "Full reference for the skills.* config schema, agent allowlists, workshop settings, and sandbox env var handling."
|
|
read_when:
|
|
- Configuring skill loading, install, or gating behavior
|
|
- Setting per-agent skill visibility
|
|
- Adjusting Skill Workshop limits or approval policy
|
|
---
|
|
|
|
Most skills configuration lives under `skills` in
|
|
`~/.openclaw/openclaw.json`. Agent-specific visibility lives under
|
|
`agents.defaults.skills` and `agents.entries.*.skills`.
|
|
|
|
```json5
|
|
{
|
|
skills: {
|
|
allowBundled: ["gemini", "peekaboo"],
|
|
load: {
|
|
extraDirs: ["~/Projects/agent-scripts/skills"],
|
|
allowSymlinkTargets: ["~/Projects/manager/skills"],
|
|
watch: true,
|
|
},
|
|
install: {
|
|
preferBrew: true,
|
|
nodeManager: "npm",
|
|
allowUploadedArchives: false,
|
|
},
|
|
workshop: {
|
|
autonomous: { mode: "auto" },
|
|
allowSymlinkTargetWrites: false,
|
|
approvalPolicy: "auto",
|
|
maxPending: 50,
|
|
maxSkillBytes: 40000,
|
|
},
|
|
entries: {
|
|
"image-lab": {
|
|
enabled: true,
|
|
apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" },
|
|
env: { GEMINI_API_KEY: "GEMINI_KEY_HERE" },
|
|
},
|
|
peekaboo: { enabled: true },
|
|
sag: { enabled: false },
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
<Note>
|
|
For built-in image generation, use `agents.defaults.mediaModels.image`
|
|
plus the core `image_generate` tool instead of `skills.entries`. Skill
|
|
entries are for custom or third-party skill workflows only.
|
|
</Note>
|
|
|
|
## Loading (`skills.load`)
|
|
|
|
<ParamField path="skills.load.extraDirs" type="string[]">
|
|
Additional skill directories to scan, at the lowest precedence (below
|
|
bundled and plugin skills). Paths are expanded with `~` support.
|
|
</ParamField>
|
|
|
|
<ParamField path="skills.load.allowSymlinkTargets" type="string[]">
|
|
Trusted real target directories that symlinked skill folders may resolve
|
|
into, even when the symlink lives outside the configured root. Use this for
|
|
intentional sibling-repo layouts such as
|
|
`<workspace>/skills/manager -> ~/Projects/manager/skills`. Keep this list
|
|
narrow — do not point at broad roots like `~` or `~/Projects`.
|
|
</ParamField>
|
|
|
|
<ParamField path="skills.load.watch" type="boolean" default="true">
|
|
Watch skill folders and refresh the skills snapshot when `SKILL.md` files
|
|
change. Covers nested files under grouped skill roots.
|
|
</ParamField>
|
|
|
|
## Install (`skills.install`)
|
|
|
|
<ParamField path="skills.install.preferBrew" type="boolean" default="true">
|
|
Prefer Homebrew installers when `brew` is available.
|
|
</ParamField>
|
|
|
|
<ParamField path="skills.install.nodeManager" type='"npm" | "pnpm" | "yarn" | "bun"' default='"npm"'>
|
|
Node package manager preference for skill installs. This only affects skill
|
|
installs - the OpenClaw CLI and Gateway runtime require Node because the
|
|
canonical state store uses `node:sqlite`. `openclaw setup --node-manager` and
|
|
`openclaw onboard --node-manager` accept `npm`, `pnpm`, or `bun`; set
|
|
`"yarn"` directly in config for Yarn-backed skill installs.
|
|
</ParamField>
|
|
|
|
<ParamField path="skills.install.allowUploadedArchives" type="boolean" default="false">
|
|
Allow trusted `operator.admin` Gateway clients to install private zip
|
|
archives staged through `skills.upload.*`. Normal ClawHub installs do not
|
|
need this setting.
|
|
</ParamField>
|
|
|
|
## Operator Install Policy (`security.installPolicy`)
|
|
|
|
Use `security.installPolicy` when operators need a trusted local command to
|
|
approve or block skill and plugin installs with host-specific policy. The
|
|
policy runs after OpenClaw has staged source material and before the install
|
|
or update continues. It applies to ClawHub skills, uploaded skills, Git/local
|
|
skills, skill dependency installers, and plugin install/update sources.
|
|
|
|
```json5
|
|
{
|
|
security: {
|
|
installPolicy: {
|
|
enabled: true,
|
|
// Omit targets to cover every supported target.
|
|
targets: ["skill", "plugin"],
|
|
exec: {
|
|
source: "exec",
|
|
command: "/usr/local/bin/openclaw-install-policy",
|
|
args: ["--json"],
|
|
timeoutMs: 10000,
|
|
noOutputTimeoutMs: 10000,
|
|
maxOutputBytes: 1048576,
|
|
passEnv: ["OPENCLAW_STATE_DIR", "PATH"],
|
|
env: { POLICY_MODE: "strict" },
|
|
trustedDirs: ["/usr/local/bin"],
|
|
},
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
<ParamField path="security.installPolicy.enabled" type="boolean" default="false">
|
|
Enables operator-owned install policy. When enabled without a valid `exec`
|
|
command, installs fail closed.
|
|
</ParamField>
|
|
|
|
<ParamField path="security.installPolicy.targets" type='("skill" | "plugin")[]'>
|
|
Optional target filter. When omitted, policy applies to every supported
|
|
target so new installs do not unexpectedly fail open.
|
|
</ParamField>
|
|
|
|
<ParamField path="security.installPolicy.exec.command" type="string">
|
|
Absolute path to the trusted policy executable. OpenClaw runs it without a
|
|
shell and validates the path before use.
|
|
</ParamField>
|
|
|
|
<ParamField path="security.installPolicy.exec.args" type="string[]">
|
|
Static arguments passed after `command`.
|
|
</ParamField>
|
|
|
|
<ParamField path="security.installPolicy.exec.timeoutMs" type="number" default="10000">
|
|
Maximum wall-clock runtime for one policy decision.
|
|
</ParamField>
|
|
|
|
<ParamField path="security.installPolicy.exec.noOutputTimeoutMs" type="number" default="timeoutMs">
|
|
Maximum time without stdout or stderr output before the policy fails
|
|
closed.
|
|
</ParamField>
|
|
|
|
<ParamField path="security.installPolicy.exec.maxOutputBytes" type="number" default="1048576">
|
|
Maximum combined stdout and stderr bytes accepted from the policy process.
|
|
</ParamField>
|
|
|
|
<ParamField path="security.installPolicy.exec.env" type="Record<string, string>">
|
|
Literal environment variables provided to the policy process.
|
|
</ParamField>
|
|
|
|
<ParamField path="security.installPolicy.exec.passEnv" type="string[]">
|
|
Environment variable names copied from the OpenClaw process into the
|
|
policy process. Only named variables are passed.
|
|
</ParamField>
|
|
|
|
<ParamField path="security.installPolicy.exec.trustedDirs" type="string[]">
|
|
Optional allowlist of directories that may contain the policy executable.
|
|
</ParamField>
|
|
|
|
The policy command and interpreter script arguments must be direct regular
|
|
files with trusted ownership, restricted permissions, and verifiable parent
|
|
directories. Symlinks and insecure paths are rejected.
|
|
|
|
The policy receives one JSON object on stdin with `protocolVersion: 1`,
|
|
`openclawVersion`, `targetType`, `targetName`, `sourcePath`, `sourcePathKind`,
|
|
optional structured `source`, structured `origin`, and `request`. It must
|
|
write one JSON object on stdout with an `allow`, `warn`, or `block` decision.
|
|
`warn` and `block` require a non-empty `reason`; every decision may include a
|
|
`findings` array. Each finding requires non-empty string `ruleId` and `message`
|
|
fields plus a `severity` of `info`, `warn`, or `critical`. Optional `file` and
|
|
`evidence` values must be non-empty strings; a finite numeric `line` is rounded
|
|
down and clamped to the safe-integer range from 1 through `Number.MAX_SAFE_INTEGER`.
|
|
Malformed finding entries are ignored, and
|
|
invalid optional fields are omitted. A non-array `findings` value is treated as
|
|
absent. Operator-facing reason and finding text are limited to 1,000 characters.
|
|
OpenClaw retains at most 100 normalized findings for display. Only a `warn`
|
|
response with more than 100 valid findings fails closed and cannot be
|
|
acknowledged; `allow` and `block` retain the first 100. A warning stops the
|
|
install before commit. A `warn` review whose fully rendered notice, including
|
|
its title, target, sanitized reason and findings, and recovery guidance, exceeds
|
|
the 4,000-character aggregate display limit fails closed without presenting a
|
|
partial review. An over-budget `block` remains terminal with a
|
|
bounded denial, while over-budget findings on `allow` are summarized in bounded
|
|
diagnostic output. Interactive CLI
|
|
plugin and skill commands ask the operator to type the target name using the
|
|
same `install anyway` or `update anyway` copy as suspicious ClawHub releases,
|
|
then run policy again before continuing. Declined and non-interactive commands
|
|
on the direct CLI may use `--acknowledge-install-policy-warning` as explicit
|
|
approval after review for every warning in that command invocation;
|
|
every approved warning is re-evaluated before continuing.
|
|
The Control UI can review and approve warnings for its plugin install request;
|
|
that approval covers every warning in the invocation, and each warning is
|
|
still re-evaluated. Other Gateway-backed and automatic installs remain blocked
|
|
when they have no operator-confirmation flow. Use an equivalent direct plugin
|
|
or skill command to review and approve the warning when one exists. Otherwise,
|
|
change `security.installPolicy` to return `allow` for the reviewed request,
|
|
then retry the managed flow. `--force` does not approve policy warnings. A `block`,
|
|
non-zero exit, timeout, invalid JSON, non-object response, missing or invalid
|
|
protocol version or decision, or missing or empty `warn`/`block` reason always
|
|
fails closed.
|
|
|
|
OpenClaw does not execute install policy during normal Gateway startup.
|
|
Installs and updates fail closed when policy is enabled but unavailable.
|
|
`openclaw doctor` performs static validation; `openclaw doctor --deep`
|
|
executes a synthetic install probe against the configured command.
|
|
|
|
Bulk updates apply policy per target: a blocked skill or plugin update fails
|
|
that target without disabling the policy or skipping later targets in the
|
|
batch.
|
|
|
|
Example stdin:
|
|
|
|
```json
|
|
{
|
|
"protocolVersion": 1,
|
|
"openclawVersion": "2026.6.1",
|
|
"targetType": "skill",
|
|
"targetName": "weather",
|
|
"sourcePath": "/var/folders/.../openclaw-skill-clawhub/root",
|
|
"sourcePathKind": "directory",
|
|
"source": {
|
|
"kind": "clawhub",
|
|
"authority": "openclaw",
|
|
"mutable": false,
|
|
"network": true
|
|
},
|
|
"origin": {
|
|
"type": "clawhub",
|
|
"registry": "https://clawhub.openclaw.ai",
|
|
"slug": "weather",
|
|
"version": "1.0.0"
|
|
},
|
|
"request": {
|
|
"kind": "skill-install",
|
|
"mode": "install",
|
|
"requestedSpecifier": "clawhub:weather@1.0.0"
|
|
},
|
|
"skill": {
|
|
"installId": "clawhub"
|
|
}
|
|
}
|
|
```
|
|
|
|
Minimal policy command:
|
|
|
|
```js
|
|
#!/usr/bin/env node
|
|
|
|
let input = "";
|
|
process.stdin.setEncoding("utf8");
|
|
process.stdin.on("data", (chunk) => {
|
|
input += chunk;
|
|
});
|
|
process.stdin.on("end", () => {
|
|
const request = JSON.parse(input);
|
|
if (request.targetType === "plugin" && request.source?.kind === "local-path") {
|
|
process.stdout.write(
|
|
JSON.stringify({
|
|
protocolVersion: 1,
|
|
decision: "block",
|
|
reason: "local plugin paths are not approved on this host",
|
|
}),
|
|
);
|
|
return;
|
|
}
|
|
process.stdout.write(JSON.stringify({ protocolVersion: 1, decision: "allow" }));
|
|
});
|
|
```
|
|
|
|
## Bundled skill allowlist
|
|
|
|
<ParamField path="skills.allowBundled" type="string[]">
|
|
Optional allowlist for **bundled** skills only. When set, only bundled
|
|
skills in the list are eligible. Managed, agent-level, and workspace
|
|
skills are unaffected.
|
|
</ParamField>
|
|
|
|
## Per-skill entries (`skills.entries`)
|
|
|
|
Keys under `entries` match the skill `name` by default. If a skill defines
|
|
`metadata.openclaw.skillKey`, use that key instead. Quote hyphenated names
|
|
(JSON5 allows quoted keys).
|
|
|
|
<ParamField path="skills.entries.<key>.enabled" type="boolean">
|
|
`false` disables the skill even when bundled or installed. The
|
|
`coding-agent` bundled skill is opt-in — set it to `true` and ensure one of
|
|
`claude`, `codex`, `opencode`, or another supported CLI is installed and
|
|
authenticated.
|
|
</ParamField>
|
|
|
|
<ParamField path="skills.entries.<key>.apiKey" type='string | { source, provider, id }'>
|
|
Convenience field for skills that declare `metadata.openclaw.primaryEnv`.
|
|
Supports a plaintext string or a SecretRef: `{ source: "env", provider: "default", id: "VAR_NAME" }`.
|
|
</ParamField>
|
|
|
|
<ParamField path="skills.entries.<key>.env" type="Record<string, string>">
|
|
Environment variables injected for the agent run. Only injected when the
|
|
variable is not already set in the process.
|
|
</ParamField>
|
|
|
|
<ParamField path="skills.entries.<key>.config" type="object">
|
|
Optional bag for custom per-skill configuration fields.
|
|
</ParamField>
|
|
|
|
## Agent allowlists (`agents`)
|
|
|
|
Use agent config when you want the same machine/workspace skill roots but a
|
|
different visible skill set per agent.
|
|
|
|
```json5
|
|
{
|
|
agents: {
|
|
defaults: {
|
|
skills: ["github", "weather"], // shared baseline
|
|
},
|
|
entries: {
|
|
writer: { default: true }, // inherits github, weather
|
|
docs: { skills: ["docs-search"] }, // replaces defaults entirely
|
|
"locked-down": { skills: [] }, // no skills
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
<ParamField path="agents.defaults.skills" type="string[]">
|
|
Shared baseline allowlist inherited by agents that omit
|
|
`agents.entries.*.skills`. Omit entirely to leave skills unrestricted by
|
|
default.
|
|
</ParamField>
|
|
|
|
<ParamField path="agents.entries.*.skills" type="string[]">
|
|
Explicit final skill set for that agent. Explicit lists **replace**
|
|
inherited defaults — they do not merge. Set to `[]` to expose no skills for
|
|
that agent.
|
|
</ParamField>
|
|
|
|
<Warning>
|
|
Agent skill allowlists are a visibility and loading filter for OpenClaw
|
|
skill discovery, prompts, slash-command discovery, sandbox sync, and skill
|
|
snapshots. They are not a shell-time authorization boundary. If an agent
|
|
can run host `exec`, that shell can still run external clients or read
|
|
host files that are visible to the execution user, including MCP client
|
|
registries such as `~/.openclaw/skills/config/mcporter.json`. For
|
|
per-agent MCP isolation, combine skill allowlists with sandbox/OS-user
|
|
isolation, deny or tightly allowlist host exec, and prefer per-agent
|
|
credentials at the MCP server.
|
|
</Warning>
|
|
|
|
## Workshop (`skills.workshop`)
|
|
|
|
<ParamField path="skills.workshop.autonomous.mode" type='"off" | "propose" | "auto"' default='"auto"'>
|
|
`off` disables autonomous capture while keeping the durable-instruction
|
|
suggestion nudge. `propose` creates pending proposals from corrections and
|
|
substantial completed work. `auto` sends the same captures through the normal
|
|
scanner-gated Workshop apply path and runs daily collection cleanup that can
|
|
rewrite or drop eligible writable skills. User-prompted skill creation,
|
|
`/learn`, and manual history scan continue to work in every mode.
|
|
</ParamField>
|
|
|
|
See [Self-learning](/tools/self-learning) for eligibility, privacy, cost,
|
|
proposal-only permissions, and troubleshooting.
|
|
|
|
<ParamField path="skills.workshop.approvalPolicy" type='"pending" | "auto"' default='"auto"'>
|
|
`auto` allows agent-initiated apply, reject, or quarantine without an
|
|
additional approval prompt. `pending` requires operator approval.
|
|
</ParamField>
|
|
|
|
<ParamField path="skills.workshop.allowSymlinkTargetWrites" type="boolean" default="false">
|
|
Allow Skill Workshop apply to write through workspace skill symlinks whose
|
|
real target is already trusted by `skills.load.allowSymlinkTargets`. Keep
|
|
this disabled unless generated proposal applies should mutate that shared
|
|
skill root.
|
|
</ParamField>
|
|
|
|
<ParamField path="skills.workshop.maxPending" type="number" default="50">
|
|
Maximum pending and quarantined proposals retained per workspace (allowed
|
|
range: 1-200).
|
|
</ParamField>
|
|
|
|
<ParamField path="skills.workshop.maxSkillBytes" type="number" default="40000">
|
|
Maximum proposal body size in bytes (allowed range: 1024-200000). Proposal
|
|
descriptions are hard-capped at 160 bytes separately, because they appear
|
|
in discovery and listing output.
|
|
</ParamField>
|
|
|
|
See [Skill Workshop](/tools/skill-workshop) for the proposal lifecycle, CLI
|
|
commands, agent tool parameters, and Gateway methods this config controls.
|
|
|
|
## Symlinked skill roots
|
|
|
|
By default, workspace, project-agent, extra-dir, and bundled skill roots are
|
|
containment boundaries. A symlinked skill folder under `<workspace>/skills`
|
|
that resolves outside the root is skipped with a log message.
|
|
|
|
To allow an intentional symlink layout, declare the trusted target:
|
|
|
|
```json5
|
|
{
|
|
skills: {
|
|
load: {
|
|
extraDirs: ["~/Projects/manager/skills"],
|
|
allowSymlinkTargets: ["~/Projects/manager/skills"],
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
With this config, `<workspace>/skills/manager -> ~/Projects/manager/skills`
|
|
is accepted after realpath resolution. `extraDirs` scans the sibling repo
|
|
directly; `allowSymlinkTargets` preserves the symlinked path for existing
|
|
layouts.
|
|
|
|
Skill Workshop apply does not write through those symlinks by default. To
|
|
let Workshop apply mutate skills under already-trusted symlink targets, opt
|
|
in separately:
|
|
|
|
```json5
|
|
{
|
|
skills: {
|
|
load: {
|
|
allowSymlinkTargets: ["~/Projects/manager/skills"],
|
|
},
|
|
workshop: {
|
|
allowSymlinkTargetWrites: true,
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
Managed `~/.openclaw/skills` and personal `~/.agents/skills` directories
|
|
already accept skill-directory symlinks unconditionally (per-skill
|
|
`SKILL.md` containment still applies) — `allowSymlinkTargets` is only needed
|
|
for workspace, extra-dir, and project-agent (`<workspace>/.agents/skills`)
|
|
roots.
|
|
|
|
## Sandboxed skills and env vars
|
|
|
|
<Warning>
|
|
`skills.entries.<skill>.env` and `apiKey` apply to **host** runs only.
|
|
Inside a sandbox they have no effect — a skill that depends on
|
|
`GEMINI_API_KEY` will fail with `apiKey not configured` unless the sandbox
|
|
is given the variable separately.
|
|
</Warning>
|
|
|
|
Pass secrets into a Docker sandbox with:
|
|
|
|
```json5
|
|
{
|
|
agents: {
|
|
defaults: {
|
|
sandbox: {
|
|
docker: {
|
|
env: { GEMINI_API_KEY: "your-key-here" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
<Note>
|
|
Users with Docker daemon access can inspect `sandbox.docker.env` values
|
|
through Docker metadata. Use a mounted secret file, a custom image, or
|
|
another delivery path when that exposure is not acceptable.
|
|
</Note>
|
|
|
|
## Loading order reminder
|
|
|
|
```text
|
|
workspace/skills (highest)
|
|
workspace/.agents/skills
|
|
~/.agents/skills
|
|
~/.openclaw/skills
|
|
bundled + Custodian skills
|
|
skills.load.extraDirs (lowest)
|
|
```
|
|
|
|
Custodian skills share bundled precedence but load only for the agent selected
|
|
by `agents.defaults.systemAgent.agentId` (or the existing sole-agent fallback).
|
|
See [Custodian skills](/tools/custodian-skills).
|
|
|
|
Changes to skills and config take effect on the next new session when the
|
|
watcher is enabled, or on the next agent turn when the watcher detects a
|
|
change.
|
|
|
|
## Related
|
|
|
|
<CardGroup cols={2}>
|
|
<Card title="Skills reference" href="/tools/skills" icon="puzzle-piece">
|
|
What skills are, loading order, gating, and SKILL.md format.
|
|
</Card>
|
|
<Card title="Creating skills" href="/tools/creating-skills" icon="hammer">
|
|
Authoring custom workspace skills.
|
|
</Card>
|
|
<Card title="Skill Workshop" href="/tools/skill-workshop" icon="flask">
|
|
Proposal queue for agent-drafted skills.
|
|
</Card>
|
|
<Card title="Self-learning" href="/tools/self-learning" icon="brain">
|
|
Conservative, opt-in proposals from completed work.
|
|
</Card>
|
|
<Card title="Slash commands" href="/tools/slash-commands" icon="terminal">
|
|
Native slash-command catalog and chat directives.
|
|
</Card>
|
|
</CardGroup>
|