skill-creator
Create or update local skills in this agent home repo.
Phase 0: Should this be a skill?
Before creating a new skill, answer these three questions. If any answer is "no" or "unclear" — stop and write a reply or a wiki page instead.
Recurring? Has this workflow appeared ≥2 times, or is it clearly going to repeat? One-off workflows are not skills — they bloat the catalog and dilute discoverability.
Non-trivial? Will the body exceed ~20 lines of meaningful steps? A three-line procedure belongs in a reply, not a skill file.
Clear trigger? Can you write a one-line description that unambiguously tells the agent when to invoke this skill — and when NOT to? Fuzzy triggers produce noisy skill catalog entries that get invoked on the wrong task.
All three "yes" → proceed. One or more "no" → don't create the skill.
Where Skills Go
User-editable skills belong in:
skills/<skill-name>/SKILL.md
Example:
skills/triage-issues/SKILL.md
Built-in skills are exposed at:
/mimir/skills/<skill-name>/SKILL.md
Treat built-in skills as read-only.
Critical Rule: Trigger Description
The YAML frontmatter description is the trigger signal. It must make it obvious
when the skill should be used.
Every skill description should include:
- what the skill does
- exact "when to use" triggers
- what it should not be used for
Bad description:
Helps with docs.
Good description:
Create and update release notes from git history. Use when the user asks for changelogs, release summaries, or version notes. Do not use for code changes.
Frontmatter schema
Every SKILL.md begins with YAML frontmatter delimited by --- lines. The parser of record is mimir/skill_md.py — when in doubt, that file determines what's accepted and rejected. Required and recommended fields:
| Field | Required | Shape | Notes |
|---|---|---|---|
name |
yes | string | The skill's short identifier (matches the directory name). |
description |
yes | string | Trigger description — what the skill does + exact "when to use" cues + what it should NOT be used for. Single line is safest; if you need length, use plain quoted strings (NOT YAML folded > blocks — those have a known parser pitfall, see "Gotchas" below). |
success_criteria |
optional | dict | Operator-declared "did the skill's procedure actually run?" test for outcome telemetry. See mimir/skill_outcomes.py:SkillSuccessCriteria for the shape (any_of list of tool_call patterns, with name + optional args / args_glob). Skills with a clear declarative completion signal benefit from declaring this; meta-cognitive skills can omit it. |
allowed-tools |
optional | string or list | Descriptive tool names rendered with the skill. This metadata does not authorize or restrict request tools. |
The <!-- desc: --> first-body-line convention
The line immediately after the closing --- of frontmatter should be an HTML comment of the form:
<!-- desc: <one-line description of what's in this file> -->
The auto-managed memory/INDEX.md (and state/INDEX.md) scrape this line to render the file's row in the every-turn-visible index. Without it, the indexer falls back to the first sentence prefixed with [auto] — usable but lower-signal for skill authors who want their skill discovered.
Important scope note: SKILL.md files are not currently walked by the memory indexer. The desc-line in a SKILL.md is a convention for human readers and future tooling — it is not scraped into memory/INDEX.md or state/INDEX.md today. The live skill discovery path is memory/skills-catalog.md (maintained by mimir skills catalog, regenerated on index drift). If the memory indexer is extended to walk skill directories in a future release, the desc-line would automatically become the index entry — that's why the convention is enforced now.
Conventions:
- Single line, ≤200 chars. The row appears in the system prompt every turn — keep it dense.
- Agent-facing voice (what the skill does), not engineer-facing (which chainlink filed it, what commit shipped it).
- Stable across revisions — if the skill's purpose changes substantially, change the
<!-- desc: -->; if just the body changed, leave the comment alone.
The conformance test (test_skill_md_body_starts_with_desc_comment in tests/test_skill_conformance.py) enforces this — a missing <!-- desc: --> is a CI failure (chainlink #102). The line is required; the indexer's [auto] fallback path is a lint-only safety net, not a production path.
allowed-tools metadata
allowed-tools is optional descriptive metadata. It accepts either a scalar with space- or comma-separated tool names or a YAML list of tool-name strings. DeepAgents normalizes both forms and renders the names in the skill listing.
This field is not an authorization boundary. It does not add, remove, restrict, or grant request tools. Runtime tool availability and authorization come from the agent graph, middleware, and access-control policy. Use the field to help readers understand the expected procedure, describe tool use in the body, and use success_criteria to measure whether the canonical tool actually fired.
Skills that send a message to a specific channel
If your skill calls send_message, be deliberate about channel_id — getting it wrong fails at runtime, not authoring time.
Replying in context? Omit
channel_id. It defaults to the current turn's channel (send_message(text=...)). This is the right choice for almost every skill — don't name a channel you don't need to.Targeting a different channel? The id must be prefix-qualified, not a raw platform id. The registered bridge dispatches by prefix:
discord-<channel_id>/dm-discord-<dm_channel_id>slack-<channel_id>/dm-slack-<im_id>web-<conv_id>,bench-<task_id>
Concrete: a Discord channel whose raw id is
123456789012345678is addressed asdiscord-123456789012345678. Passing the bare number123456789012345678has no registered bridge and raisesUnknownChannelError(mimir/channel_registry.py). The canonical prefix scheme is SPEC §7.2.3.To DM a person, use their DM channel id — not their user id. A DM channel id is the platform's conversation id (Slack
dm-slack-D…, Discorddm-discord-<snowflake>), captured the first time they message you and listed by thelist_channelstool asdms[].channel_id.dm-slack-<user_id>is wrong — calllist_channelsto get the real id rather than constructing it from a user id.Never hardcode a channel id in the SKILL.md. Channel ids are deployment-specific — a literal baked into the skill body will work in your head and fail on someone else's deployment. Source them from runtime instead:
- The operator alert channel is named in the system prompt's Operator config section (
MIMIR_OPERATOR_ALERT_CHANNEL). Thealertbuiltin skill is the reference example. - Other targets come from the current turn (omit
channel_id), the identities registry, or operator-set skill config — not a literal in the body.
- The operator alert channel is named in the system prompt's Operator config section (
Developer-facing sibling docs (DESIGN.md and friends)
When a skill has content that's useful to developers (impl cross-refs, architecture notes, migration history) but doesn't help the agent mid-turn, putting it in SKILL.md costs prompt tokens every time the agent reads the file. The fix is a sibling doc in the same directory.
Convention
For any skill that has developer-facing supplementary content, add a sibling file alongside SKILL.md:
mimir/skills/<name>/
├── SKILL.md # agent + operator-facing: prose, frontmatter, usage
└── DESIGN.md # developer-facing: implementation cross-refs (optional)
Other potential siblings that may emerge:
ARCHITECTURE.md— internal mechanism notes too long for SKILL.md proseMIGRATION.md— how this skill replaced earlier patterns, upgrade paths
Pointer line in SKILL.md
Add a single blockquote near the relevant section (or in a footer):
> Implementation seams (code paths for each tier) are in
> [`DESIGN.md`](DESIGN.md) — developer reference; not loaded by the agent.
The pointer is prose, not a structural element — it tells a developer
where to look without making the agent load the file. The agent won't
auto-surface DESIGN.md unless it explicitly Reads the path.
What goes in DESIGN.md
Typically: a heading per concept from SKILL.md (tier, step, mechanism) with
a _→ file.py:function()_ annotation pointing to the implementing code.
The memory skill's DESIGN.md (PR #388) is the canonical reference
example — 12 tiers mapped to core_blocks.py, prompts.py, searchtools.py,
etc.
Per-skill conformance test (recommended)
Each adopter should add a test that pins the invariant across both files. For the memory skill:
- Test asserts: tier count in SKILL.md ≥ 12, seam annotation count in DESIGN.md ≥ 12.
- When one file changes without the other, the test fails loudly.
See tests/test_skill_defs.py:test_memory_skill_visibility_tiers for the
pattern.
When to use
Opt-in, not required. Use when:
- The implementation-seam / architecture notes are long enough that reading SKILL.md mid-turn noticeably increases token cost (≥100 lines or ≥10 seam annotations).
- The content is clearly developer-facing (code paths, file line numbers, design rationale for maintainers) rather than agent-useful context.
For short skills (< 100 lines total), keep all content in SKILL.md.
Gotchas
- YAML folded scalars (
description: >): the bundledmimir/skill_md.pyparser handlesdescription: >anddescription: |blocks correctly (covered bytest_parse_frontmatter_handles_folded_description;mimir/skills/onboarding/SKILL.mdships using folded form), so mimir's own catalog + INDEX rendering is fine. The pitfall is for downstream tooling that usesyaml.safe_loaddirectly: if the next key sits at the same indent without a blank-line separator,yaml.safe_loadcan fold it into the description's value. Stick to plain quoted strings if you want maximum portability across tooling that doesn't go throughmimir/skill_md.py. - Stale catalog: after editing a SKILL.md, run
mimir skills catalogto regeneratememory/skills-catalog.md. The catalog isn't auto-regenerated on file write today. - Missing
<!-- desc: -->: CI fails (test_skill_md_body_starts_with_desc_comment). Add the comment immediately — see step 3 of the Authoring Checklist below.
Authoring Checklist
- Phase 0 gate — verify Phase 0 criteria above before touching anything.
- Write frontmatter with
nameand a high-signaldescription. Optionally add asuccess_criteriablock if the skill has a clear declarative completion signal (a Bash command pattern, a Write to a specific path, etc.) — seemimir/skills/weather/SKILL.mdfor a minimal example. - Add the
<!-- desc: -->first-body-line comment (agent-facing voice, dense). - Add concise execution steps in the SKILL body.
- Include concrete paths/commands the agent should run. If the skill calls
send_message, follow "Skills that send a message to a specific channel" above — omitchannel_idto reply in context, and never hardcode a channel id. - Keep scope narrow; split broad domains into multiple skills.
- Prefer deterministic instructions over generic advice.
- Quality gate — read the skill as an outsider. Before writing tests, re-read the complete SKILL.md as if you were a different agent seeing it for the first time. Ask: Is the trigger unambiguous? Would the procedure produce consistent output across different runs? Are the steps concrete enough to follow without context? Fix gaps now — tests lock in behavior, so verify quality before tests encode it.
- Optional: sibling doc — if the skill has ≥100 lines of developer-facing implementation notes (code paths, architecture rationale), move them to
DESIGN.mdand add a pointer blockquote in SKILL.md. See "Developer-facing sibling docs" above. - Test-gate (new skills only) — For a new skill (not an edit to an existing one), write
tests/test_<skill-name>_skill.pywith at minimum one skill-specific assertion before declaring the skill accepted. The assertion should check something the general conformance suite doesn't already cover — e.g., that the SKILL.md body contains the required phases/sections, that thesuccess_criteriapattern validates correctly against a sample tool-call, or that a key trigger phrase is present in the description. Then run all three:pytest tests/test_skill_conformance.py tests/test_skill_catalog.py tests/test_<skill-name>_skill.py. If any test fails: revise SKILL.md (or the test) and re-run — do not declare done on a failing suite. The rewrite-loop is: write → run tests → fail → revise → re-test → accept. The skill is only "registered" once all targets pass green. This is the MUSE-Autoskill quality gate (state/wiki/concepts/muse-autoskill-skill-lifecycle.md§Test-gated registration). - Run
pytest tests/test_skill_conformance.py tests/test_skill_catalog.pylocally to catch frontmatter drift before pushing. - After landing, run
mimir skills catalogto refresh the catalog file.