Skills: Authoring and Lifecycle
Maintainer guide to the Astound skill lifecycle: authoring under services/skills, discovery, selection into the plugin, delivery to clients, runtime loading and invocation measurement.
On this page
TL;DR: A skill is a versionable package of instructions and supporting files,
not a model or a permission grant. Astound discovers skills from services/skills/,
selects them into the astound-commons plugin, delivers them to clients through
the signed plugin manifest, and records authenticated invocations.
The architecture at a glance
services/skills source
-> discovered skill catalog
-> plugin/marketplace selection (astound-commons)
-> signed plugin manifest
-> installed client skill
-> governed invocation and audit event
The astound-commons plugin ships four general skills: who_am_i,
agent_farm_runs, systemprompt_cli and report_issue. The
astound-connectors plugin carries three connector skills (psa_data,
salesforce_hygiene_check, google_workspace_test) that only work once their
connectors are configured; see Connectors. The pinned systemprompt-core crates supply
skill loading, manifest assembly and accounting; PostgreSQL stores requests and
audit records.
How a skill works
Authoring contract
Each skill lives under services/skills/<skill_id>/ and normally contains:
services/skills/example_skill/
config.yaml
SKILL.md
references/ # optional
scripts/ # optional
templates/ # optional
diagnostics/ # optional
data/ # optional
assets/ # optional
config.yaml supplies the platform identity and catalog metadata. The fields used by
the shared disk model are id, name, description, enabled, file, tags,
category, and hosts. This fork also uses display metadata such as
display_category for its public skills page. The directory name and canonical skill
ID use snake_case.
file selects the instruction document; the shipped skills set it to SKILL.md.
If it is omitted, the shared loader looks for index.md. The instruction file may
have YAML front matter for client interoperability. Platform loaders remove that
front matter before injecting or hashing the instruction body.
The services loader auto-discovers these directories when it loads the active
profile. Configuration is memoized for the process lifetime, so a skill changed on
disk under services/ is picked up when the service restarts — which for this
repository's own skills means a deploy. A missing
directory produces an empty catalog; an invalid or missing per-skill configuration
is skipped or rejected according to the consumer and validation stage. Run
configuration validation before treating a skill as distributable.
Discovery and inspection
Use the CLI to inspect the same configured services tree:
systemprompt core skills list
systemprompt core skills list --enabled
systemprompt core skills show who_am_i
list reports configured skill metadata. show returns the configuration and an
instruction preview. These commands are inspection tools: they do not install,
invoke, or publish a skill.
The public /skills/ page independently reads enabled skill descriptors to render
the human catalog.
Selection and signed distribution
A plugin selects skills explicitly by id (skills.source: explicit in
services/plugins/<id>/config.yaml); this instance has two plugins,
astound-commons and astound-connectors, each with its own marketplace.
Manifest assembly filters disabled skills
and host-incompatible skills, then projects each selected skill into the client
layout:
skills/<kebab-case-id>/SKILL.md
skills/<kebab-case-id>/references/...
skills/<kebab-case-id>/scripts/...
The projection changes the ID from snake_case to the kebab-case form clients
expect. It generates compatible SKILL.md front matter, preserves the instruction
body, and includes eligible text supporting files. Known binary file types, hidden
files, and __pycache__ are excluded from this skill projection. Scripts ending in
.sh or .py are marked executable.
The plugin manifest records each skill's SHA-256 and is distributed through the signed plugin/catalog path. A signature establishes integrity and origin; it does not grant model, MCP, or tool access. Those entitlements remain governed separately.
Runtime loading and invocation measurement
This instance ships no server-side (A2A) agents, so this path is idle here.
For a server-side agent, core's SkillService resolves the active skills root, reads the
descriptor and instruction body, and returns the stripped instructions. SkillInjector
adds them to the agent prompt under Writing Guidance. A load emits a skill_loaded
AG-UI event. When the request has a task ID and the execution-step repository is
wired, it also records a skill execution step. Missing tracking context does not
prevent the instructions from loading.
Claude Code can invoke an installed skill as a slash command or through its Skill
tool. Each hook event is stamped at ingest with the skill it invoked, and the
skill_events view reads those stamps: both signals are normalized to the dashed
plugin:skill, and a slash/tool duplicate observed within five seconds is recorded
as a repeat of the earlier row and not counted. Tool-based rows require a nearby
governance decision; slash commands do not generate a governed tool call and are
therefore measured from the authenticated prompt event. This is why tool-call counts
alone under-report skill usage.
Invocation analytics are observational. One conversation may invoke several skills, and its surrounding inference cost overlaps those skills. Do not sum related conversation cost as though each skill independently incurred it.