Skip to content

Skills

Skills are markdown files with YAML frontmatter that customize how the agent behaves. A skill can be always-on (part of every prompt) or dynamic (loaded on demand when it’s relevant).

Every skill declares one loading strategy:

Strategy Behavior
always Always composed into the system prompt. For identity, voice, and durable cross-cutting rules. Keep it short — it costs context on every turn.
dynamic Loaded on demand. For task / domain skills. Enters context through one of the activation signals below.

A dynamic skill activates through any of:

  1. Its description — the model sees every skill’s name + description in a catalog and activates the relevant one. This is the default path, so write the description to say what the skill does and when to use it (include the words a user would say). Always available — no extra field needed.
  2. tool-affinity — auto-activates when a tool in the active set matches one of its globs. For skills bound to specific tools.
  3. triggers — auto-activates on an exact phrase, for deterministic “must-fire” cases (e.g. a compliance rule that must load whenever a regulated action is named).

A dynamic skill with no tool-affinity and no triggers loads only when the model activates it on its description.

Every conversation’s system prompt carries a Skill Catalog — one line (name + description) per dynamic skill available in that workspace: authored org/workspace/user skills, skills published by installed apps, and curated connector overlays. Always-on skills don’t appear (their full text is already in the prompt), and neither do disabled ones.

When a task matches a listed skill, the model loads it with the nb__use_skill tool, which returns the skill’s full body in the tool result. A skill is delivered at most once per conversation — loading one that’s already in context (via nb__use_skill or a connector overlay) returns a short “already loaded” note instead of a second copy.

A skill is a markdown file (.md) with YAML frontmatter. Standard fields are top-level; NimbleBrain configuration nests under metadata.nimblebrain:

---
name: meeting-notes
description: >
Summarize and search meeting notes from Granola. Use when the user mentions
meetings, notes, agendas, minutes, or standups.
allowed-tools: granola__search_meetings granola__get_meeting
metadata:
author: mat
version: "1.0"
nimblebrain:
loading-strategy: dynamic
priority: 50
tool-affinity:
- granola__*
triggers:
- summarize my meetings
- meeting notes
---
You are a meeting notes assistant. Use the Granola tools to search and
summarize meeting transcripts.
When asked about meetings:
1. Search with granola__search_meetings
2. Retrieve transcripts with granola__get_meeting
3. Summarize the key discussion points, decisions, and action items
Format action items as a checklist.

Standard (top-level):

Field Type Required Description
name string Yes Lowercase letters, digits, and single hyphens (e.g. meeting-notes); ≤64 chars; matches the filename.
description string Yes What the skill does and when to use it — the activation signal for dynamic skills, so include the words a user would say. ≤1024 chars.
license string No License name or reference to a bundled license file.
allowed-tools string No Space-separated globs for tools this skill may call (e.g. granola__*).
metadata.author / metadata.version string No Conventional metadata.

NimbleBrain (metadata.nimblebrain.*):

Field Type Required Description
loading-strategy always | dynamic Yes How the skill loads (see above).
priority number No Selection / ordering priority, 11–99 (0–10 reserved for core). Default 50.
status active | disabled No Default active.
tool-affinity string[] No Globs; a dynamic skill auto-loads when a matching tool is active (e.g. granola__*).
triggers string[] No Exact phrases; a dynamic skill auto-loads on a case-insensitive substring match.

Skills are loaded from multiple locations, in order:

  1. Core skills (src/skills/core/) — shipped with the platform, always loaded
  2. Built-in skills (src/skills/builtin/) — shipped with the package
  3. Global skills (~/.nimblebrain/skills/) — user-created
  4. Config directories — any paths listed in skillDirs in nimblebrain.json
{
"skillDirs": [
"./skills",
"/home/user/custom-skills"
]
}

Files must have a .md extension.

Three core skills are always-on for every workspace and need no setup:

Skill What it does
soul Base identity — use your tools, don’t guess, be concise and direct.
customer-communication Talk about the customer’s goal and the work, not the tool names, IDs, and internal state used to do it.
capabilities How to find the tools an app publishes and promote them into the active set.

All three ship with the platform, so they can’t be edited in place — skills__update refuses to write a platform-vendored file. Change one by authoring your own skill with the same name in a scope that outranks it.

Skills merge by name, with later tiers winning: user > workspace > org (~/.nimblebrain/skills/) > platform. So a file at ~/.nimblebrain/skills/customer-communication.md replaces the shipped one for the whole org; the same name under a workspace or a single user narrows it further. Writing an org-scoped skill requires an org admin or owner role.

soul and capabilities are reserved names — skills__create refuses them, so an override for those two has to be a file you write yourself. customer-communication is not reserved, so either route works.

To turn a default off rather than reword it, give the override status: disabled — an always-on skill that isn’t active is dropped before the prompt is composed. Author it directly as a file, or create the override and toggle it off in the Skills app:

---
name: customer-communication
description: Disabled — this deployment wants the machinery visible
metadata:
nimblebrain:
loading-strategy: always
status: disabled
---

Installing a connector can automatically apply a short usage overlay — curated guidance for that connector’s tools (e.g. “confirm the recipient before sending mail”). Overlays target the connectors you don’t author yourself (Composio and other third-party MCP servers), so they’re sourced from a NimbleBrain-curated public overlay repo keyed by the connector’s flat slug (the Composio toolkit, e.g. gmail; otherwise the connector slug, e.g. notion) at a pinned version.

Overlays differ from authored skills in three ways:

  • Surfaced once into the conversation, not the system prompt. An authored dynamic + tool-affinity skill composes into the system prompt for the turn. A connector overlay instead appears in the conversation history exactly once — on the first call to a matching tool — and then rides the (cached, append-only) history for the rest of the conversation. It’s relevant (only when the connector’s tools are actually used) and cache-friendly (it never re-varies the cached system prefix turn to turn). The tradeoff of this reactive model: the overlay surfaces after the first matching tool call, so guidance whose value is preemptive (e.g. “confirm before sending”) does not gate that very first call. It does reach the model before its next action in the same turn, so it governs every call after the one that triggered it. For hard preconditions, prefer an authored always skill or a tool-side guard.
  • Bound to the connector’s tools. An overlay is bound to every tool of the connector it is installed for (<server-name>__*), or, when its frontmatter declares metadata.nimblebrain.tool-affinity, only to the tools it names, read the same way as for a skill published by the server. The block needs nothing else: the other runtime fields are stamped on install, and an overlay always loads dynamic.
  • Stored separately. Materialized overlays live in a connector-skills/ store, distinct from authored skills/. They don’t appear in the skills__list tool output; list them with the connector tool’s list_bound_skills action instead.

Resolution is always on and fail-soft: it fetches at a pinned repo version, records the fetched content hash, and treats a missing overlay (or any fetch error) as a no-op — a connector install never fails because its optional guidance couldn’t be fetched, and there’s nothing to enable (an overlay exists only if we’ve curated one). Override the repo / version with CONNECTOR_SKILLS_REPO and CONNECTOR_SKILLS_VERSION. When a connector has a curated overlay, the platform skips synthesizing the server’s own skill://<name>/SKILL.md guidance so the model sees one set of instructions, not two.

Uninstalling the connector removes its materialized overlays.

A server publishes its own guidance as skills through the MCP Skills Extension (io.modelcontextprotocol/skills). The platform declares the extension when it connects, and a server that does not declare it publishes no skills: its skill:// resources are ordinary resources.

To publish skills, a server must:

  1. Declare the extension in its capabilities: "extensions": { "io.modelcontextprotocol/skills": {} }, alongside the resources capability. On a 2026-07-28 connection the platform asks only a server that declares it. On a 2025-era connection it asks every server once per discovery window, because some SDKs (the Python mcp SDK among them) leave the declaration out of the legacy initialize result; a server that answers -32601 (method not found) publishes no skills.
  2. Implement skills/list, returning one entry per skill: the SKILL.md URI, its frontmatter verbatim as JSON, and a resources manifest of every file in the skill with its SHA-256 digest and byte size (or "dynamic" for generated content).
  3. Implement skills/get, returning the same entry for one skill URI.
  4. Serve each file with resources/read.

How the platform uses them:

  • Discovery reads the listing only. The catalog and each skill’s loading rules come from the listed frontmatter; no SKILL.md is fetched until the skill is needed. An always skill is fetched when a turn composes it, a dynamic skill when tool-affinity or a trigger selects it or when the agent activates it with nb__use_skill.
  • Every fetched SKILL.md is verified. Its bytes must match the listed digest and size, and its frontmatter the listed frontmatter, or the skill does not load that turn. A verified body is cached by digest, so an unchanged skill is not fetched again; a server that updates a skill lists the new digest.

The skill’s name is its frontmatter name. When two skills on one server share a name, each is named by its full skill path instead (acme/billing/refunds). The platform reads the same metadata.nimblebrain.* block documented above off that published frontmatter, so a published skill loads by the rules its author writes rather than a fixed convention:

Field Effect on a published skill
loading-strategy always composes it every turn; dynamic (the default when omitted) loads it on demand.
priority Ordering, same scale. Defaults to 60.
triggers Phrases that load it deterministically, whether or not the server’s tools are in the active tool set.
tool-affinity The server’s own tools the skill governs, as bare names or globs (draft_email, draft_*). The platform prefixes each with <server-name>__. Omitted, the skill is bound to every tool of the server (<server-name>__*).

Two differences from an on-disk skill:

  • Reading is lenient, not strict. A malformed or absent metadata.nimblebrain block leaves the fields unset and the defaults apply — an unrecognized value never rejects the skill. (An on-disk skill with bad frontmatter is skipped and logged.) Blank trigger phrases are dropped: an empty phrase would match every message.

  • tool-affinity is scoped to the publishing server. A server cannot know the name it is installed under, so it declares its tools bare and the platform binds them to that install. Matching is anchored after the prefix, so a declared pattern can only narrow within the server’s own tools: * means all of them, and no declared value reaches another connector’s tools. A dynamic skill loads, and is delivered when a tool is promoted or called mid-turn, only when a tool it names is involved, so a server that publishes several workflow skills should give each one the tools it governs. A declared pattern that matches none of the tools the connector advertises is logged as a warning (skills.tool_affinity.unmatched, naming the connector, the skill, and the patterns); the same check applies to an overlay’s patterns. allowed-tools is not read from the server.

    ---
    name: writing
    description: How to draft email in the user's voice. Use when drafting or replying.
    metadata:
    nimblebrain:
    loading-strategy: dynamic
    tool-affinity:
    - draft_email
    - reply_*
    ---

If a connector has a curated overlay, the platform skips synthesizing its published skills — see the section above.

Ask the agent which skills are loaded — it uses the built-in skills__list tool to report each skill’s name, loading strategy, priority, and source file. To see one skill’s full definition, ask the agent for its details.

skills__list says which skills exist. To see which ones actually reached the model, and how, replay the skill-load ledger with skills__loading_log.

Ask the agent and it reports the summary — how many loads, across how many conversations, broken down by channel. The per-load rows come back in the tool’s structured output, so calling it directly (over the API or from an MCP client) is the path for analysis. Each row carries the skill, timestamp, conversation, tokens, and a loaded_by channel:

loaded_by Meaning
always Composed into the system prompt because the skill declares loading-strategy: always.
tool_affinity Composed in because a tool matching its globs was in the active set.
trigger Composed in because an exact trigger phrase appeared.
tool_use A connector overlay delivered on the first matching tool call.
activation The model loaded a Skill Catalog entry by name with nb__use_skill.

Filter by conversation_id, skill (name or id), loaded_by, or a since/until window. This is the way to answer questions the catalog alone can’t — whether a skill is reaching the model at all, and whether it gets there because the model chose it or because a tool call forced it.

  1. Create a .md file in your skills directory:

    Terminal window
    mkdir -p ~/.nimblebrain/skills
  2. Write the skill file with frontmatter and body:

    Terminal window
    cat > ~/.nimblebrain/skills/code-review.md << 'EOF'
    ---
    name: code-review
    description: >
    Review code for bugs, security issues, and style. Use when the user asks
    to review code, check a diff, or look for bugs.
    allowed-tools: bash__run
    metadata:
    nimblebrain:
    loading-strategy: dynamic
    priority: 50
    triggers:
    - review this code
    - code review
    ---
    You are a code reviewer. When asked to review code:
    1. Read the file using bash tools
    2. Identify bugs, security issues, and style problems
    3. Suggest specific improvements with code examples
    EOF
  3. Restart the server to load the new skill. In development, bun run dev reloads on source changes.

  4. Verify it loaded by asking the agent which skills are available (it uses the skills__list tool).