Most skills configuration lives under skills in ~/.apus/apus.json. Agent-specific visibility lives under agents.defaults.skills and agents.entries.*.skills.

{
  skills: {
    allowBundled: ["gemini", "peekaboo"],
    load: {
      extraDirs: ["~/path/to/agent-scripts/skills"],
      allowSymlinkTargets: ["~/path/to/skills"],
      watch: true,
    },
    install: {
      preferBrew: true,
      nodeManager: "npm",
      allowUploadedArchives: false,
    },
    workshop: {
      autonomous: { mode: "auto" },
      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 },
    },
  },
}

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.

Loading (skills.load)

skills.load.extraDirs string[]

Additional skill directories to scan, at the lowest precedence (below bundled and plugin skills). Paths are expanded with ~ support.

skills.load.allowSymlinkTargets 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 -> ~/path/to/skills. Keep this list narrow — do not point at broad roots like ~ or a whole projects directory.

skills.load.watch boolean

Watch skill folders and refresh the skills snapshot when SKILL.md files change. Covers nested files under grouped skill roots.

Install (skills.install)

skills.install.preferBrew boolean

Prefer Homebrew installers when brew is available.

skills.install.nodeManager "npm" | "pnpm" | "yarn" | "bun"

Node package manager preference for skill installs. This only affects skill installs. Node remains the primary and recommended Apus runtime; Bun 1.4+ with WAL-reset-safe node:sqlite is supported as an explicit runtime opt-in. apus setup --node-manager and apus onboard --node-manager accept npm, pnpm, or bun; set "yarn" directly in config for Yarn-backed skill installs. Setup preserves this preference unless you pass --node-manager; fresh configurations default to npm.

skills.install.allowUploadedArchives boolean

Allow trusted operator.admin Gateway clients to install private zip archives staged through skills.upload.*. Normal ClawHub installs do not need this setting.

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 Apus 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.

{
  security: {
    installPolicy: {
      enabled: true,
      // Omit targets to cover every supported target.
      targets: ["skill", "plugin"],
      exec: {
        source: "exec",
        command: "/usr/local/bin/apus-install-policy",
        args: ["--json"],
        timeoutMs: 10000,
        noOutputTimeoutMs: 10000,
        maxOutputBytes: 1048576,
        passEnv: ["APUS_STATE_DIR", "PATH"],
        env: { POLICY_MODE: "strict" },
        trustedDirs: ["/usr/local/bin"],
      },
    },
  },
}
security.installPolicy.enabled boolean

Enables operator-owned install policy. When enabled without a valid exec command, installs fail closed.

security.installPolicy.targets ("skill" | "plugin")[]

Optional target filter. When omitted, policy applies to every supported target so new installs do not unexpectedly fail open.

security.installPolicy.exec.command string

Absolute path to the trusted policy executable. Apus runs it without a shell and validates the path before use.

security.installPolicy.exec.args string[]

Static arguments passed after command.

security.installPolicy.exec.timeoutMs number

Maximum wall-clock runtime for one policy decision.

security.installPolicy.exec.noOutputTimeoutMs number

Maximum time without stdout or stderr output before the policy fails closed.

security.installPolicy.exec.maxOutputBytes number

Maximum combined stdout and stderr bytes accepted from the policy process.

Literal environment variables provided to the policy process.
security.installPolicy.exec.passEnv string[]

Environment variable names copied from the Apus process into the policy process. Only named variables are passed.

security.installPolicy.exec.trustedDirs string[]

Optional allowlist of directories that may contain the policy executable.

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, apusVersion, 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. Apus 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.

Apus does not execute install policy during normal Gateway startup. Installs and updates fail closed when policy is enabled but unavailable. apus doctor performs static validation; apus doctor --deep executes a synthetic install check 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:

{
  "protocolVersion": 1,
  "apusVersion": "2026.6.1",
  "targetType": "skill",
  "targetName": "weather",
  "sourcePath": "/var/folders/.../apus-skill-clawhub/root",
  "sourcePathKind": "directory",
  "source": {
    "kind": "clawhub",
    "authority": "apus",
    "mutable": false,
    "network": true
  },
  "origin": {
    "type": "clawhub",
    "registry": "https://clawhub.ami-docs.dev.apuscorp.com",
    "slug": "weather",
    "version": "1.0.0"
  },
  "request": {
    "kind": "skill-install",
    "mode": "install",
    "requestedSpecifier": "clawhub:weather@1.0.0"
  },
  "skill": {
    "installId": "clawhub"
  }
}

Minimal policy command:

#!/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

skills.allowBundled 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.

Per-skill entries (skills.entries)

Keys under entries match the skill name by default. If a skill defines metadata.apus.skillKey, use that key instead. Quote hyphenated names (JSON5 allows quoted keys).

`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. Convenience field for skills that declare `metadata.apus.primaryEnv`. Supports a plaintext string or a SecretRef: `{ source: "env", provider: "default", id: "VAR_NAME" }`. Environment variables injected for the agent run. Only injected when the variable is not already set in the process. Optional bag for custom per-skill configuration fields.

Agent allowlists (agents)

Use agent config when you want the same machine/workspace skill roots but a different visible skill set per agent.

{
  agents: {
    ownership: "explicit",
    defaults: {
      skills: ["github", "weather"], // shared baseline
      heartbeat: { agentId: "writer" },
      systemAgent: { agentId: "writer" },
      authInheritance: { agentId: "writer" },
    },
    entries: {
      writer: { workspace: "~/.apus/workspace" }, // inherits github, weather
      docs: { skills: ["docs-search"] }, // replaces defaults entirely
      "locked-down": { skills: [] }, // no skills
    },
  },
  talk: { agentId: "writer" },
}
agents.defaults.skills string[]

Shared baseline allowlist inherited by agents that omit agents.entries.*.skills. Omit entirely to leave skills unrestricted by default.

agents.entries.*.skills 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.

Agent skill allowlists are a visibility and loading filter for Apus 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 ~/.apus/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.

Workshop (skills.workshop)

skills.workshop.autonomous.mode "off" | "auto"

auto lets agents save and update Workshop skills: a background review runs after substantial work, and learned skills unused for 30 days are archived. Review changes are announced in the conversation, and every change can be undone. off disables the background review and unused-skill cleanup. User-prompted skill creation, /learn, and manual learning sessions work in both modes.

See Self-learning for eligibility, privacy, cost, and troubleshooting.

skills.workshop.maxSkillBytes number

Maximum SKILL.md size in bytes for Workshop skills (allowed range: 1024-200000). Skill descriptions are capped at 1024 bytes separately; keep them near 160 bytes because they appear in discovery and listing output.

apus doctor --fix migrates configs from the removed proposal flow: it changes autonomous.mode: "propose" to "off" and deletes skills.workshop.approvalPolicy and skills.workshop.maxPending. Run apus config set skills.workshop.autonomous.mode auto to turn automatic learning back on.

See Skill Workshop for the skill 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:

{
  skills: {
    load: {
      extraDirs: ["~/path/to/skills"],
      allowSymlinkTargets: ["~/path/to/skills"],
    },
  },
}

With this config, <workspace>/skills/manager -> ~/path/to/skills is accepted after realpath resolution. extraDirs scans the sibling repo directly; allowSymlinkTargets preserves the symlinked path for existing layouts.

Skill Workshop uses each agent's <state-dir>/agents/<agentId>/agent/workshop-skills containment boundary. It does not use allowSymlinkTargets, and it rejects symlinked skills that resolve outside that directory.

Managed ~/.apus/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

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.

Pass secrets into a Docker sandbox with:

{
  agents: {
    defaults: {
      sandbox: {
        docker: {
          env: { GEMINI_API_KEY: "your-key-here" },
        },
      },
    },
  },
}

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.

Loading order reminder

See Loading order for source precedence, including the per-agent Workshop tier, and Snapshots and refresh for when changes become visible.

Related

Skills reference

What skills are, loading order, gating, and SKILL.md format.

Creating skills

Authoring custom workspace skills.

Skill Workshop

Agent-learned skills, change history, and undo.

Self-learning

Automatic, undoable skill learning from completed work.

Slash commands

Native slash-command catalog and chat directives.