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:

StrategyBehavior
alwaysAlways composed into the system prompt. For identity, voice, and durable cross-cutting rules. Keep it short — it costs context on every turn.
dynamicLoaded 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):

FieldTypeRequiredDescription
namestringYesLowercase letters, digits, and single hyphens (e.g. meeting-notes); ≤64 chars; matches the filename.
descriptionstringYesWhat the skill does and when to use it — the activation signal for dynamic skills, so include the words a user would say. ≤1024 chars.
licensestringNoLicense name or reference to a bundled license file.
allowed-toolsstringNoSpace-separated globs for tools this skill may call (e.g. granola__*).
metadata.author / metadata.versionstringNoConventional metadata.

NimbleBrain (metadata.nimblebrain.*):

FieldTypeRequiredDescription
loading-strategyalways | dynamicYesHow the skill loads (see above).
prioritynumberNoSelection / ordering priority, 11–99 (0–10 reserved for core). Default 50.
statusactive | disabledNoDefault active.
tool-affinitystring[]NoGlobs; a dynamic skill auto-loads when a matching tool is active (e.g. granola__*).
triggersstring[]NoExact 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.

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 two 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.
  • 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 can publish its own guidance as skill resources — skill://<name>/SKILL.md, discovered by listing the server’s resources. 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:

FieldEffect on a published skill
loading-strategyalways composes it every turn; dynamic (the default when omitted) loads it on demand.
priorityOrdering, same scale. Defaults to 60.
triggersPhrases that load it deterministically, whether or not the server’s tools are in the active tool set.

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 and allowed-tools are not read from the server. The platform stamps affinity itself, as <server-name>__*, so a dynamic published skill loads whenever that server’s tools are active.

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_byMeaning
alwaysComposed into the system prompt because the skill declares loading-strategy: always.
tool_affinityComposed in because a tool matching its globs was in the active set.
triggerComposed in because an exact trigger phrase appeared.
tool_useA connector overlay delivered on the first matching tool call.
activationThe 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).