ExAthena.Skills (ExAthena v0.4.5)

Copy Markdown View Source

Claude Code-style skills with progressive disclosure.

A skill is a directory containing a SKILL.md markdown file with YAML frontmatter. The frontmatter is cheap (a sentence) and is injected into the system prompt as a one-line catalog entry. The body is loaded into context only when the model decides it needs the skill — either by emitting a [skill: <name>] sentinel in its response, or by the host pre-attaching it via preload/2.

This means dozens of skills can be available at ~50 tokens each in catalog form; only the ones the model actually wants pay the full body cost.

Layout

~/.config/ex_athena/skills/<name>/SKILL.md   # user-level skills
<cwd>/.exathena/skills/<name>/SKILL.md       # project-level skills

Project skills override user skills with the same name.

Frontmatter schema

---
name: my-skill
description: short description used in the catalog
disable-model-invocation: false
allowed-tools: [read, glob, grep]
---

# Body
whatever instructions the agent should follow when this skill is
active. Anthropic recommends keeping bodies under 500 lines and
splitting into linked files for anything larger.

Only name and description are required. disable-model-invocation hides the skill from the catalog (host can still preload/2 it). allowed-tools (when set) restricts the tool list while the skill is loaded; PR3a wires this into Permissions.check/4.

Catalog rendering

Skills.catalog_section([%Skill{name: "deploy", description: "Deploy
this app to staging"}, ...])
#=>
## Available Skills

Use `[skill: <name>]` to load a skill's full instructions.

  - `deploy`  Deploy this app to staging

Summary

Functions

Build a system-role message that activates skill_name from skills.

Render the catalog section that's appended to the system prompt. Empty string when no model-invocable skills exist (so we don't pollute the prompt with a bare header).

Discover all skills available for a given working directory. Returns a map keyed by skill name; later sources override earlier ones (project beats user).

Extracts skill names referenced via [skill: <name>] sentinels in a block of model output. De-duplicated; case-sensitive on the name.

Returns the set of skill names already activated in messages (so we don't re-attach idempotently).

Pre-load a list of skill bodies onto a message list. Returns the amended message list. Idempotent — already-loaded skills are skipped.

Functions

activation_message(skills, skill_name)

@spec activation_message(map(), String.t()) ::
  {:ok, ExAthena.Messages.Message.t()} | {:error, :not_found}

Build a system-role message that activates skill_name from skills.

Returns {:ok, message} when the skill exists, {:error, :not_found} otherwise. The message is tagged name: "skill:<name>" so we can detect already-loaded skills (idempotency) and so the compactor knows not to drop it.

catalog_section(skills)

@spec catalog_section(map() | [ExAthena.Skills.Skill.t()]) :: String.t()

Render the catalog section that's appended to the system prompt. Empty string when no model-invocable skills exist (so we don't pollute the prompt with a bare header).

discover(cwd, opts \\ [])

@spec discover(
  String.t(),
  keyword()
) :: %{required(String.t()) => ExAthena.Skills.Skill.t()}

Discover all skills available for a given working directory. Returns a map keyed by skill name; later sources override earlier ones (project beats user).

Options

  • :user_dir — override the user-level skills directory.
  • :project_dir — override the project-level skills directory (default: <cwd>/.exathena/skills).

extract_sentinels(text)

@spec extract_sentinels(String.t() | nil) :: [String.t()]

Extracts skill names referenced via [skill: <name>] sentinels in a block of model output. De-duplicated; case-sensitive on the name.

loaded_skills(messages)

@spec loaded_skills([ExAthena.Messages.Message.t()]) :: MapSet.t()

Returns the set of skill names already activated in messages (so we don't re-attach idempotently).

preload(messages, skills, names)

Pre-load a list of skill bodies onto a message list. Returns the amended message list. Idempotent — already-loaded skills are skipped.

Useful for hosts that know up-front which skills the agent will need (e.g. a /deploy slash command pre-attaching the deploy skill so the agent doesn't have to discover it).