Rules & Skills¶
bnerd's AI chat surfaces (the TUI's :ai/:code/:pa assistant and bnerd web) share one markdown-driven system for layering reusable domain knowledge onto the model's system prompt: rules and skills. A rule is prose guidance the model should generally follow (security conventions, report structure, house style); a skill is the same, plus — optionally — a set of tool definitions that only exist while the skill is active. This page covers where they're loaded from, when their content actually reaches the model, and how a skill gets loaded on demand.
For the mission-catalog side of this — a team role force-loading specific skills via its skills: frontmatter — see Team Recipes → Role frontmatter fields and Agent Profiles.
Rules vs. skills¶
Both are markdown files with YAML frontmatter; the body is the prompt text injected when the entry is active.
| Frontmatter key | Meaning |
|---|---|
name | Unique identifier (also what you pass to /skill load, /skill unload, and the skill tool) |
description | One-line summary — shown in /skills and in the "Available Skills" advertisement (see below) |
kind | rule or skill. Omit (or any other value) and it's treated as a skill. |
applies_to | List of modes (chat, code, pa, or a team role name) the entry is relevant to. Omit for "applies everywhere." |
always_on | Overrides the kind's default (see below) |
tags, keywords | Classification and case-insensitive keyword-trigger words for the TUI/web auto-load path (unchanged by this feature — a text match against keywords auto-enables an entry the same way it always has) |
tools | Skills only. Tool definitions the skill activates while it's active — see Permission Rules → Deny rules now reach subagents, teammates, and skill tools for how those tools are evaluated against permission rules. |
The one default that differs between the two kinds: a rule defaults to always_on: true (it's meant to just apply), a skill defaults to always_on: false (it's meant to be loaded when relevant). Either can override its default explicitly.
The lazy-loading model¶
On every API round, the dynamic part of the system prompt is rebuilt from whatever is currently active, plus a directory of what else could be loaded. This part is new; how an entry becomes active is not — keyword matching, /skill load, and a team role's skills: list all still work exactly as before.
- Active Rules — the full body of every currently-active rule.
- Active Skills — the full body of every currently-active skill.
- Available Skills — for every skill (not rule) that applies to the current mode but isn't active, just its name and one-line description. This is the only state a rule doesn't get: an applicable-but-inactive rule is simply absent from the prompt, not advertised — only skills are self-loadable, so only skills get a directory entry.
The model loads an available skill by calling the skill tool with its exact name. That call returns the skill's full body as the tool result for the current turn (so the model can act on it immediately); the skill also becomes active in the registry, meaning it appears in Active Skills in the prompt from the next round onward. If the skill declares tools, those tools are registered at the same time, but — like any newly-registered tool — they become callable starting the next round, not the one in progress, since the backend needs the updated tool list to know they exist.
Why: an inactive skill never rode along in the prompt — skills have always defaulted to always_on: false — but before this feature the model had no way of knowing it existed, let alone of reaching for it. A skill only became active if a keywords match happened to fire or the operator typed /skill load, so in practice a good skill sat unused unless someone remembered it by name. The win is discovery plus on-demand bodies: every applicable skill is now advertised in one line each, and its full text arrives as a tool result at the exact moment the model decides it's relevant — not earlier, not never.
The prompt block is also cache-friendlier for a second, narrower reason: the three sections are rendered in name order, so a session whose active set hasn't changed produces a byte-identical block round after round, instead of reshuffling with Go's map iteration and invalidating the prompt cache prefix every turn.
Discovery¶
Rules and skills are loaded from four tiers, each able to override an entry of the same name defined by an earlier tier:
| Precedence (low → high) | Path | Format |
|---|---|---|
| 1. Builtin | embedded in the bnerd binary | flat .md |
| 2. Global | ~/.bnerd/rules/*.md, ~/.bnerd/skills/*.md | flat .md |
| 2. Global | ~/.claude/skills/*/SKILL.md | SKILL.md subdirectory |
| 2. Global | ~/.bnerd/skills/*/SKILL.md | SKILL.md subdirectory |
| 3. Project | <workdir>/.bnerd/rules/*.md, <workdir>/.bnerd/skills/*.md | flat .md |
| 3. Project | <workdir>/.claude/skills/*/SKILL.md | SKILL.md subdirectory |
| 3. Project | <workdir>/.bnerd/skills/*/SKILL.md | SKILL.md subdirectory |
| 4. Mission | <mission repo>/orchestrator/skills/*.md | flat .md |
| 4. Mission | <mission repo>/orchestrator/skills/*/SKILL.md | SKILL.md subdirectory |
Project skills directories are trusted input
Tier 3 is whatever the checked-out repository contains: anyone who can land a file in <workdir>/.bnerd/skills/ or <workdir>/.claude/skills/ writes text that goes into the model's system prompt — and, from a .bnerd source, tool definitions that go into its tool list. Treat a project's skills directory with the same care as its CI configuration or its Makefile, and review changes to it in the same pull requests. Two guard rails narrow the blast radius, but neither makes an untrusted repo safe to run against: a SKILL.md under a .claude/skills tree cannot declare tools, kind: rule or always_on (see SKILL.md format), and no project file may turn a built-in rule into a skill (see Overriding a built-in).
Two invariants this ordering preserves:
- Project always beats global, regardless of which format either side uses — a project
.claude/skills/foo/SKILL.mdoverrides a global flat~/.bnerd/skills/foo.md, not the other way around. - Within one tier, the SKILL.md-subdirectory format beats the flat-file format for the same name — so migrating a personal skill from
~/.bnerd/skills/foo.mdto~/.claude/skills/foo/SKILL.md(or vice versa) is a clean override, not a collision.
Mission is placed last (highest precedence) rather than lowest: a mission repo's orchestrator/skills is the team catalog's canonical, most-current set for that mission, matching how mission-sourced roles and recipes already take priority over host-local ones (see Team Recipes → Schema loading and precedence). It only applies when a mission repo is configured or discovered; without one, discovery stops at tier 3. Every surface resolves the mission repo the same way — mission-repo in ~/.bnerd.yaml, --mission-repo, or BNERD_MISSION_REPO, else by walking up from the working directory — so the TUI assistant, bnerd web and a team teammate all see the same tier 4.
SKILL.md format¶
The */SKILL.md layout is the same directory-per-skill convention Claude Code itself uses, so a skill directory built for Claude Code loads here too (and vice versa) as far as v1 goes:
- A skill directory needs
nameanddescriptionin its SKILL.md frontmatter; a directory missing either — or missing SKILL.md itself — is skipped silently, matching Claude Code's own tolerance of junk directories in this layout. - Omitting
kind(as most SKILL.md files do) is treated askind: skill. - Unknown frontmatter keys are ignored, so a SKILL.md written with Claude Code-specific fields bnerd doesn't understand still loads fine.
- A SKILL.md under a
.claude/skillstree is always a plain, opt-in skill.kind,always_onandtoolsare bnerd extensions that the Claude Code format doesn't have, so under~/.claude/skillsor<workdir>/.claude/skillsthey are ignored: the entry loads askind: skill,always_on: false, with no tools. Nothing the upstream format defines is lost, and a directory you share with Claude Code can't quietly become an always-on rule that also registers tools. Use bnerd's own.bnerd/skills/<name>/SKILL.md(or the flat.bnerd/skills/<name>.md) when you want the full schema — same subdirectory layout, same precedence tier. - Only
SKILL.mditself is read. A skill directory's companion files — areferences/subfolder, bundled scripts, additional docs — are not inlined or exposed in v1. See the roadmap for the deferred follow-up.
Overriding a built-in¶
Defining an entry with the same name as a built-in replaces it — that's the supported way to customize house rules: put your own k8s-security.md in .bnerd/rules/ and yours is the one that loads.
One case is refused: a later tier may not turn a built-in rule into a skill. A built-in rule is always-on text nobody opted into and nobody is watching, so a same-named kind: skill file would quietly convert it into something opt-in that then simply never loads — the guidance disappears from every prompt and nothing says so. When that happens the built-in rule is kept and a warning is recorded instead:
Warning: project "k8s-security" declares kind "skill" but "k8s-security" is a
built-in rule; keeping the built-in rule (rename it, or use kind: rule to override)
The TUI prints it as a system message in the chat transcript when the skills registry is first loaded; bnerd web prints it to stderr at startup, alongside the agent-catalog load warnings. Rename your file, or declare kind: rule, to override the built-in properly. Overriding a built-in skill with either kind, and overriding a built-in rule with your own rule, are unaffected.
Where the skill tool is (and isn't) available¶
- TUI and
bnerd web(chat/code/pa, and any mission-catalog role running as a standalone agent profile viabnerd web --mode <role>) — yes. The AI can self-load any skill the "Available Skills" directory lists. - Team teammates (a role spawned by
bnerd team runor the TUI's team view) — no. A teammate's skills are exactly whatever its role'sskills:list force-loads at spawn time (see Agent Profiles → Role skills are force-enabled); it has no self-serveskilltool to reach for anything beyond that. This is a v1 choice, not a technical ceiling: roles are expected to declare what they need up front. bnerd mcp-server— no. The MCP server's tool registry is built independently of the rules/skills system entirely; there is no dynamic prompt injection orskilltool on that surface.
TUI: /skills and /skill¶
/skills lists every loaded rule and skill with a state marker:
* k8s-security [rule] Pod Security Standards and securityContext hardening (builtin)
+ flux [skill] Flux v2 API types, reconciliation loop, and image automation (builtin)
terraform [skill] Terraform provider usage and module patterns (builtin)
* = active | + = skill this mode can self-load | (blank) = other mode, or an inactive rule
/skill load <name> · /skill unload <name> — manual override, works on any entry
The AI can also load an available (+) skill itself mid-conversation via the skill tool.
*— active now (its full body is in the prompt this round).+— a skill that applies to the current mode and isn't active. This is exactly the set the model sees under "Available Skills" and can load itself with theskilltool.- blank — everything else: an entry scoped to a different mode via
applies_to, or an inactive rule. Theskilltool only loads entries of kindskill, so a rule you turned off with/skill unloadis not marked+even while itsapplies_tostill matches — the AI cannot bring it back on its own. Use/skill load <name>to re-enable it manually.
/skill load <name> and /skill unload <name> are the manual override — unlike the AI's own skill tool, they work on rules and skills alike, and were unchanged by this feature.
See also¶
- Permission Rules — how a skill-provided tool is evaluated against
--ai-permissionsrules. - Agent Profiles — role skills as a standalone
bnerd webchat profile. - Team Recipes — the
skills:role frontmatter field in the team-runtime context. - Available Tools — the
skilltool's entry in the shared tool catalog.