# Agent skill selection: behavioral specification

This document describes the current main-session behavior of `pi-agent-switch`
with Pi coding-agent 1.5.0 or later. For setup and examples, see the
[README](../README.md#agent-skill-selection). The [architecture decision
records](adr/) explain the reasons for the integration and failure policies.

## Scope and terminology

A **loadout** is the structured skill catalogue this extension supplies for a
run. The **ambient catalogue** is the incoming `systemPromptOptions.skills`
array at this extension's `before_agent_start` handler, including changes
made by earlier handlers. **Private candidates** are discovered from the
active agent's `skillPath` entries. A **selected name** is an entry in its
`skills` field.

The catalogue affects automatic model-facing skill discovery; it is not an
access-control mechanism or a separate conversation. Pi excludes skills with
`disable-model-invocation: true` from the formatted model-facing catalogue.
The extension does not enable those skills for model invocation.

## Agent fields and paths

| Field | Accepted values | Default and meaning |
| --- | --- | --- |
| `inheritSkills` | YAML boolean | `true`: start with the incoming ambient catalogue. `false`: start empty. Quoted booleans are invalid. |
| `skills` | Comma-separated string or YAML list of non-empty strings | Empty: add no explicit names. Names are trimmed; repeated names are resolved once. |
| `skillPath` | Comma-separated string or YAML list of non-empty strings | Empty: search no private paths. Entries are candidate discovery paths, not an instruction to advertise everything found there. |

An empty YAML list is valid. Empty elements, non-string list entries and other
field types invalidate the agent definition. Malformed frontmatter and
duplicate YAML keys also invalidate it. Unknown fields, including the
semantics of `permission:`, are not interpreted by the skill resolver.

A path may name a directory or an `.md` file. Relative paths resolve against
the directory containing the winning agent definition, not the session cwd;
absolute paths are supported. No shell expansion of `~` or environment
variables is performed. A trusted project definition shadows the global
definition of the same filename; definitions are not merged. Untrusted project
definitions are not considered. An invalid project definition still shadows a
global definition of that name.

Directory discovery checks root-level Markdown files and recursively discovers
`SKILL.md` files. A directory containing an eligible `SKILL.md` is treated as a
skill root and is not traversed further. Hidden entries and `node_modules` are
skipped, and `.gitignore`, `.ignore`, and `.fdignore` rules are considered.
Symlinks are followed with canonical-path cycle detection; traversal is
bounded. Skill metadata and validation diagnostics are obtained through Pi's
loader, supplemented by checked discovery.

## Catalogue construction

1. If there is no active agent in a genuinely fresh, resolved session, leave
   the incoming catalogue unchanged. An agent with `inheritSkills: true` and
   no selected names or private paths likewise preserves the incoming catalogue
   unchanged, including its order and duplicate entries.
2. Otherwise, begin with the incoming catalogue if inheritance is enabled,
   retaining the first entry for each name in incoming order. With inheritance
   disabled, begin empty.
3. Resolve distinct selected names in their declared order. A valid unique
   private match takes precedence over ambient matches. If there is no private
   match and discovery can establish that absence, use one unambiguous enabled
   ambient match. Explicitly selected ambient files are rechecked on disk for
   readability, validity, name, and model-invocation status; the incoming
   skill metadata is retained for the selected entry.
4. Replace an inherited entry of the same name in place; append other selected
   entries in declaration order. An empty isolated selection produces an empty
   catalogue. The incoming array is not mutated by the resolver.

A disabled skill already present in an inherited structured array remains
there; Pi's prompt formatter omits it from the model-facing catalogue. An
explicit selection of a disabled skill is an error.

## Resolution and diagnostics

| Condition | Result |
| --- | --- |
| Invalid owned agent field or definition | Agent is invalid. |
| Configured path is missing, inaccessible, unsupported, or cannot be safely inspected | Loadout error, even if no selected name matches that path. |
| Multiple distinct private candidates for a selected name | Loadout error. Multiple paths resolving to the same canonical private file count once. |
| One private candidate is invalid or disables model invocation | Loadout error; do not fall back to ambient for that name. |
| A private candidate cannot be classified by name | If names are selected, resolution cannot establish their uniqueness or absence and fails; otherwise retain the diagnostic as a warning. |
| No private candidate; one valid enabled ambient match | Select that ambient entry. Ambient matches with the same file path count once. |
| No private candidate; ambient match is missing, invalid, disabled, or ambiguous | Loadout error. |
| Unrelated invalid candidate or collision with a known unselected name | Retain a warning for inspection; it does not by itself invalidate the loadout. |
| Selected skill description exceeds Pi's recommended length | Retain Pi's warning; length alone does not invalidate the selection. |

The switch-time check is preliminary: it validates the definition, private
paths and known private candidates, but the future run's incoming ambient
catalogue is not yet available. A selected name that might come from ambient
skills is therefore pending rather than rejected merely for being absent from
private paths. A known-invalid switch is rejected before recording a new
`active_agent` entry.

## Run-time failure, recovery, and inspection

Resolution is repeated for each run. If a recorded agent's loadout is invalid,
the extension retains the recorded agent identity, reports the errors, marks
the loadout unresolved, sets the structured skill array to empty on a
best-effort basis, calls `ctx.abort()`, and blocks model tool calls while the
loadout is unresolved. It does not present a partial catalogue as successful.
Repair the files or select a valid agent; the next run revalidates the
loadout. An unresolved agent identity is handled separately and also blocks
model tool calls.

`/agent-skills` reports the latest run-time resolution snapshot: agent, mode,
time, advertised names and paths when resolved, and relevant diagnostics.
Before a resolution it says the loadout is not yet resolved; a failed
resolution is reported as unresolved, not as a valid empty catalogue. A
snapshot can become stale after switching, file changes or reload. It does not
reflect subsequent handlers or a forced full prompt.

## Boundaries and validation

* A later extension may change the structured catalogue; a forced full system
  prompt may mask structured changes. An earlier full-prompt override can be
  detected and warned about; a later one cannot be detected by this handler.
* Existing `/skill:name` commands, prior conversation history, cwd, filesystem
  access and user-entered shell commands are not changed by skill filtering.
  Private skills may not acquire slash commands, and ambient commands may
  remain available even when filtered from the catalogue. Permission policies
  and sandboxes are separate concerns.
* Emptying the array, aborting and blocking model tool calls are defenses in
  depth, not a universal guarantee that no provider request is sent. A
  provider or custom stream can ignore an abort signal. The README describes
  the Pi versions, adapters and frontends tested against local endpoints.
* `test/skills.test.ts` covers resolution; `test/skill-runtime.test.ts` and
  `test/skill-lifecycle.test.ts` cover Pi sessions and extension composition;
  CLI and TUI tests cover the corresponding frontend checks. Update these tests
  and this specification together when behavior changes.
