Skip to main content

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.

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.