Adding a skill
Teach an agent a procedure with a SKILL.md playbook — which folder, what to put in it, and what not to.
The agent already has tools. What it lacks is when and in what order to use them for a job that is bigger than one call — "write an NCDC scheme of work", "orient on my class before answering", "put this PDF in chat files."
That is a skill: a markdown playbook on disk, mounted read-only at /skills/. The model sees each skill's name and description up front, and only reads the file when the task matches. It is not a tool. It does not run code. It does not replace a missing RPC method.
This page is the ticket "Bean should follow our scheme-of-work procedure" or "every agent should know how we handle PDFs."
Where skills live
general/<skill-name>/SKILL.md ← every agent
rover/<skill-name>/SKILL.md ← supervisor only
angie/<skill-name>/SKILL.md ← class teacher
bean/<skill-name>/SKILL.md ← subject teacher
sarah/<skill-name>/SKILL.md ← director of studiesgeneral/ is a shared library, not a persona. Put a skill there only if every agent should load it (PDF, PPTX, image, shared-docs, write-docs). Persona folders are extra sources on top.
| Agent | Loads |
|---|---|
| Rover | /skills/general/ then /skills/rover/ |
Angie (class_teacher) | /skills/general/ then /skills/angie/ |
Bean (subject_teacher) | /skills/general/ then /skills/bean/ |
Sarah (dos) | /skills/general/ then /skills/sarah/ |
Order matters: last wins on a name collision, so a persona skill can shadow a general one of the same name.
There is no graph file to edit. The helpers below already point each agent at general/ + its persona folder. A new SKILL.md in the right folder is picked up on the next worker boot.
The helpers that mount skills
Files: apps/worker/src/naalya-agent/core/backends/skills.constants.ts and skills.backend.ts. You do not call these when adding a playbook. You need to know they exist so you do not invent a fifth folder that nothing loads.
| Helper | What it does |
|---|---|
SKILLS_ROUTE | '/skills/' — where CompositeBackend mounts the on-disk library. |
GENERAL_SKILL_SOURCE | '/skills/general/' — shared playbooks every agent loads first. |
WIRE_ID_TO_SKILL_PERSONA | rover → rover, class_teacher → angie, subject_teacher → bean, dos → sarah. The only map between wire ids and folder names. |
skillSourcesFor(persona) | [GENERAL_SKILL_SOURCE, '/skills/<persona>/']. Trailing slashes required. Order matters: last wins on a name collision, so a persona skill can shadow a general one. |
skillSourcesForWireId(wireId) | skillSourcesFor(WIRE_ID_TO_SKILL_PERSONA[wireId]). What Rover and each role subagent actually pass to deepagents. |
SKILLS_PERMISSIONS | Deny write on /skills/**. Skills are author-owned; agents may discover and read them, never rewrite the source of truth. |
resolveSkillsRoot() | Directory that contains the persona folders. Prefers the webpack-copied tree beside the worker bundle (dist/apps/worker/skills/), then the git tree (apps/worker/src/naalya-agent/core/skills) for Studio / nest start:dev. Warns once if neither exists — agents would otherwise run with zero skills silently. |
createSkillsBackend() | Read-only FilesystemBackend on that root, virtualMode: true. |
createSkillsOnlyComposite() | CompositeBackend with StateBackend as the writable scratch root and the skill tree mounted at SKILLS_ROUTE. Skills without a sandbox still need this so the skill tree is never the writable root. |
Do not invent a fifth persona folder without a row in WIRE_ID_TO_SKILL_PERSONA and a skillSourcesFor caller. Custom subagents do not inherit Rover's skills — each graph gets its own skills array. Role subagents already call skillSourcesForWireId('class_teacher' | …).
What is already there
| Folder | Skills |
|---|---|
general/ | pdf, pptx, image, shared-docs, write-docs |
rover/ | chat-files |
angie/ | know-my-class, class-roster, class-gradebook |
bean/ | scheme-of-work |
sarah/ | (none yet — empty folder is fine) |
Step 1: Pick the folder
Ask two questions:
- Does every agent need this? →
general/. PDF handling yes. "How Angie reads a roster" no. - If not, which specialist owns the procedure? Angie must not carry a scheme-of-work skill: those create tools are Bean-only. A playbook that tells Angie to call
create_scheme_of_workis a bug — she does not have the tool.
Do not invent a fifth persona folder without wiring it in WIRE_ID_TO_SKILL_PERSONA and skillSourcesFor — see the helpers above.
Step 2: Create the folder and frontmatter
Directory name equals YAML name. Deep Agents looks up skills that way.
---
name: know-my-class
description: >-
Orient before answering class-teacher questions about streams, subjects, or
who teaches what. Use when the teacher asks about their class, form, stream
setup, subject coverage, or staff assigned to their classroom.
---description is the model's trigger. Write when to open this file, not a slogan. If it is vague, the skill never loads — or it loads on every turn and wastes context.
You can add REFERENCE.md or extra files in the same folder (the PDF skill does). Keep the entry point as SKILL.md.
Step 3: Write a playbook, not a tool list
| Skills should contain | Skills should not be |
|---|---|
| When this work applies | A restatement of the tool inventory |
| Standards / constraints (NCDC alignment, template-first) | Vague "be helpful" prose |
| A decision tree (template vs ask user vs stop) | Instructions for tools this persona does not have |
| What a good answer looks like | Identity or targeting rules the server already enforces |
| Which tools to use as steps inside the procedure |
Bean's scheme-of-work skill is the template: NCDC constraints, "template means a platform record, never an upload", the list_templates → ask_user → create_scheme_of_work → update_* sequence, and when to stop.
Angie's know-my-class skill is the other shape: list_my_classrooms first, then subjects/staff, never invent UUIDs, ask when multiple streams match.
Keep tool descriptions short. Progressive disclosure lives here.
Step 4: Confirm it mounts
You should not need a worker code change. If the skill is missing in production but present locally, the webpack copy of core/skills/** is the usual cause.
npx vitest run apps/worker/src/naalya-agent/core/tests/skills.backend.spec.tsThat spec locks the source paths: Rover → general + rover, Angie → general + angie, and so on. It does not parse your markdown — read the file as if you were the model.
Custom subagents do not inherit Rover's skills. Each graph gets its own skills array. Role subagents already call skillSourcesForWireId('class_teacher' | …).
Gotchas
Skills are author-owned. There is no tenant-editable skill store. Staff cannot upload a playbook. Do not build that here.
Do not teach /workspace/ from a skills-only backend. The files prompt is only attached when sandbox + thread-files are mounted. Skills themselves never need that.
A skill cannot grant a tool. If Bean's playbook needs list_curriculum_topics and that RPC tool does not exist, add the tool first. The skill will not conjure it.
Sarah's folder can stay empty. An empty persona source is valid; she still loads general/.