Skip to main content
Skills are what the chat agent reads before it answers. They are the only description of this system it has. So a skill that is wrong is not a stale document: it is an agent confidently telling somebody to do something that cannot work. There are twenty-one, each a directory under skills/ holding a SKILL.md and, beside it, a FACTS.md.

The twenty-one

The two rules for changing one

1. One skill, one editor, one commit. Do not edit a skill somebody else is editing. If you are adding a capability that touches three skills, commit them together: a reader who gets one skill from your commit and another from somebody else’s gets a contradiction. 2. A skill describes what the code DOES, today. Not what it will do, not what it used to do. Every claim should be checkable against a catalogue entry, a test, or a run, and when you change the code, the skill changes in the same commit or it starts lying. That second rule has already been broken once, expensively. limit-orders said “there is no adapter that waits for a price, and you must not look for one” for as long as that was true, and kept saying it after the feature shipped. An agent reading it talked somebody out of the exact thing they were asking for. A negative claim is the dangerous kind, because nothing fails when it goes stale.

What to change when

Grep first. grep -rl "loan.close" skills/ costs nothing and finds the ones you forgot.

Facts, beside the prose

Each skill has a FACTS.md: the same knowledge, in the shortest form that is still true. A section is prose, and prose earns its length; it carries the why, and the sentences that tell a model how to say a thing are not compressible into claims at all. But a question like “is gold borrowable on Aave v3” is answered by one line, and answering it from prose alone costs the 542-word section that line sits in. So facts are indexed beside the prose rather than instead of it. find_skill leads with facts and follows with the two best sections, which is cheaper than five sections and finds more, because a fact is a small document dense in the terms somebody actually searches for. Two properties worth knowing:
  • One fact, one fragment. Grouping a section’s facts into one row would return them the way prose is returned: all of a section, to answer one question, which is exactly the cost this removes.
  • The heading is the join, and it is checked rather than trusted. A fact names the section it came from and the section supplies the line range, so a fact cites the prose that backs it. A heading that no longer exists is a build failure, not a fact that quietly outlives its source.
Facts are the one thing here that is authored rather than derived. Everything else in the index is reconciled from a file by content hash; extracting facts is a model’s job, and doing it at boot would make the corpus nondeterministic, slow, and different in every process. So they are extracted once, reviewed by a person, and committed.

Writing for retrieval

The index searches sections, not whole files. Three arms rank them: an English-stemmed one, a literal one that keeps identifiers intact, and BM25: fused by reciprocal rank. Name the subject in the heading and in the first line of the body. The ranking cannot find a section that never says what it is about. Measured on this corpus: the best answer to “what is an approval gate” is a section headed “When a run stops and asks” which does not contain the string approv anywhere in it. Keep sections short. BM25 divides by length, so a two-line answer beats an essay that mentions the subject in passing, but only if those two lines exist as their own section. Write identifiers exactly: arm_schedule, limit_price, loan.close. The literal arm exists so somebody typing a name finds it; an English stemmer mangles those into something a search for the exact name no longer matches.

A skill may only name a tool that exists

And more sharply: a tool that spends is withheld from discussion. A skill telling the agent to call one while it is still working out what somebody wants is a skill describing something that will be refused. See the tool server for which five those are.

The index rebuilds itself

The index is a derived cache in Postgres, and the repository wins on every boot: unchanged sections are not rewritten, and sections absent from disk are pruned, in one transaction.
  • Restart the tool server after editing a skill and the change is live. There is nothing to migrate and nothing to remember.
  • If Postgres will not answer, the process falls back to reading the files. The table is never a source of truth.
  • On staging, skills are baked into the image, so an edit there needs a rebuild rather than a restart. The boot line naming how many sections were indexed is the proof it took.
Edit the markdown, restart the server, ask the question. Drift lives one deploy.

Before you commit

  • grep -rn "<the thing you changed>" skills/: did you get all of them?
  • Restart the server and ask the question a person would ask, in their words.
  • If a section you expected did not come back, the heading is the problem, not the ranking.