kind: Skill
What it is
kind: Skill declares a standalone entry in the workspace’s skill
registry — a SKILL.md body (markdown + front-matter) that any
agent in any crew can later bind to. It is the inverse of the nested
Skill reference under Crew.spec.agents[].skills: the nested form
says “this agent uses skill X (by slug)” and assumes the row already
exists; the standalone kind: Skill document says “ensure THIS
SKILL.md exists in the registry.”
The common pattern pairs the two: declare the Skill once at the top
level (or as a top-level document in a workspace bundle), then
reference its slug from as many agents as you like.
The kind is implemented in
internal/manifest/kinds/skill.go. The backing endpoint is the
import handler, which is a genuine upsert keyed on slug — so Create
and Update both POST the same body; the manifest only distinguishes
the two for readable dry-run output.
One body source — pick exactly one
A Skill carries itsSKILL.md body via exactly one of three
mutually-exclusive sources. Zero is a validation error (there is
nothing to import); two or more is rejected with the offending list.
YAML schema
Field reference
display_name,category, andiconare forward-compatibility metadata today: the importer reads front-matter from the body verbatim and does not yet merge these spec fields into it. They still round-trip via export, so set them now if you want them populated when the merge lands.
Examples
Inline body
Sibling file (large body)
Remote source
Cross-kind reference
Once a Skill is declared, agents reference it by slug:CLI reference
The manifest pipeline drives the same import endpoint the UI uses; no new per-kind subcommands ship with this kind.REST endpoint mapping
Endpoints used:
Validation rules
SkillDocument.Validate (offline — no HTTP, no filesystem) enforces:
apiVersion, when set, equalscrewship/v1.kind, when set, equalsSkill.metadata.nameis non-empty.metadata.slugis non-empty and matches^[a-z0-9][a-z0-9_-]*$.spec.descriptionis non-empty.- Exactly one of
inline/path/sourceis set (zero or more than one is an error, with the offending list named). inlinebody length ≤ 8 KiB.source, when set, parses as a URL, uses thehttpsscheme, and has a host.path, when set, must have been resolved by the bundle loader (a hand-constructed document that never went through Load is flagged).
WorkspaceContext — Skill is a leaf kind
with no FK references; the argument exists only for dispatcher
uniformity.
Apply behavior
ApplyUpsert (default)
For each declared Skill, Plan compares against the matched-by-slug remote row:- Remote missing →
ActionCreate(POST import). - Remote present and a body source is declared →
ActionUpdate(POST import). Because the list endpoint exposes no body hash, the manifest cannot tell whether the body actually changed, so any apply that declares a body re-posts it. This is deliberate: it is preferable to silently losing an edit. - Remote present, no body source, decoration matches →
ActionUnchanged(no REST call). - Remote row is
source: BUNDLED→ error. Bundled skills are server-seeded on every startup; the manifest refuses to touch them (mirrors the server-side guard). Pick a different slug.
ApplyReplace
Replace mode canDELETE the registry row then re-POST it. Be
aware bundled rows are still off-limits.
Round-trip via export
crewship export workspace calls ExportSkills, which emits one
kind: Skill document per non-BUNDLED row, sorted by slug.
Body lossiness: the list endpoint does NOT return the SKILL.md
content, so exported documents carry metadata + decoration but no
body source (no inline, path, or source). The export is
suitable for cataloguing what a workspace has, but re-applying it
as-is fails Validate (“exactly one of inline / path / source must be
set”). To get a round-trip-safe export you must re-attach a body
(materialise the SKILL.md files and rewrite the docs with path:
entries).