Contributing and reference5 of 12
Adding a skill
A skill is a Markdown file, not code. It is guidance the model pulls up when a task calls for it — a
playbook rather than a capability. soul/skills/research.md is one; it tells her how to run a real
research job, and it changes nothing about what tools she has.
Where they live
soul/skills/*.md
Three ship today, confirmed by asking the loader:
| Skill | Requires |
|---|---|
research | nothing — always eligible |
discord-server | the discord toolset |
operating-websites | the browser toolset |
The directory is resolved by api/src/kotoba/core/skill_docs.py in this order:
KOTOBA_SKILLS_DIR, if set.soul/skills/next to the clone.- The copy packaged inside the wheel.
A skill name is slugified and jailed to that directory, so it can never escape it — skill_view with a
../ path returns nothing.
The frontmatter
---
name: operating-websites
description: Drive a real website end-to-end — log in, search, navigate, fill forms — with the browser tools.
when_to_use: any multi-step task inside a real website (Facebook, Gmail, a dashboard, a store)
requires_toolsets: [browser]
---
# Operating websites
The body. Everything below the frontmatter is what `skill_view` hands the model.
What each key does, read out of the loader:
| Key | Read by the code | Effect |
|---|---|---|
name | yes | the name she calls skill_view with, and the title in the prompt list |
description | yes | the one line that appears in the system prompt beside the name |
requires_toolsets | yes | the skill is offered only when every named toolset is active |
when_to_use | no | nothing reads it; the three shipped skills carry it as prose for a human |
requires_toolsets accepts a list or a single string. Absent, it means no requirement, and the skill is
always eligible. Surface a browser playbook only when the browser is connected.
Frontmatter is optional. Without it, the loader falls back to the first # heading as the title and the
first paragraph as the description, and the file stem as the name.
Malformed YAML degrades to no frontmatter rather than raising, so a broken skill file loses its metadata and keeps its body.
How it reaches her
The system prompt lists name — description for every skill. When one looks relevant she calls
skill_view, which returns the whole body. Nothing is loaded until she asks, so a long playbook costs
nothing on a turn that does not need it.
Writing one
- The body is instructions, not documentation. Address her directly.
- Say what to do first. The shipped skills open with the one rule that prevents most failures, and say so.
- Name the tools it depends on, and declare them in
requires_toolsets, or it will be offered on a turn where those tools do not exist. - Only
research.mdcarries a language rule. If your skill needs one, write it; nothing inherits.
Testing it
api/tests/test_skill_frontmatter.py covers the loader — parsing, the no-frontmatter fallback, and the
toolset filter. It writes its skills into a temporary directory via KOTOBA_SKILLS_DIR, which is the
pattern to copy if you test a skill of your own.
pytest api/tests/test_skill_frontmatter.py -q
Measured: 4 passed.
