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 agent, 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# 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. |
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).
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 agent, 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 agent, 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.
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
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.
