# `ExAthena.Skills`
[🔗](https://github.com/udin-io/ex_athena/blob/v0.7.1/lib/ex_athena/skills.ex#L1)

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

# `activation_message`

```elixir
@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`

```elixir
@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`

```elixir
@spec discover(
  String.t(),
  keyword()
) :: %{required(String.t()) =&gt; 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`

```elixir
@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`

```elixir
@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`

```elixir
@spec preload([ExAthena.Messages.Message.t()], map(), [String.t()]) :: [
  ExAthena.Messages.Message.t()
]
```

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).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
