Skills
Give an agent skills in the Agent Skills format, folders with a SKILL.md it activates when a task needs one, from the host or from its workspace.
Overview
A skill is a folder with a SKILL.md and whatever else the task needs: scripts, references, templates. It follows the Agent Skills format, so the skills you already keep for other agents (in .agents/skills or .claude/skills) work as they are.
.agents/skills/
└── release/
├── SKILL.md
├── checklist.md
└── scripts/
└── bump.sh---
name: release
description: Release a new version of the project. Use when asked to ship, release or tag a version.
license: MIT
metadata:
owner: platform-team
---
1. Read checklist.md.
2. Run scripts/bump.sh with the new version.
3. Write the release notes.Skills load in three steps, so a hundred skills cost the context almost nothing until one is used:
- Metadata: the name and description of every skill go into the system prompt, in an
<available_skills>block. - Instructions: when a task matches, the model calls the built-in
use_skilltool, which returns the body ofSKILL.md(without its frontmatter), the skill folder and the list of its files. - Files: the model reads the files the instructions point to when it needs them.
Skills written in code with defineSkill are different: their tools and instructions join the agent's right away, see Agents. An agent can have both.
Adding skills to an agent
Pass a SkillRegistry (or the configuration of one) in skills:
import {
Agent,
Cogitator,
LocalWorkspace,
SkillRegistry,
workspaceTools,
} from '@cogitator-ai/core';
const workspace = new LocalWorkspace({ root: './project' });
const agent = new Agent({
name: 'maintainer',
model: 'anthropic/claude-sonnet-5-5',
instructions: 'You maintain this project.',
tools: workspaceTools(),
workspace,
skills: [new SkillRegistry({ user: true, dirs: ['./shared-skills'] })],
});
await new Cogitator().run(agent, { input: 'Ship version 2.1.0' });skills: [{ user: true }] does the same with a registry made for you. The search happens when a run starts and counts toward its timeout: a run aborted or timed out while a loader still searches ends at once. An agent whose search finds nothing runs without any skill tool or prompt section, and an invalid source (a SkillRegistry configuration with an unknown option, an object that looks like a defineSkill skill but is not one) fails with a CONFIGURATION_ERROR naming the agent.
Where skills are found
A SkillRegistry searches, in this order:
| Option | Where | Default |
|---|---|---|
dirs | Each folder given: a folder of skill folders, or a skill folder itself | none |
project | .agents/skills, .cogitator/skills and .claude/skills under a project folder on the host | none |
user | The same three folders in the home folder (true) or in the folder named | false |
workspace | The same three folders under the root of the run's workspace | true |
When two skills share a name, the first one found wins: dirs before the project, the project before the user, the user before the workspace, and .agents/skills before .cogitator/skills before .claude/skills. So a project overrides a skill of the same name in your home folder, and the skills you chose on the host win over the ones a repository or the agent put into the workspace.
precedence: 'workspace' puts the workspace right after dirs, before the project and the user: for an agent that works on repositories you trust, whose own skills should override yours.
Folders on the host are scanned once per registry, call registry.reload() after you change them. The workspace is scanned on every run, since the agent may add a skill there itself.
Skills are trusted content
The instructions of a skill reach the model as they are, and the model is told to follow them. A skill can tell the agent to run commands, send data somewhere or ignore other instructions, so load skills only from places whose authors you trust:
- Host folders (
dirs,project,user) are yours, and win a shared name by default. - The workspace may hold skills a cloned repository brought along or the agent wrote itself. Turn it off with
workspace: falsefor a workspace you do not trust, or keep the defaultprecedence: 'host'so it cannot replace a skill you chose. - In the workspace, only real folders and files count: a skill folder, a
SKILL.mdor a file that is a symbolic link (or sits behind one, from the workspace root down) is never read, checked again on every read, so a skill cannot reach.envor anything else outside its folder, also after a folder was swapped for a link.
The SKILL.md format
The frontmatter is YAML. The registry reads it leniently, the way the format recommends:
| Field | Rule | When it is broken |
|---|---|---|
description | Required, 1 to 1024 characters | Missing or empty: the skill is skipped. Too long: kept, with a warning |
name | 1 to 64 lowercase letters, digits and single hyphens, the folder name | Missing: the folder name is used. Other rules: kept, with a warning |
license | A string | Ignored, with a warning |
compatibility | 1 to 500 characters | Too long: kept, with a warning |
metadata | A map of strings | A number or boolean is read as a string, anything else is left out, with a warning |
allowed-tools | Space separated tool names (a YAML list works too) | Ignored, with a warning |
A file without frontmatter, with YAML that does not parse or with frontmatter that is not a mapping is skipped. Fields the format does not define are ignored, and a YAML tag the parser does not know (!foo, !!js/function) is read as a plain value, with a warning. Every problem lands in the catalog's diagnostics and is logged once:
const catalog = await new SkillRegistry({ project: '.' }).discover();
for (const problem of catalog.diagnostics)
console.log(problem.level, problem.path, problem.message);allowed-tools is experimental in the format. The registry keeps it on the skill (allowedTools) and use_skill tells the model which tools the skill expects, but nothing approves or restricts tools by it.
What the model gets
The system prompt ends with the catalog:
<available_skills>
<skill name="release">Release a new version of the project. Use when asked to ship, release or tag a version.</skill>
</available_skills>
Skills hold instructions and files for particular tasks. When a task matches a skill's description, call use_skill with that skill before you start, then follow what it returns.Descriptions fill at most 12 000 characters, the skills past them are listed by name only. use_skill({ skill }) takes any of the names and returns:
<skill_content name="release" source="workspace" workspace="ws_3f9a" location=".agents/skills/release">
1. Read checklist.md.
...
</skill_content>
Skill folder: .agents/skills/release (in the workspace, relative to its root). Paths in the instructions are relative to it.
Files of the skill, read them with read_file, by these paths:
- .agents/skills/release/checklist.md
- .agents/skills/release/scripts/bump.shThe files are read with the tool that reaches them:
- A skill in the run's workspace, when the agent has the
read_fileofworkspaceTools(): withread_file, by their workspace paths. Its scripts run withrun_commandlike any other file of the workspace. - A skill on the host (or in a workspace the agent has no
read_filefor): with the built-inread_skill_file({ skill, path }), which reads only inside the skill folder. A path with.., an absolute path elsewhere and a symlink that points out of the folder are refused, and the file list names only the files it can read.
Instructions longer than maxInstructionChars (20 000 characters by default, about the 5 000 tokens the format recommends at most) are cut at a line end, with a note that tells the model to read the rest of SKILL.md as a file of the skill.
The registry never runs a script. Running one is up to the tools you give the agent, inside the boundaries of its workspace.
Active skills stay in the conversation
A skill activated once stays active for the run and the thread:
- Calling
use_skillagain for a skill whose instructions are still in the conversation returns a short note instead of the whole body again. A skill is told apart by its folder, and by its workspace for one found there, so another agent's skill of the same name, also one at the same path of another workspace, loads its own instructions. - Clearing tool outputs (
context.clearToolOutputs) leaves the newestuse_skillresults of the run alone, up to 40 000 characters of instructions in all. Older ones past that are cleared like any tool output. - When compaction of the conversation (
context) drops ause_skillresult, the instructions come back as a system message right after the system prompt, the newest first within the same 40 000 characters, and a short note names the skills left out. Compaction keeps room for them, so the conversation and what comes back fit the window together. - A handoff takes along the instructions kept this way only for the skills the agent it hands over to has.
On Anthropic and Google, every system message joins the system prompt, so instructions put back after a compaction change it. Compaction already changed the conversation, so the prompt cache starts over either way.
Once the instructions are gone from the history (a later run after the thread was compacted in memory, or the oldest ones past the budget), use_skill returns the whole body again.
Writing your own loader
skills takes anything with discover(context) that returns a SkillCatalog. context holds the run's workspace and a signal that aborts with the run. The catalog has the skill manifests (name, description, license, compatibility, metadata, allowedTools, location, source), the diagnostics, and body(name), files(name) and readFile(name, path). Use it to serve skills from a database or a package. parseSkillFile(text, origin) and splitSkillFile(text) parse a SKILL.md the way the registry does.
Harness Agent
createHarnessAgent() assembles a coding or assistant agent from the workspace, todos, skills, subagents, commands, project instructions, permissions, compaction and tool search in one call, with presets and every piece overridable.
Todo Lists
Let an agent plan a long task as a todo list with write_todos, kept in the thread across runs and compaction, reported to your code and to Cogitator Studio.