SKILL.md Reference
The schema of record for a skill — its required name and description, the optional metadata object, and how the body becomes agent context.
Every skill is defined by a single SKILL.md at the root of its directory: a block of YAML frontmatter followed by a Markdown body. This page is the schema of record, drawn directly from the scanner (app/skills/scanner.py). It covers exactly what is required, what is optional, and how each part is used.
File anatomy
A SKILL.md is frontmatter delimited by --- lines, then Markdown:
---
name: my-skill
description: One sentence describing what this skill does and when to use it.
metadata: {
# optional — see below
}
---
# my-skill
**Purpose:** ...the instruction manual the agent reads...The scanner finds the file case-insensitively (SKILL.md or skill.md), reads it as UTF-8, and splits the first ----delimited block as YAML.
Required frontmatter
Exactly two fields are required, and both must be non-empty strings:
| Field | Type | Purpose |
|---|---|---|
name | string | Unique skill identifier. By convention it matches the directory name. Surfaced in the skill's section header in the agent prompt. |
description | string | One sentence: what the skill does and when to reach for it. This is the only frontmatter field injected into the agent's reasoning prompt. |
If either is missing or not a string, the scanner logs a warning and skips the directory — the skill simply will not load.
The description does real work
The description is what the agent reads to decide whether a skill is relevant. Write it as a precise, single sentence covering the verbs the skill supports and the system it touches. Vague descriptions make the agent miss the skill.
What the agent actually sees
A subtle but important detail: when the scanner builds the prompt context for a skill, it rebuilds the frontmatter to contain only description. The name is surfaced separately in the skill's section header, and every other frontmatter field — including metadata — is stripped before the content reaches the agent. The agent then sees a clean frontmatter plus your full Markdown body.
So the body is the instruction manual. The frontmatter is metadata for Cremind, not prose for the model.
The metadata object (optional)
metadata is an optional YAML mapping. The scanner parses it and stores it on the skill record, but — as the scanner's own docstring states — it is "preserved for future use": the reasoning prompt currently consumes only name and description. Treat metadata as the established convention for declaring a skill's machine-readable shape, while knowing it is read by surrounding tooling (notifications, listener registration) rather than fed to the agent today.
metadata: {
environment_variables: ["CREMIND_CONNECT_URL", "GOOGLE_CLIENT_ID"],
optional_environment_variables: ["CREMIND_CONNECT_URL"],
events: {"event_type": [{"name": "new_email", "description": "A new email arrived in the INBOX"}]},
long_running_app: {
command: "uv run scripts/event_listener.py",
description: "Persistent listener that drops new messages as markdown."
}
}environment_variables
A list of environment-variable names the skill expects to be configured (for example credentials or endpoints). Cremind writes a skill's configured variables to scripts/.env, which the script reads at runtime. Declaring them here lets the Settings UI render a config form for the skill.
optional_environment_variables
A list of variable names that are optional — they have sensible defaults and only need to be set to override behavior. The built-in skills, for example, list their relay URL and OAuth client id here because they are fetched dynamically from Cremind Connect unless you choose to override.
events
A map from the literal key event_type to a list of {name, description} objects, one per event the skill can emit:
events: {"event_type": [
{"name": "new_event", "description": "A new calendar event was added"},
{"name": "updated_event", "description": "An existing event was modified"}
]}Each name is an event type and corresponds to a drop-zone folder at events/<name>/. This declaration is what the Subscribe dialog in the web UI and cremind skill-events events <skill> read to show which events you can subscribe to. See Event Subscriptions.
long_running_app
Declares the skill's persistent background process — its event listener. It is an object with two fields:
| Field | Meaning |
|---|---|
command | The exact command line that launches the listener, e.g. uv run scripts/event_listener.py. |
description | A human-readable summary of what the process does. |
When a skill with a long_running_app lands in a profile for the first time, Cremind pushes a high-priority notification prompting you to register the background process. See Event Listeners.
The Markdown body
Everything after the closing --- is the body, and it is the skill's documentation for the agent. There is no required structure, but the built-in skills follow a useful pattern worth copying:
- Purpose — a short paragraph restating what the skill is and the auth/setup model.
- How it works — the mental model (for event skills, how the listener and relay interact).
- Setup — what to configure, with defaults called out.
- CLI Commands — a table of subcommands with their required and optional flags.
- Examples — copy-pasteable command lines.
- Event listener — behavior, state files, and the event markdown schema.
- Troubleshooting — the common failure messages and their fixes.
- Module layout — a directory tree so the agent (and you) know where things live.
Because the body is injected verbatim, treat it as both human docs and a prompt. Be concrete: show exact subcommands and flags, because the agent will reproduce them.
Other frontmatter fields
The scanner's docstring is explicit that any frontmatter field other than name, description, and metadata (such as variables or arguments) is ignored. Don't rely on them.
Validation rules, in one place
namemust be a non-empty string, else the skill is skipped.descriptionmust be a non-empty string, else the skill is skipped.metadata, if present, must be a mapping; a non-mapping value is treated as empty.- Duplicate
names across directories: the first one scanned wins, and the duplicate is skipped with a warning.