Kotoba

Where to start

What Kotoba isWho she is and where she runs, in one page
InstallingOne package, two commands
First runA key, a model, her first words
The approval gateHow she asks before she acts
The two voice modesLocal voice, or the agent tunnel
The soul fileChange who she is
RoadmapWhat grows next, and what was cut on purpose

Or a section

↑↓ move openesc close124 pages
All pages

Her character6 of 8

Skills

About 8 minutes to read

On this page

A skill is a markdown file. Not code, not a plugin, not a tool — a playbook she can open and read when a job matches it.

The idea, from core/skill_docs.py:

"A 'skill' here is a markdown file under soul/skills/ (e.g. research.md): not code, but guidance the model pulls up when relevant. The system prompt lists the available skill titles; she calls skill_view to load a body when she needs it."

Three ship. You can add your own by dropping a file in a folder.


The three that ship

SkillRequiresSizeWhat it is for
researchnothing10,536 charsBeing a real research agent: plan, search several angles, prefer primary sources, synthesise, and produce a cited .md report
operating-websitesbrowser6,050 charsDriving a real website end to end — log in, search, navigate, fill forms — with the browser_* tools
discord-serverdiscord2,920 charsWorking in a guild: reading a conversation back, remembering people, and knowing what she cannot do there

research

The most substantial of the three, and the one that shapes the most visible behaviour. Its sections: the dead-time rule, Phase 1 (companion), Phase 2 (work), continuing an existing investigation, and what to do when a tool fails.

Its central rule is a split between two phases, and the reason is that you are listening:

"NEVER do heavy, multi-source research inside the live voice turn. One quick search is fine; a chain of searches + reading pages is NOT — it leaves the user listening to nothing."

So the live turn does one broad search and a short spoken summary; the deep phase (3–6 sources, cross-checking, the written report) goes to start_work in the background.

It is also the only skill in the tree with a language rule, which is why it is the one that gets loaded most carefully: the background worker sees only the goal string, so a goal written in the wrong language produces a report in that language.

operating-websites

One rule prevents most failures, and the skill leads with it:

"the browser is driven by the accessibility SNAPSHOT — you act on elements by their ref, never by a CSS selector you remembered."

Navigate, snapshot, act on the ref, re-snapshot after the page changes. Plus sections on logging into your own accounts, searching a site, knowing you actually succeeded (do not fabricate), saving a photo to visual memory, and when to stop.

discord-server

Mostly about the difference between a room and a conversation:

"A guild channel is a room with people in it. Every word you write is read by everyone there, including whatever a tool hands back."

Plus: whose things are whose (your files and keys are not a stranger's to ask for), reading history back by message link or time, one lookup then answer, remembering people, and what she cannot do there.


The frontmatter

Optional YAML, fenced with --- above and below — the opposite convention from the soul file, which uses a single --- as a separator. Getting them the wrong way round breaks both.

markdown
---
name: operating-websites
description: Drive a real website end-to-end — log in, search, navigate, fill forms.
when_to_use: any multi-step task inside a real website (Facebook, Gmail, a dashboard, a store)
requires_toolsets: [browser]
---

# Operating websites

...
KeyRead byEffect
namelist_skillsThe name she calls skill_view(name) with. Defaults to the filename stem
descriptionlist_skillsThe one line shown in the prompt beside the name
requires_toolsetsskills_for_toolsetsWork mode hides the skill unless all named toolsets are active
when_to_usenothingNot parsed. It reaches her only because the whole file, frontmatter included, is what skill_view returns

requires_toolsets accepts a list, a single string, or the nested metadata.hermes.requires_toolsets form. An absent or empty value means "always eligible".

Frontmatter is optional. Without it, the title is the first # heading and the description is the first non-heading paragraph. test_skill_frontmatter.py pins that fallback.

A malformed YAML block does not crash anything: the parser catches and degrades to ({}, text), so the file still loads with a filename-derived name.


How she chooses one

She is told what exists, and she decides. There is no matcher, no embedding, no router.

In a conversation, skill_titles() puts every skill in the prompt as name — description, under a block that pulls in two directions on purpose:

"for everyday things you already know (chat, a quick search, simple math, a basic page) just ACT — don't call skill_view as 'preparation' and don't loop on it; over-preparing makes you look like you're stalling. BUT if a skill clearly matches a non-trivial, multi-step job you're about to do … load it ONCE with skill_view(name) before you start. Load at most one, once, then act."

Note: the conversational list is not filtered by toolset. All three appear whether or not the browser and the Discord bot are running.

In work mode it is filtered, and framed harder — skills_prompt() calls it step zero:

"SKILLS (playbooks you can load) — this list is your STEP ZERO. Scan it; if one matches the job, load that ONE with skill_view(name) first and follow what it says. If none matches, there is no step zero: act straight away. Never a second skill, never a re-read."

The phrase "step zero" exists to settle a contradiction that ran for months: this block asked for the skill while the work subsystem's BIAS TO ACTION called a skill_view a forbidden preparation step, and the prohibition won every run. What went with it was everything a skill knows and nothing else does — including that language rule, which is why a Spanish research request came back as an English report.

All of the above is instruction. Nothing forces a skill to be read. The enforcement is only in the plumbing: the tool exists, the list is accurate, the body is returned.


The two tools

ToolWhat it does
skill_listReturns - name: description for every skill. Returns nothing when there are none
skill_view(name)Returns the skill's entire file, frontmatter included. Returns nothing when the name is unknown, which surfaces as its fail line: "I don't have a skill by that name yet."

Both are RISK = "read", toolset skills.

skill_view slugifies the name and jails it inside the skills directory. Measured:

python
>>> view_skill("Research")          # case-folded
<the full file>
>>> view_skill("../../etc/passwd")  # slugified to nonsense, then jailed
None

Where skills live

skills_dir() answers, in order:

  1. KOTOBA_SKILLS_DIR if set
  2. <REPO_ROOT>/soul/skills if it is a directory
  3. the copy packaged inside the wheel

On a clone, REPO_ROOT is the checkout, so that is soul/skills/. Add your file there.

On a wheel, REPO_ROOT is ~/.kotoba, so the answer is the packaged copy — until you create ~/.kotoba/soul/skills/.

The gotcha: the folder replaces, it does not merge

kotoba setup seeds ~/.kotoba/soul/default.md and the templates. It does not seed the skills. The moment you create ~/.kotoba/soul/skills/ and put one file in it, the three shipped skills are gone. Measured:

no ~/.kotoba/soul/skills   ->  <site-packages>/kotoba/data/soul/skills
                               skills: discord-server, operating-websites, research

with the folder + mine.md  ->  ~/.kotoba/soul/skills
                               skills: mine

If you want yours and theirs, copy the shipped three across first. The same is true of KOTOBA_SKILLS_DIR: it replaces the search, it does not add to it.

The consequence for upgrades runs the other way from the soul file. On a wheel with no home skills folder, pip install -U does give you improved skills, because they are read straight out of the package. Create the folder and you freeze them.


Writing your own

  1. Choose the directory (soul/skills/ on a clone, ~/.kotoba/soul/skills/ on a wheel — remembering that creating it hides the shipped three).
  2. Name the file after the skill: deploying-my-app.md. skill_view slugifies whatever she passes, so lowercase-with-hyphens is the safe filename.
  3. Write frontmatter with at least name and description. The description is what she reads in the prompt when deciding, so it should say when to use this, not what it contains.
  4. Write the body as steps. The three shipped skills are all imperative and specific: numbered core loops, explicit tool names with argument shapes, and a "what going wrong looks like" section.
  5. Restart is not required. list_skills() re-reads the directory each call, and the prompt is rebuilt every turn. A new file is visible on the next thing you say.

That last point is the one difference from everything else on these pages. Skills are the only part of her character you can change without restarting her.

What makes a good one

Reading the three that ship, the pattern is consistent:

  • Lead with the one rule that prevents most failures. operating-websites opens with refs, not selectors. research opens with the dead-time rule.
  • Name the tools and their argument shapes exactly, e.g. browser_click(target="e20", element="the Search box"). She acts on this literally.
  • Say what failure looks like, so she recognises it — "If a step fails with 'does not match any elements', you used a stale or invented ref".
  • Say when to stop. Two of the three have a section for it.
  • Keep it around ten thousand characters or less. The largest shipped skill is 10,536, and the whole file — frontmatter included — is loaded into context in one go.

What a skill cannot do

  • It cannot grant a tool. requires_toolsets hides a skill when a toolset is missing; it does not turn anything on.
  • It cannot change the four canned voice lines, the face map, or any cap.
  • It is not automatically loaded. It is a document she may choose to open.