| name | skill-auto-select |
| description | Design a Skill (or Plugin) that an LLM agent can reliably discover, select, and invoke based on its description, with explicit selection syntax, name-collision handling, and three-layer matching.
USE WHEN: authoring a new skill for a Plugin, designing skill frontmatter, deciding between structured `UserInput::Skill` vs implicit `$skill-name` mention, handling duplicate skill names, picking between path-precise and name-based matching, or any task involving "make my skill actually get picked up by the agent".
TRIGGER PHRASES: "skill selection", "skill auto-pick", "$skill-name mention", "skill description", "skill metadata", "SkillMetadata", "ExplicitSkillLookup", "three-layer matching", "name collision", "ambiguous skill name".
SKIP WHEN: writing a one-shot script (use `error-recovery-strategy` or similar task skill), skill is human-only (no agent invocation), skill is bundled and not selectable.
|
| license | Apache-2.0 |
| compatibility | Requires MiniMax Code with Agent Plugins 1.0 support. |
| metadata | {"author":"antianqi","version":"0.1.0","inspired-by":"https://github.com/openai/codex/tree/main/codex-rs/skills/ (P-85/86/87/88/89/92)","changes-from-v0.0.0":"Initial design distilled from P-85/86/87/88/89/92 deep-dive (Phase 1 Week 2)."} |
Skill Auto-Select
Design a Skill (or a whole Plugin) so an LLM agent can reliably discover it, decide
it is the right one, and invoke it. Mirrors the design of Codex's codex-rs/skills/
runtime, which is what this very Plugin is mimicking.
When to use
Activate when designing:
- A new skill's frontmatter (
name, description, short_description, interface, dependencies, policy).
- A skill marketplace or registry where multiple skills may collide on name.
- A path-based discovery surface (logical discovery path vs canonical path).
- An explicit-vs-implicit invocation model (structured input vs
$name mention vs shell command invocation).
When NOT to use
- Skills that are bundled, not selectable (e.g. always-on system skills). Use a different distribution model.
- One-shot scripts that should never be auto-selected. Use task skills (
error-recovery-strategy, plan-stream-emit).
Process
1. Write the 11-field SkillMetadata
Every skill should expose at minimum these fields:
| Field | Type | Purpose |
|---|
name | String (≤ 64 chars) | Canonical name, used in mentions and uniqueness checks. |
description | String (≤ 128 chars) | One-line purpose, used by LLM to decide "is this for me?". |
short_description | Option<String> | UI label, used in lists. |
interface | Option<SkillInterface> | UI metadata (display_name, icon, brand_color, default_prompt). |
dependencies | Option<SkillDependencies> | Declared external tools (MCP / function / etc). |
policy | Option<SkillPolicy> | allow_implicit_invocation (default true), products. |
path_to_skills_md | AbsolutePathBuf | Host-side canonical path. |
scope | SkillScope | Source: User / System / Plugin / etc. |
plugin_id | Option<String> | If from a marketplace plugin. |
remote_plugin_id | Option<String> | If remote. |
| (system) | enabled | Computed from disabled_paths. |
In your frontmatter, the only fields that matter for LLM matching are name and
description. The other fields matter for the runtime.
2. Write a keyword-greppable description (v0.6.1 format)
description: |
<one-sentence purpose>.
USE WHEN: <comma-separated concrete signals and keywords>.
TRIGGER PHRASES: <user-original-language phrases the user might say>.
SKIP WHEN: <anti-patterns where this skill does not apply>.
Why:
- The LLM matches on real signals (
ECONNREFUSED, permission denied, retries exceeded, "上下文满了" / "出错了" / "重试"), not abstract prose.
USE WHEN and TRIGGER PHRASES are greppable substrings; SKIP WHEN reduces false positives.
- Bilingual (English + Chinese) descriptions match user language directly.
3. Adopt three-layer matching
When a user types $skill-name or [$skill-name](path):
Layer 1 — canonical path: /path/to/skills/SKILL.md
Layer 2 — discovery path: skill://skill-name/SKILL.md (logical)
Layer 3 — plain name: skill-name (only if unambiguous)
Rules:
- Layer 1 wins if path matches canonical.
- Layer 2 wins if path matches discovery path AND Layer 1 missed.
- Layer 3 wins ONLY if
skill_count == 1 && connector_count == 0 (uniqueness check via name_counts).
- If a structured
UserInput::Skill already matched some name, block that name from Layer 3 (blocked_plain_names).
Complexity target: O(T + (N_s + N_t) * S) time, O(S + M) space (T = text length, S = skill count, M = mentions per input). With ~20 skills and 1KB text, this is sub-millisecond.
4. Provide explicit invocation syntax
Two syntaxes, both supported:
$skill-name # plain
[$skill-name](skill://path/SKILL.md) # linked
Exclude environment variables from being mistaken for skills (is_common_env_var($HOME) → true, skip). Support the 5 tool mention kinds with 4 path prefixes:
app://app-id/...
mcp://server/tool
plugin://plugin-id/...
skill://skill-name/...
SKILL.md (literal filename)
5. Detect implicit invocation in shell commands
Before doing the explicit three-layer match, also detect when a shell command references a skill script or document:
detect_implicit_skill_invocation_for_command(outcome, command, workdir)
- Tokenize (Windows: PowerShell; Unix: shlex).
- Look for
python / node / bash / sh / pwsh invocations.
- Look for
Read operations on scripts/ or references/.
- Match by path (scripts dir → skill) and by doc (read path → skill).
6. Cache the loaded snapshot
Use a SkillRootSnapshotCache<Root> trait so the loader can re-use a parsed snapshot:
pub trait SkillRootSnapshotCache<Root>: Send + Sync {
fn get(&self, root: &Root) -> Option<LoadedSkillRoot>;
fn insert(&self, root: Root, snapshot: LoadedSkillRoot);
}
SkillRootSnapshots is Arc<dyn SkillRootSnapshotCache<Root>> with identity-based
Hash / Eq (uses Arc::ptr_eq). Cache key safety: clones share the same Arc,
so identity equality holds.
7. Load with errors-as-data
LoadedSkillRoot { skills, errors: Vec<SkillError>, ... } — never let one bad skill
kill the whole root. Collect errors and surface them at the top.
Output contract
A skill that follows this design:
- Has a 64-char-max
name and a greppable 128-char-max description.
- Supports both
$name plain and [$name](path) linked mention.
- Three-layer matching with uniqueness check on plain name.
- Implicit invocation detection in shell commands.
- Cached snapshot with identity-based hashing.
- Errors collected per-skill, never aborting the whole root.
Common pitfalls
- Plain name on a duplicate → ambiguous; ignored. Always provide a path or qualify with the structured form.
- Description too abstract → LLM cannot match. Use the 4-line
USE WHEN / TRIGGER PHRASES / SKIP WHEN format with concrete keywords.
- Bypassing the uniqueness check → two skills fire from one mention. Always require
skill_count == 1.
- Forgetting
is_common_env_var → $HOME / $PATH become "skill mentions". Filter them.
- Loading all skills on every mention → slow. Use
SkillRootSnapshotCache.
- Frontmatter name > 64 chars → rejected by parser. Count your characters.
- Skills with
description: "" → MissingField error. Description is mandatory.
Example — minimal frontmatter
---
name: my-skill
description: |
Detect a specific failure mode in the running session and recover.
USE WHEN: ECONNREFUSED, permission denied, retries exceeded, "can't connect" / "出错了" / "重试" / "权限".
TRIGGER PHRASES: "recover", "retry failed", "switch tool", "ask me", "出错了", "重试".
SKIP WHEN: short task, in middle of dictating.
license: Apache-2.0
compatibility: Requires MiniMax Code with Agent Plugins 1.0 support.
metadata:
author: you
version: "0.1.0"
---
... the actual instructions ...
Verification checklist