Writing an OpenKnowledge skill
You are helping the user author an
Agent Skill — a
file (plus
optional
and
) that teaches an AI agent how to do a
recurring task. In OpenKnowledge a skill is a first-class, versioned,
installable artifact: you author it with the
/
skill verbs, then
it into the user's editors.
Skills earn their keep by being
recognized at the right moment and
followed
faithfully. Most of the craft is in two places: a
that triggers
reliably, and a body short and concrete enough that the agent actually does what
it says. Work the stages below in order, but jump to where the user already is.
Stage 1 — Capture intent and classify the skill
Gate — does this already exist? Check BEFORE you build. First list managed
skills with
, then read any likely match with
;
these are the Project/Global skills OpenKnowledge already manages. Also search
the public marketplace with
skills({ query: "<2-4 trigger words>" })
before
drafting when the task sounds reusable beyond this project. Each marketplace row
returns
,
, and
; inspect the strongest descriptions,
then import a chosen candidate with
import({ source, skill: name, add: [...] })
and adapt
it only if reuse is the right call. Use the Vercel
skill,
, or manual skills.sh search only when the OK MCP
path is unavailable. If the user already has a skills.sh page
open, pass the full skill-page URL as
, e.g.
import({ source: "https://www.skills.sh/<owner>/<repo>/<skill>", add: [...] })
— the
middle segment is the REPO, not a literal
. Do not
run
as the
default install path in this flow: import through OpenKnowledge
(
import({ source, skill, add })
) so the skill lands as a real folder with
provenance, versioning, and managed fan-out;
says where it goes, and
afterwards changes where it lives. Use 2-4 concrete trigger phrases from the user's request plus
the domain or tool name, then open/read the strongest candidates' descriptions
before judging. If an existing or public skill covers most of it, STOP and
recommend reuse — a near-duplicate with overlapping triggers mis-fires and
dilutes both. If a public skill is close but not exact, decide WITH the user
whether to import/adapt it into OpenKnowledge, install it outside OK with the
Skills CLI, or write a narrower companion whose
explicitly hands
off to it. Build a new skill only when it is genuinely distinct. Surface the
overlap and the installed-skill plus marketplace search outcome before drafting
or writing anything. This is a disclosure gate: tell the user what you checked,
what matched, and why reuse/import/adapt/new-skill is the right next step. Never
discover overlap after the skill is written. If
/ skills.sh
is unreachable, say so plainly and continue with the installed-skill check.
Ask only what you can't infer:
- What recurring task should this skill handle? Get one concrete example.
- Skill type, because it sets how much rigor to apply:
- Reference / technique (most skills) — "how to do X." Prose body, examples.
- Discipline — enforces a behavior the agent tends to skip under pressure
(e.g. "always write a failing test first"). These need the RED baseline +
pressure-testing in Stage 4–6; reference skills don't.
- Degrees of freedom (calibrate body precision to task fragility):
high (free prose — judgment tasks), medium (parameterized steps), low
(a fixed command — when any deviation breaks the result). Don't
over-specify a judgment task or under-specify a fragile one.
Stage 2 — Resolve scope FIRST (never infer silently)
Scope determines where the skill lives and where
fans it. This is
the user's decision and has different blast radius — make it explicit.
| Scope | Lives in | fans it to |
|---|
| Global | a real folder under your home's skill roots (e.g. ) | your editors, in every project |
| Project | a real folder in this repo's skill roots (e.g. or the hub — shared via git) | this project's editors; teammates get it on |
Default heuristic: inside an OK project and the task is specific to it → project;
"for all my work / globally" → global; otherwise ask one question. State the
choice and its consequence before writing.
Stage 3 — Plan the contents
- Body = the durable, reusable instructions — under ~500 lines. If it's
growing past that, move depth into (loaded only when
needed) and point at it from the body. For a project skill the reference
auto-connects in the graph either way, so a backticked
path is fine; use a wiki-link only when you want the
mention to be a clickable inline link. For a global skill use a plain
backtick path — global references aren't graph docs, so a wiki-link there
dangles. Keep references one level deep.
- Do NOT include: a README/CHANGELOG/QUICK_REFERENCE, install instructions
for the skill itself, version histories, or anything host-specific. The skill
is the instructions, not documentation about the instructions.
- For a discipline skill, plan the failure mode you're correcting and how
you'll prove the skill fixes it (Stage 4).
Stage 4 — RED baseline (discipline skills only)
Before writing the skill, run the scenario WITHOUT it and capture what the agent
does wrong — verbatim, including its rationalizations ("the test is trivial so I
skipped it"). Those rationalizations are the exact loopholes the skill body must
close. Run the scenario in a fresh agent session with the skill absent — a new
chat, a sub-agent, or a second terminal — and keep the transcript; if you have no
way to run it clean, reason through the baseline with the user instead. Skip this
stage for plain reference skills.
Stage 5 — Draft the skill
Author the source with the skill verb (fs-direct; a live preview updates if a
server is running):
write({ skill: { name: "<lowercase-hyphen-name>", description: "<triggers>", body: "<markdown>", scope: "project" } })
Frontmatter contract (validated on write — get it right):
- — lowercase letters, digits, hyphens; ≤64; equals the directory.
- — ≤1024 chars, no XML tags, no field. See Stage 7.
- Nothing else. OK never injects its own frontmatter; bookkeeping lives in .
Write the body as direct instructions to the agent (imperative, second person),
concrete over abstract.
Add depth files —
(loaded on demand) and
(shown as
text, never executed by OK) — through the skill verbs, never native
/
:
# write one or more bundle files (independent of body — no need to resend SKILL.md)
write({ skill: { name: "<name>", files: [{ path: "references/tiers.md", content: "..." }] } })
# surgical edit inside one bundle file (mirrors edit({ document }))
edit({ skill: { name: "<name>", file: "references/tiers.md", find: "...", replace: "..." } })
# list the bundle, then read one file (no native cat)
skills({ name: "<name>" }) # → files: [{ path, kind }]
skills({ name: "<name>", file: "references/tiers.md" }) # → { path, kind, text }
# delete specific bundle files (omit `files` to delete the whole skill)
delete({ skill: { name: "<name>", files: ["references/tiers.md"] } })
Paths are skill-relative and must stay inside the skill dir (no
, no
absolute paths).
and
are the conventional homes and the
ones to reach for; other roots (
, a per-harness dir) are accepted
because published skills ship them and an import preserves them verbatim. Keep
references one level deep. A project
reference becomes a live content doc that
auto-connects to its SKILL in the graph regardless of how the body mentions it —
a backticked
path joins the graph just like a
wiki-link. Reach for a wiki-link (or
[label](references/<name>.md)
) only when you want a clickable inline link.
Global skills are different: their references aren't graph docs, so use a plain
backtick path there — a wiki-link would dangle.
Stage 6 — GREEN eval + refactor
Re-run the scenario WITH the skill. For a discipline skill,
pressure-test:
combine 2–3 pressures (time, authority, sunk cost) and confirm the agent still
follows the rule — then patch any loophole and re-test (
references/pressure-testing.md
).
For a reference skill, confirm the agent now does the task correctly and the body
isn't longer than it needs to be. Cut anything the agent already knows.
Stage 7 — Optimize the description (this is what makes the skill fire)
The
is the
only thing the agent sees when deciding whether to
load the skill. Get it right (
references/description-optimization.md
):
- Triggers, not a summary. Say WHEN to use it, in the user's words and
phrasings — NOT a recap of the body. Summarizing the workflow in the
description makes the agent follow the description and skip the body.
- Concrete and a little pushy to fight under-triggering: name the situations,
verbs, and phrasings that should activate it.
- Sanity-check against near-miss queries: phrasings that SHOULD trigger it and
adjacent ones that should NOT.
Stage 8 — Install (choose where it's available)
The skill is already live for whichever agent reads the folder it was created
in — its own folder IS the skill (there is no draft state).
manages
WHERE ELSE it is available, additively:
install({ name: "<name>", add: ["codex", "opencode"] }) // add locations
install({ name: "<name>", remove: ["codex"] }) // remove locations
install({ name: "<name>", add: ["codex"], mode: "link" }) // ...as a symlink
install({ name: "<name>", convert: ["codex"], mode: "copy" }) // change one location's form
install({ name: "<name>", source: ".team/skills" }) // move the real folder (run alone)
Location ids are editor host ids (
…
),
(the
vendor-neutral hub), or a custom root path like
.
Which form a location takes. applies only to the locations the call
names — those in
, or those in
— and to nothing else. Omit it on
and the new location takes the form the skill already uses. There is no
skill-wide mode: installing never rewrites a location you did not name.
Changing an existing location is
, which leaves membership alone; pass
every location to make them uniform.
What each form means. A symlink points at the source, so it cannot drift. A
copy is its own folder and refreshes automatically from the source — until
someone hand-edits it, at which point it forks and is never overwritten again.
Lifecycle. Edit with
; it routes to the source and versions
in place. There is no "uninstall everywhere" — removing every other location
still leaves the source folder loading for its agent, and a skill stops existing
only via
, which removes the source and every location. Roll
a
project skill back with
→
restore_version({ skill, version })
; global skills are unversioned in this build. For a project skill,
commit the skill folders — they are ordinary files in git, so teammates get them
on pull.
Reminders
- Prefer ONE good skill over many overlapping ones; split only when triggers diverge. (The Stage 1 gate is where you ENFORCE this — don't leave overlap to discover later.)
- Scope is the only placement decision — don't fold harness/format/toolchain assumptions into it, and don't bake one into the scope question's wording. You are authoring an OpenKnowledge skill: create it with — it lands as a real folder at the project's default skill home with attribution and versioning — and fan it out with ; don't hand-scatter copies across editor dirs, because owns fan-out and refreshes unedited copies from the source (a hand-edited copy forks and stops refreshing). If you load this flow, author through it.
- Ground claims about how skills behave (versioning, install targets, scope semantics) in this guide or the tool descriptions — don't assert system facts from assumption.
- Avoid blanket ALWAYS/NEVER rules without a stated reason — they read as noise and
get ignored. Explain the why.
- A skill that ships executable is projected verbatim into another
agent's trust domain — only include scripts the user has reviewed.