Agent Skills
Agent Skills are reusable, model-readable instruction folders that the Inference Gateway CLI (infer) loads on demand. Each skill is a directory with a SKILL.md playbook; the agent reads the body only when the skill is relevant, so a skill costs only its one-line metadata until it is actually used (and nothing at all when disabled).
The CLI uses the same on-disk format as Claude Code, Gemini CLI, and OpenAI Codex CLI, so a folder authored for any of those tools drops into .infer/skills/ - or the cross-tool .agents/skills/ open standard - unchanged. To browse or publish skills in the shared index, see the Skills Catalog.
Skills are enabled by default (since cli#618) - discovered skills are injected into every run's system prompt as lightweight metadata. Turn them off with
agent.skills.enabled: false(orINFER_AGENT_SKILLS_ENABLED=false).
How skills work
When skills are enabled, three things happen across every run mode (chat, infer headless, channels, and scheduled runs):
- Discovery is always injected. The system prompt gains an
AVAILABLE SKILLS:block listing each discovered skill'sname, scope,description, and the absolute path to itsSKILL.md. Only this metadata is added - the bodies are not loaded at startup. - Explicit invocation activates a skill deterministically. When you invoke a skill by name (see Activation), the CLI injects an
ACTIVE SKILLpointer telling the agent to read that skill'sSKILL.mdand follow it. This removes the old guesswork where activation depended on the model opportunistically deciding to read a file. - The body is read on demand. Only the skill's metadata is ever injected. The
SKILL.mdbody (and anyreferences/*.md) stays progressive-disclosure - the agent reads it with the Read tool when it actually needs it, kept reachable by the sandbox carve-out.
On-disk layout
A skill is a directory containing a SKILL.md file, optionally alongside supporting material the model reads or executes once the skill is active:
.infer/skills/ # also scanned: .agents/skills/ and ~/.infer/skills/
└── pdf-helper/
├── SKILL.md # required - the playbook + frontmatter
├── references/ # optional - long supporting docs, read on demand
├── scripts/ # optional - helper scripts the model runs via Bash
└── assets/ # optional - templates, fixtures, imagesThe same <name>/SKILL.md folder layout applies under every root. The CLI scans three locations, in precedence order (highest first):
| Scope | Path | Precedence | Notes |
|---|---|---|---|
| Project-local | .infer/skills/<name>/SKILL.md | Highest | Checked into the project; overrides a same-named skill in any other scope |
| Open standard | .agents/skills/<name>/SKILL.md | Middle | The shared .agents/ convention, so community skill folders work without modification |
| User-global | ~/.infer/skills/<name>/SKILL.md | Lowest | Personal defaults available across every project |
.agents/skills/ is an emerging open standard adopted across agent tooling (Claude Code, Gemini CLI, OpenAI Codex CLI, and others). The CLI scans it as a middle-precedence location so a skill folder published by a community author works unchanged, without copying it into .infer/skills/.
When the same name exists in more than one scope, the higher-precedence copy wins: a project .infer/skills/ skill overrides an .agents/skills/ skill, which in turn overrides a user-global ~/.infer/skills/ skill - useful for overriding a shared or personal default with a per-project variant.
The SKILL.md contract
SKILL.md is a markdown file with a YAML frontmatter block at the top:
---
name: pdf-helper
description: Extract text from PDFs. Use when the user asks to read, summarise, or analyse a PDF file.
---
# PDF Helper
1. Use the Bash tool to invoke `pdftotext input.pdf -` and capture stdout.
2. If the PDF is image-only, fall back to `tesseract` for OCR.Frontmatter rules, validated at discovery time and re-validated after every install:
| Field | Rule |
|---|---|
name | Required. ≤64 chars, lowercase letters / digits / hyphens only. Must equal the directory name. Must not contain infer, claude, anthropic, gemini, or openai. |
description | Required. Non-empty, ≤1024 chars. This is the routing signal the model uses to decide when a skill is relevant - make it actionable (say what it does and when to use it). |
Unknown frontmatter keys (for example Anthropic's allowed-tools: or Gemini's disabled:) are tolerated and ignored, so cross-vendor skills validate without edits.
Built-in skills
The CLI ships a small set of built-in skills embedded in the binary. On infer init they are seeded into the user-global ~/.infer/skills/ - the same directory the skills loader scans - only if absent ("seed-if-absent"). Once on disk they are ordinary user-scope skills: discovered, shown by infer skills list, and injected as lightweight metadata exactly like a skill you authored there yourself. Because seeding never overwrites an existing folder, your edits survive every later infer init.
The first built-in is the tmux starter skill:
| Skill | What it teaches the agent |
|---|---|
tmux | Drive interactive terminal programs - TUIs, REPLs, pagers, a debugger, or another CLI's chat UI - that the plain Bash tool cannot script, by running them inside tmux and scripting them with send-keys / capture-pane. It prefers to add a pane to the tmux session you already have open so the work stays visible, and only falls back to a detached session in a headless, CI, or piped run. |
Customizing a built-in
A built-in is just a user-scope skill, so you tune it with the same knobs as any other skill - there is no special "built-in" mode:
| Goal | How |
|---|---|
| Edit in place | Change ~/.infer/skills/tmux/SKILL.md. A later infer init never re-seeds over your edit. |
| Override per project | Add a .infer/skills/tmux/ (or .agents/skills/tmux/) folder; it shadows the built-in for that repo (first match wins). |
| Disable it | Add the name to agent.skills.disabled_skills. |
| Reset to the shipped default | Run infer init --overwrite to re-seed it. |
Refreshing a built-in after a CLI upgrade. A plain
infer initwill not re-seed over an already-initialized~/.infer, so a newer built-in shipped by a CLI upgrade does not land automatically. To pick it up today, either replace the one~/.infer/skills/<name>/SKILL.mdby hand, or runinfer init --overwrite- the latter also refreshes the other shipped~/.inferdefaults, so reach for it when you want a clean baseline rather than a single-skill update. (Moving seeding to load-time is a tracked CLI follow-up.)
Enabling and disabling
Skills are enabled by default. To turn them off (or back on) use the config file, the config set command, or an environment variable:
# .infer/config.yaml (project) or ~/.infer/config.yaml (user)
agent:
skills:
enabled: true # default - set to false to turn skills off
max_chars: 4000 # cap on the rendered AVAILABLE SKILLS block (0 disables the cap)
disabled_skills: [] # optional list of skill names to skip
repository: inference-gateway/skills # catalog repo the index and skill bodies resolve against# Disable for this project's .infer/config.yaml
infer config set agent.skills.enabled false
# Or disable globally in ~/.infer/config.yaml
infer config set agent.skills.enabled false --userspace
# Or disable for a single run via environment variable
INFER_AGENT_SKILLS_ENABLED=false infer chat| Setting | Type | Default | Description |
|---|---|---|---|
agent.skills.enabled | bool | true | Master switch. Also enables the sandbox carve-out. |
agent.skills.max_chars | int | 4000 | Cap on the rendered AVAILABLE SKILLS block. 0 disables the cap. |
agent.skills.disabled_skills | string[] | [] | Skill names to discover but never inject or activate. |
agent.skills.repository | string | inference-gateway/skills | GitHub owner/repo the catalog index and skill bodies resolve against. Point it at a fork to serve your own catalog. |
The matching environment variable INFER_AGENT_SKILLS_ENABLED takes precedence over the config file. To keep skills off everywhere, set agent.skills.enabled: false in your user config (~/.infer/config.yaml).
When max_chars is set (default 4000), skills whose full entry would exceed the budget are listed by name only in the AVAILABLE SKILLS block - their descriptions and paths are omitted. These skills remain fully invocable via /<name> or the "use the <name> skill" phrase; the agent reads the full SKILL.md on demand through the sandbox carve-out. Set max_chars: 0 to disable the cap and render every skill's full entry.
The matching environment variable INFER_AGENT_SKILLS_MAX_CHARS overrides the config value.
Managing skills
# List discovered skills (works regardless of agent.skills.enabled)
infer skills list
infer skills list --format json
# Install (three accepted forms)
infer skills install pdf-helper # by name, from the Skills Catalog index
infer skills install acme/internal-comms # owner/repo GitHub shorthand
infer skills install https://github.com/anthropics/skills/tree/main/skills/pdf # full directory URL
# Install flags
infer skills install pdf-helper --user # install to ~/.infer/skills instead of ./.infer/skills
infer skills install pdf-helper --overwrite # replace an existing skill folder of the same name
# Uninstall by directory name
infer skills uninstall pdf-helper
infer skills uninstall internal-comms --user # remove from the user scope
# The same operations from inside chat
> /skills list
> /skills install acme/internal-comms
> /skills uninstall pdf-helperinfer skills list works whether or not the feature is enabled, so you can verify discovery (and see validation errors for skipped skills) before turning skills on.
A bare <name> resolves through the public Skills Catalog index. The owner/repo and full-URL forms install straight from GitHub - the URL must point at a directory (https://github.com/<owner>/<repo>/tree/<ref>/<path>); URLs at /blob/ (a file) or the repo root are rejected with a clear error.
Installer notes:
infer skills installwrites to./.infer/skills/by default, or to~/.infer/skills/with--user. It never installs into.agents/skills/- that location is for skill folders you vendor yourself or that another agent tool drops in, and the CLI discovers them there read-only (see On-disk layout).- Frontmatter is re-validated after download against the same rules used at discovery - a half-installed skill is never left on disk. Without
--overwrite, an existing folder is left untouched and the install fails fast. - Unauthenticated GitHub requests are limited to 60 per hour per IP (easily exhausted on shared CI runners). Set
GITHUB_TOKEN(orGH_TOKEN, matching theghCLI) to raise the limit to 5,000/hour and to install from private repositories the token can access. - Refs containing a literal
/(such asfeature/foobranches) are not supported - use a tag, the default branch, or a single-segment branch. - Uninstall takes the on-disk directory name, regex-validated before any filesystem operation, so it cannot traverse outside the skills directory. There is no confirmation prompt (it matches
npm uninstall/brew uninstall).
The skills catalog and agent.skills.repository
A bare <name> install (and the on-demand catalog download described in Security) resolves through a catalog served straight from GitHub. The agent.skills.repository config (default inference-gateway/skills) is the owner/repo that catalog resolves against, so pointing it at a fork lets anyone serve their own skills.
The derived index URL
The repository is the single source for both the index and the skill bodies. The CLI derives the index URL from it:
https://raw.githubusercontent.com/<repository>/main/catalog.jsonThe default inference-gateway/skills therefore reads https://raw.githubusercontent.com/inference-gateway/skills/main/catalog.json (the same catalog.json the Skills Catalog publishes). Skill bodies resolve against that same repository - there is no second host to configure.
The catalog.json schema
catalog.json has a versioned header plus a flat skills array. The CLI is tolerant: it reads only a few fields and ignores the rest, so extra keys are harmless.
Top-level:
| Field | Read by the CLI? | Notes |
|---|---|---|
version | No | Schema/format version. Ignored today. |
release | Yes - infer skills search header | Catalog release tag, shown in the search header. |
updated | Yes - infer skills search header | Last-rebuild timestamp, shown in the search header. |
skills | Yes | The array of catalog entries. |
Per entry in skills:
| Field | Read by the CLI? | Notes |
|---|---|---|
name | Yes | Kebab-case identity; what infer skills install <name> resolves. |
description | Yes | Routing signal listed to the model and in search results. |
source | No | Upstream SKILL.md location. Ignored - bodies resolve against repository. |
vendor | No | Provenance, shown in the registry UI. Ignored by the CLI. |
license | No | Ignored by the CLI. |
tags | No | Ignored by the CLI. |
categories | No | Ignored by the CLI. |
homepage | No | Ignored by the CLI. |
Versioning
The catalog is versioned as a whole via the top-level release / updated header - there is no per-skill version field, so there is nothing to pin per skill. To pin a known-good catalog, pin the whole thing (a fork or tag) via agent.skills.repository.
Activation
Discovery puts every skill's metadata in the system prompt, but activation is explicit and deterministic. The CLI scans your messages for an invocation and, on a match, injects an ACTIVE SKILL pointer - the invoked skill's description and absolute SKILL.md path - instructing the agent to read that file and follow it.
Two triggers activate a skill, in any run mode (chat, infer headless, channels, scheduled runs):
| Trigger | Example | Where it works |
|---|---|---|
/<name> slash invocation | /pdf-helper | Chat input |
"use the <name> skill" phrase (case-insensitive) | use the pdf-helper skill | Any mode - chat, infer headless, channels, scheduled runs |
Typing /pdf-helper for a known installed skill is now routed to the agent and flags the skill active, rather than dead-ending as an "Unknown shortcut".
Activation behavior:
- Only metadata is injected. The pointer carries the skill's description and path - never the full body. The
SKILL.mdbody stays progressive-disclosure and is read on demand with the Read tool, reachable thanks to the sandbox carve-out. - More than one skill can be activated in a single message (for example
/pdf-helper /git-helper, or naming two skills in prose). - Repeated invocations are de-duplicated across the conversation, and tokens that don't match a known skill are ignored.
- Only your messages are scanned - a skill name appearing in an assistant reply does not self-activate the skill.
/skills <cmd>and/<name>are different things./skills list|install|uninstallmanages skills;/<name>(a bare skill name) activates an already-installed skill for the current turn.
Skills sandbox carve-out
A skill's instructions only reach the model through the Read tool (progressive disclosure). But user-scope skills live in ~/.infer/skills, which is outside the default Read sandbox:
tools:
sandbox:
directories: ['.', '/tmp', '.infer/tmp'] # defaultWithout a carve-out, Read ~/.infer/skills/<name>/SKILL.md is denied as "outside configured sandbox directories", so the skill never loads. This is exactly what broke the @infer CI bot, which installs to the user scope with infer skills install <skill> --user --overwrite and then runs the agent outside the project directory.
The carve-out: when agent.skills.enabled is true, the Read sandbox automatically grants read access to ./.infer/skills and ~/.infer/skills (and everything under them). Installed SKILL.md and references/*.md are therefore reachable even when the agent runs outside the project directory, such as in CI.
How it relates to tools.sandbox.directories
The carve-out is automatic and additive - you do not add the skills directories to tools.sandbox.directories yourself:
| Aspect | Configured tools.sandbox.directories | Skills carve-out |
|---|---|---|
| Default value | ['.', '/tmp', '.infer/tmp'] | ./.infer/skills and ~/.infer/skills |
| How it is granted | Explicit - you list each directory | Implicit - applied only when agent.skills.enabled |
| Access | Governs Read tool access generally | Read-only, scoped to the skills directories |
| When skills disabled | Unchanged | Not applied (Read of ~/.infer/skills is denied) |
Two guarantees still hold on top of the carve-out:
tools.sandbox.protected_pathswins. Protected patterns are checked first, so a file like*.envor.infer/config.yamlunder the skills tree stays denied even though the directory is carved out.- Lookalike siblings are not granted. Only the exact
./.infer/skillsand~/.infer/skillsdirectories (and their descendants) are allowed - a sibling such as~/.infer/skills-backupis not.
If you set tools.sandbox.directories: [], sandboxing is disabled entirely and the carve-out is moot. See the CLI security model for the full sandbox and protected-paths picture.
Security
A skill can instruct the model to run shell commands, read files, or call external APIs. Treat a skill like any other piece of executable content - only install skills from trusted sources. The CLI's normal tool-approval system still gates each Write/Edit/Delete/Bash call, but a malicious skill could craft a plausible-looking command. The name validator rejecting vendor strings (claude, anthropic, gemini, openai, infer) makes impersonating an official skill harder, but it is not a substitute for reviewing what you install.
On-demand catalog downloads
A catalog skill (one from agent.skills.repository that is not yet on disk) can be fetched and installed the moment a prompt names it - so anyone writing a prompt that reaches a profile, not just whoever installed the skills, decides what gets downloaded. If you run a CI or Telegram profile, know that a prompt containing /some-skill can trigger a download. The trust model:
- Only an explicit name triggers it. A catalog skill downloads when a prompt explicitly names it (
/rust, or "use the rust skill"). The model cannot trigger this on its own - catalog entries are listed to it without a path, so it has no way to reach an un-downloaded body. - Chat asks first. In interactive chat the install is confirmed by the user before it happens.
- Headless downloads immediately.
infer headless, pipedinfer chat, channels, and scheduled/heartbeat runs download with no prompt, by design - there is nobody to ask. - It is not a tool call, so tool approval does not gate it. The download is not a
domain.Tool, sotools.safety.require_approvalandapproval_behaviourdo not apply. Notably,approval_behaviour: blockdoes not prevent it. - Under channels the "user" is remote. In a channel the party whose prompt triggers the download is a remote sender, not the operator running the profile.
Related
- Skills Catalog - browse the shared index and publish a skill via a one-line PR.
- CLI - overview of the
infercommand-line tool, modes, tools, and shortcuts. - Configuration - the full configuration system across the gateway and CLI.
- ADL CLI - Skills - declare skills inside an A2A agent project so they scaffold into
.agents/skills/<id>/SKILL.md. - Source: inference-gateway/cli#571 (activation + sandbox carve-out), fixing inference-gateway/cli#569.
- Built-in skills: inference-gateway/cli#890 (the seeded
tmuxstarter skill), closing inference-gateway/cli#827.
