Files
tai/skills.md
T
retoor 789f5206c4 docs: skills.md contract for the skill system and bundled set
- discovery/frontmatter contract incl. the bare->/| subset limit
- bundled table with upstream provenance and adaptation rules
- install target semantics, sidecar shape, maintenance workflow
- wired in AGENTS.md, README layout; drift test in BundledSkillsTests
2026-10-18 13:51:44 +02:00

120 lines
6.8 KiB
Markdown

# skills.md
The skill system and the bundled default skills: discovery contract,
frontmatter subset, the six shipped skills with their upstream provenance
and tai adaptations, install semantics, and maintenance.
## The system
Skills are directories containing a `SKILL.md` file with `name` plus
`description` frontmatter, optionally with `scripts/`, `references/`, and
`assets/` alongside (the open Agent Skills layout). Discovery
(`discover_skills`) scans four roots and the most tai-specific one wins a
name collision: `~/.claude/skills` and `<project>/.claude/skills` /
`<project>/.agents/skills` first (external, read-only interop), then
`~/.tai/skills` and `<project>/.tai/skills` (managed).
`load_skill` returns the body plus a provenance note plus the absolute
paths of the top-level entries under `scripts/`, `references/`, and
`assets/` (`skill_extra_files`, not recursive: a nested directory shows up
as its own path and the agent descends from there). Descriptions ride the
system-prompt catalog truncated to 300 characters; bodies load on demand.
Frontmatter is a deliberate subset, not YAML (`parse_skill_front`): one
`key: value` per line, double-quoted values have their outer quotes
stripped, unknown keys are ignored, and only bare `>` (fold with spaces)
and `|` (join with newlines) continue a value onto indented lines.
Chomping variants (`>-`, `|-`, `>+`, `|+`) do NOT parse: a `>-`
description reads back as the literal string `">-"` and the folded lines
are dropped. Shipped skills must use single-line descriptions.
Missing skills with a `SKILL_BLUEPRINTS` entry are researched and built on
first `load_skill` (`bot-creator`, `api-client`, `web-researcher`,
`pdf-forms`, `data-wrangler`, `home-sysadmin`); anything else is an
"unknown skill" error listing what exists.
## Bundled skills
Six skills ship inside `tai.py` itself (`BUNDLED_SKILLS`, 21 files) so a
bare copy installs them anywhere. Upstream provenance per skill lives in
`BUNDLED_SKILL_UPSTREAM` and is copied into each installed sidecar.
| Skill | Files | Upstream | Status |
|---|---|---|---|
| `deep-research` | 6 (`SKILL.md`, 4 `references/`, `UPSTREAM.md`) | author's Muse user skill (itself from `wanqingying/debian-railway`, see `UPSTREAM.md`) | adapted |
| `seriousdevelopment` | 10 (`SKILL.md`, `CHANGELOG.md`, 8 `assets/templates/`) | author's Muse user skill v1.7.1 | verbatim |
| `git` | 2 (`SKILL.md`, `scripts/workspace-recovery.sh`) | Muse's bundled set | adapted |
| `plan` | 1 (`SKILL.md`) | Muse's bundled set | adapted |
| `requirements-clarification` | 1 (`SKILL.md`) | Muse's bundled set | adapted |
| `taste` | 1 (`SKILL.md`) | Muse's bundled set | one word |
`seriousdevelopment` is byte-identical to upstream: it carries no host
references and no em dashes. Everything else changed only where the host
leaks through; the method prose is untouched.
## Adaptation rules
Each rule below was applied everywhere its token appeared, and
`BundledSkillsTests.test_embedded_content_guards` fails if any of them
regresses:
| Upstream token | Tai equivalent | Why |
|---|---|---|
| `read_skill` | `load_skill` | tai's loader is named `load_skill` |
| `request_user_input` / `request_user_input_async` (+ `auto_resolution_ms`) | `ask_user` (question plus options, blocks; unavailable to workers and autonomous mode, decide and state the assumption there) | tai's only question tool blocks and has no async form |
| subagent / `Task` / `explore` workers, wait-call mechanics | `fork` (task plus timeout up to one hour) / `poll` (up to two minutes per call, loop until done); workers get 12 steps, no questions, nesting depth 2, 8 live max | tai's orchestration primitives and their real caps |
| `curl` recipes, `curl` commands | `web_fetch` GET with the same URL and headers | tai forbids curl and wget via shell; every recipe is a plain GET so nothing is lost (`auth_secret` covers the token case) |
| `webfetch` / `websearch` | `web_fetch` / `web_search` | tai's tool names |
| `/plan`, `/skill plan` | prose request or `load_skill` | tai has no slash-command skill routes |
| `>-` folded description | single-line description | the frontmatter subset (above) |
| em dash (U+2014) | hyphen | project prose rule, no exceptions |
Two judgment calls beyond mechanical mapping:
- `git` got an "Authorization in tai" paragraph: its never-commit rule
would otherwise veto the user's standing auto-commit rule, so history
writes follow the session's approval setting while the recovery
snapshot, lock discipline, and named-files rules still apply as
written. The `<git-skill-dir>` placeholder became `<recovery>`, bound
to the absolute script path from the bundled-files list.
- `deep-research` briefs still hand workers literal URLs even though tai
workers could read the skill directory: inline URLs keep briefs
self-contained under the 12-step budget.
## Install target
`skills` is the twelfth `INSTALL_TARGETS` entry, implemented by
`op_skills` on top of `write_bundled_skill` / `remove_bundled_skill`,
included in bare `./tai.py --install` and refreshable via the `install`
tool or `/install`. All six land in home scope (`~/.tai/skills`); project scope
never receives bundled skills. After any non-status action through the
tool, the live session re-runs discovery so the skills are usable without
a restart.
| Action | Semantics |
|---|---|
| `install` | writes missing skills only; a present `SKILL.md` means "already present", never an overwrite |
| `upgrade` | rewrites files that are missing or already equal; differing files are kept untouched and named loudly (`kept modified`) |
| `reinstall` | force-rewrites every tracked file, including user edits; user-added extra files still survive |
| `uninstall` | removes tracked files that still match (plus orphan sidecars); user-modified and user-added files stay, named loudly, with their sidecar |
| `status` | one line: `skills: P/6 bundled present`, plus `missing:` and `modified:` lists when non-empty |
The `.installs/<name>.json` sidecar records `source: tai-bundled`,
`mode: bundled`, `scope: home`, the `upstream` locator, the
`bundled_version` that wrote it, per-file sha8 `files`, and
`installed_at`. `load_skill` renders this as `[installed bundled, home
scope, from tai-bundled]`. The sidecar is rewritten only when a write
leaves zero kept files, so user edits are never re-baselined as
pristine behind their back.
## Maintenance
The embedded copies ARE the source: fix typos and drift directly in
`BUNDLED_SKILLS`, never by re-copying upstream over them (re-copying
would silently drop the adaptations above). To pull a new upstream
version of one skill: diff upstream against the embedded copy, re-apply
the adaptation rules to the new text, re-embed, and confirm the guard
test plus a fresh install round-trip. Raw strings keep backslashes
byte-exact, so no embedded file may contain a triple-double-quote or
end in a backslash; the guard test enforces the quote half.