--- name: nodetool-skill-author description: "Write or revise a NodeTool skill: a user skill row through create_skill, or a shipped system skill under packages/system-skills. Use when instructions should persist across sessions." featured: true --- # Author a NodeTool skill A skill is a named set of instructions an agent loads when its description matches the task. Write one when the same direction would otherwise be repeated every session. Do not write one for a single job: do the job. ## Which kind | | User skill | System skill | |---|---|---| | Lives in | A row in the user's account | `packages/system-skills//SKILL.md` in the build | | Written by | `create_skill`, `update_skill`, `delete_skill` | A file in a pull request | | Scope | That user | Every install, on day one, with no seeding migration | | Editable at runtime | Yes | No. Immutable, and the names are reserved so a user row cannot shadow one | A repository skill under `.claude/skills/` is a third thing: it directs the coding agent working on the NodeTool repository, not the agent inside the product — the engineering loop, not a product surface. Its maintenance rules are in a checkout's `.claude/README.md`, § Maintaining skills. A skill that teaches a product surface is a system skill even when the coding agent also wants it. `packages/system-skills/` holds the one copy and `.claude/skills/` is a symlink to it, so the product and the repository read the same document. Write a second copy under `.claude/skills/` and the two drift; `npm run check:agents-docs` fails on one. ## The contract Both kinds are the same document. ```markdown --- name: my-skill description: When to use this --- The instructions. ``` - `name` is a lowercase slug, `a-z0-9-` only, at most 64 characters. It cannot contain `anthropic` or `claude`. A user's names must be unique per user. - `description` is one bounded line, at most 1024 characters, with no markup. **It is the only part always in context**, so write it as *when to use this*, not as what the skill contains. A description that does not name the trigger means the skill never loads. - `featured: true` is for a shipped skill only. It puts the skill in the catalog that every turn carries. Every other shipped skill is found with `list_skills({query})`, so feature a skill only when a turn needs it before its first call: the entry point of a product surface, or a craft skill that work on that surface always reads. - The body must be non-empty. Frontmatter is parsed as scalar `key: value` pairs, not full YAML, and the fence is the first `---` line after the opening one, so a `---` rule inside the body survives. - `##` headings split the body into sections a loader can fetch one at a time. Use them for material an agent needs only at one step. Confirm the parse by listing it back: `list_skills` shows name and description, `load_skill {name}` returns the full instructions. ## Write it so it fires and so it holds - **Lead with the result, then the loop.** The reader is mid-task and will act on the first thing that looks like an instruction. - **Name tools exactly.** A renamed tool in a skill is a failure a model cannot recover from. Check each call against the live schema or the capability registry before writing it. - **State what a tool refuses**, not only what it accepts. Most skill failures are a call made with a field the tool drops. - **Point at the neighbour** rather than restating it. A craft skill quotes a call, the contract skill owns it. - **Do not add checkpoints or deliverables** the user did not ask for. An example in a skill is an illustration, not a requirement on the next request. ## Shipping a system skill One directory holding a `SKILL.md` whose frontmatter `name` matches the directory. Nothing imports these files, so `packages/system-skills` is not a workspace: `stageSystemSkills` in `scripts/bundle-backend.mjs` copies every directory into `_skills/` beside the bundled `server.mjs`, and `scripts/verify-backend-bundle.mjs` fails a build that misses one. A new directory ships with no other change. Discovery order is `_skills/` beside the bundle, else `packages/system-skills` on the way up, else `NODETOOL_SYSTEM_SKILLS_DIR`. Only `SKILL.md` is read: a skill ships as one document, so material that would have been a `references/` file belongs in a `##` section of the body. Symlink the new directory into `.claude/skills/` when the repository's coding agent should reach it too. Two follow-ups a new system skill usually needs: - Nothing, for the tool names: `packages/agents/tests/shipped-skill-names.test.ts` reads every snake_case call out of every shipped skill and checks it against the registry, so a new directory is covered as soon as it exists. A skill whose body names timeline calls can also go in that test's `SKILL_NAMES`, which matches them exactly against the capability specs and `edit_timeline`'s op list. - A skill that guides one model line goes in `MODEL_PROMPTING_SKILLS` (`packages/agents/src/model-prompting-skills.ts`) so `find_model` attaches it to a matching route. `packages/agents/tests/model-prompting-skills.test.ts` checks that table against the skills on disk, the model ids the shipped provider manifests name, and the capability registry. ## Reference In a NodeTool checkout, these repository sources go further: - `packages/system-skills/README.md` — the shipped set, and how one job routes across it. - `packages/agents/src/system-skills.ts` — loader and discovery rules. - `packages/protocol/src/skill-document.ts` — the parser and the validators.