Appearance
Session Kits
What a kit is, where kits are declared, and how the declaration is checked (#2221, #2225). (Back to reference index.)
A kit is the set of skills, commands and agents one activity needs — a review, a security pass, a release. Provisioning already groups items by stack and by lifecycle phase; kits add the axis a single afternoon actually has: what you are doing right now. Every trigger that selects a kit — an explicit flag, a flight plan's labels, a ritual, a machine role's default — reads the same file, so "a security session" loads the same items on every machine.
Per the Prime Directive, kit membership lives in exactly one place and is validated on every make check.
Where kits live
| What | Path |
|---|---|
| Declaration | registry/kits.json (BOB_SOURCE) |
| Schema | schemas/kits.schema.json |
| Runtime copy | ~/.claude/registry/kits.json — synced by deploy.sh with the rest of registry/ |
| Gate | scripts/checks/check-kits.js, run by make check-kits (part of make check) |
Why registry/ and not provisions/. provisions/ holds one manifest per project (and the _bob-home / _default manifests), and check-schemas validates every provisions/*.json as a project manifest. A kit file there would either be mis-validated or need a carve-out. registry/ is where the items a kit names already live, and deploy.sh syncs it to BOB_HOME, so the runtime overlay can read the kit file next to the items it links. Recorded as decision #2359.
File shape
json
{
"_meta": { "version": 1, "convention": "…" },
"kits": [
{
"name": "security",
"purpose": "Threat-model, audit and harden a surface: vulnerabilities, dependencies, secrets and HTTP headers.",
"skills": ["dependency-auditing", "secrets-management", "web-security-hardening", "drupal-security"],
"commands": ["threat-model"],
"agents": ["security-auditor"],
"runbooks": ["cloudflare-api-token-rotation", "github-pat-rotation", "bridge-secret-rotation", "tailscale-auth-key-rotation", "tellbob-token-lifecycle", "bob-attack-surface-review"]
}
]
}| Field | Rule |
|---|---|
name | lowercase-kebab, unique across the file |
purpose | one line — the activity the kit is for |
labels | optional — issue labels that select the kit when a flight plan's scope is derived; a label selects at most one kit across the file (see Labels) |
skills | directory names under registry/skills/ or skills/ |
commands | <name>.md basenames under registry/commands/ or commands/ |
agents | <name>.md basenames under registry/agents/ or agents/ |
runbooks | <name>.md basenames under registry/runbooks/ only — universal runbooks/ are referenced by absolute path and never linked per project, so a kit cannot name one (#2239) |
Each list is optional and duplicate-free; a kit must list at least one item. An item may belong to several kits (code-debugger is in both incident and bugfix).
The six kits
| Kit | Purpose |
|---|---|
review | Review a diff, branch or PR for correctness, simplicity, accessibility and search health. Carries docs-review-checklist, the versioned checklist the docs-review ritual walks (rituals.md → Checklists, #2625). |
vision-planning | Shape the vision and turn it into business cases, capabilities, requirements, solutions and risks. |
security | Threat-model, audit and harden a surface: vulnerabilities, dependencies, secrets and HTTP headers. Carries the credential-rotation runbooks (#2239) and bob-attack-surface-review, the versioned checklist bigbrain's security-review ritual walks (rituals.md → Checklists, #2241). |
release | Cut, version, promote and deploy a release, with its change record and QA sign-off. |
incident | Diagnose and recover a live failure across machines, then capture the retrospective and runbook. Carries the three drill runbooks — drill-restore, drill-lost-laptop, drill-farm-compromise — since it is the kit every drill ritual applies (rituals.md → Drills, #2240). |
bugfix | Reproduce a bug as a failing test, fix the root cause and keep the regression test. |
registry/kits.json is the membership list; this table only repeats each kit's purpose.
Applying a kit
A kit is loaded into a session as a provision overlay — the same lifecycle a flight plan's items use (#1133) — so the project's manifest is never edited (#2226):
bash
bob provision overlay apply --kit security # link the kit's items for this session
bob provision overlay status # what is active, and from which sources
bob provision overlay restore # remove the overlay links| Behaviour | Rule |
|---|---|
| What is linked | The kit's skills, commands, agents and runbooks (into .claude/runbooks/) minus those the project manifest already declares and those BOB_HOME already provides (global or universal). The overlay only owns links it alone created. |
| Where the kit is read | ~/.claude/registry/kits.json — the deployed copy, next to the items it links. |
| State | .claude/.provision-overlay.json records kit, the per-source item lists under sources, and their union under items. |
| With a flight plan | --kit composes with --plan <N> (or --file <overlay.json>), in one call or applied one after the other. The overlay is the de-duplicated union, and overlay status names both sources (plan #2311 + kit security). |
| Re-apply | Applying the same kit again is idempotent. A different kit (or plan) while one is active exits 2 and names overlay restore. |
| Unknown kit | Exits 2 with the list of declared kits; nothing is written. |
| Restore | Removes every overlay link (plan and kit together), deletes the state record and rebuilds the managed ignore block from the manifest. The project tree ends up byte-identical to before the apply, and a second restore is a no-op (exit 0). |
| From the shell | claude --kit <name> (or --kit=<name>) runs overlay apply --kit <name> on the current directory, then starts Claude Code without the flag. It sits alongside the wrapper's -a1–-a4 and -rdb flags in bin/bob-shell.zsh. A refused kit stops the launch with the overlay's exit code, and --kit with no name exits 2. |
| Crash safety | The state record is written atomically, before any link. An interrupted apply therefore leaves either no record or a whole one naming every link it may have made. bob provision status surfaces it, and overlay restore (or re-applying the same kit) recovers it. |
| Ritual run stamp | bob ritual run applies the ritual's kit with --ritual-run <owner/repo#N>, which records the run on the kit source and as ritual_run on the record (#2234). overlay status names it, for example kit review (ritual run paulirv/bigbrain#2372), plus the bob ritual close that restores it, so a run abandoned without a close is visible. bob ritual close restores only an overlay stamped with its own run, and never one that also carries a flight plan. Re-applying the same kit without a stamp keeps the stamp. The same kit stamped for a different run exits 2, as does --ritual-run without --kit or not in owner/repo#N form. |
Labels: a flight plan selects kits (#2227)
A kit's labels are the issue labels that pull the whole kit into a flight plan's overlay. plan-scope.js derive <N> (what bob provision overlay apply --plan <N> runs) checks every issue in the plan: when one carries a label a kit declares, that kit's items join the items the plan's other signals derived.
| Label | Kit |
|---|---|
security | security |
release | release |
incident | incident |
cap, business-case, use-case, vision | vision-planning |
bug | bugfix |
review declares no labels: it is only ever applied by name (--kit review, a ritual).
| Rule | Behaviour |
|---|---|
| What joins the overlay | The kit's registry items, required_with expanded, minus what the project manifest already declares. Universal items are left out — bob init already links them. |
| Provenance | derived_from.kits lists each selected kit with the labels and issue numbers that selected it. sources maps every overlay item (skills/tdd-flow) to what put it there: kit:<name> for each selecting kit, signal for the stack/label/area signals. |
| Determinism | Same plan, same issue labels, same kit file and registry → byte-identical output. |
| One label, one kit | make check-kits fails duplicate-label when two kits claim the same label. |
| Reviewing it | plan-scope.js diff <N> prints the usual + list, then an Added by block: one group per selecting kit, headed with the label and issues that selected it, then the signal-sourced additions. --json carries the same split as sources: {kits: {<kit>: ["type/name", …]}, signal: [...]}. An item both selected is listed under each. |
| A kit the machine forbids | A plan's labels can select a kit the machine's role forbids — a cap issue selects vision-planning, which farm workers forbid. overlay apply --plan does not refuse the plan: it leaves out every item only that kit contributed, names the kit and the dropped items, and keeps any item a signal or an allowed kit also selected. A plan doc without sources (written before #2227, or by hand) is applied as it is. |
The map is edited here, in registry/kits.json; plan-scope.js has no label→kit table of its own.
Context cost (#2229)
Every skill, command and agent a session can see loads its description into the context, and each one dilutes triggering for the rest. bob provision status --context measures what a session here carries, so a kit's saving is a number rather than a guess:
bash
bob provision status --context # table
bob provision status --context --json # bob-context-cost/1| Row | What it counts |
|---|---|
| global set | the machine's resolved global manifest (_bob-home*.json, the #414 chain) — the universal set every BoB session carries |
| project manifest (extra) | the project manifest's items the global set does not already provide |
| loaded today | global + project: what a session in this project carries now |
kit, + on manifest | what applying the kit adds on top of the manifest — the overlay's rule, kit items minus global and manifest |
kit, global + kit alone | what a session carrying only the global set and the kit would load, and its reduction against loaded today |
The estimate. An item's loaded text is <name>: <description>, the description read from its frontmatter with runs of whitespace collapsed to one space. Its cost is ceil(characters / 4) — the common characters-per-token rule of thumb. It approximates the real tokenizer and never calls it, so the same files always give the same numbers. A missing description costs its name alone; a declared item with no file is listed under missing and costs nothing. Runbooks are not counted: they are read by path when a step needs them, never loaded into the context. Items and kits are read from BOB_SOURCE (registry/<type>/ first, then the universal <type>/ directory); scripts/orchestrator/context-cost.js owns the method.
bob-context-cost/1. method (the rule, restated), global and project (each {manifest, types: {skills|commands|agents: {count, tokens}}, total}; project.costliest lists its ten most expensive items), loaded ({count, tokens}), kits[] ({kit, adds: {types, total}, with_manifest, alone, reduction_vs_manifest: {tokens, percent}}) and missing[]. Exit 0, or 2 when a manifest cannot be read. The global set differs per machine role, so run it on the machine whose sessions you are measuring.
Baseline: bigbrain (2026-09-30)
Measured on farm-01 (role farm-worker, global set _bob-home.farm-worker.json) at commit 47d8e704, with bob provision status --context:
| Set | Skills | Commands | Agents | Tokens |
|---|---|---|---|---|
| global set | 9 | 17 | 3 | 1,623 |
| project manifest (extra) | 16 | 18 | 11 | 5,313 |
| loaded today | 25 | 35 | 14 | 6,936 |
| Kit | Adds on top of the manifest | Global + kit alone | Reduction vs loaded today |
|---|---|---|---|
| review | +964 | 3,795 | 3,141 (45.3%) |
| vision-planning | +508 | 2,642 | 4,294 (61.9%) |
| security | +605 | 2,629 | 4,307 (62.1%) |
| release | +107 | 2,038 | 4,898 (70.6%) |
| incident | +987 | 3,259 | 3,677 (53.0%) |
| bugfix | +0 | 2,858 | 4,078 (58.8%) |
The bugfix-kit reduction is 4,078 tokens, 58.8%. A bugfix session that carries the global set and the bugfix kit instead of bigbrain's full manifest loads 2,858 description tokens instead of 6,936. Applied on top of the manifest the kit adds nothing, because bigbrain already declares every bugfix item — its saving comes from loading it instead of the manifest. The four agents with example-laden descriptions (lead-dev-architect, unit-test-generator, code-debugger, code-reviewer) cost 1,669 tokens between them, almost a third of the project's extra items. The farm-worker global manifest also declares two commands no longer in the tree (pr, research); they are listed under missing and cost nothing.
bob tour uses the same estimate: every skill, command and agent on the tour carries its tokens, and the never-invoked grades list the costliest first, so the items that load the most for no use lead the list.
Machine role defaults and exclusions
A machine's role sets a default kit and the kits it forbids (#2228). Role is a default and a constraint, not the primary key: a farm worker runs unattended, so it must never load a kit that needs a human.
The kits are declared in a global manifest, next to the item lists:
json
{
"_meta": { "project": "_bob-home", "path": null, "stack": ["bob-home"] },
"skills": ["…"],
"kits": {
"default": "bugfix",
"forbid": ["vision-planning", "security"],
"why": "farm-worker machines run unattended …"
}
}| Field | Rule |
|---|---|
default | A declared kit name, or null for no default. |
forbid | Declared kit names that overlay apply --kit refuses on this machine. |
why | Optional one line explaining the choice. |
Resolution. A machine reads its kits from the same file its global item lists come from. That is the #414 chain: _bob-home.<machine-id>.json, then _bob-home.<role>.json, then _bob-home.json. The most specific file that exists wins as a whole. A machine-id file with no kits block therefore means "no default, nothing forbidden"; it is not merged with the role file. A role file must also carry the full global item lists, because it replaces the base manifest for that machine.
| Machine role | File | Default | Forbids |
|---|---|---|---|
farm-worker (farm-01) | provisions/_bob-home.farm-worker.json | bugfix | vision-planning (a human shapes intent), security (its secrets-management skill covers credential rotation, which needs a human at the provider) |
dev-laptop | provisions/_bob-home.json (base) | none | nothing |
utility-host (fubaz) | provisions/_bob-home.json (base) — no role file, decided on #2543 | none | nothing |
Refusal. bob provision overlay apply --kit <name> with a forbidden kit exits 3 and writes nothing:
overlay: kit 'vision-planning' is forbidden on this machine (farm-01, role farm-worker) by provisions/_bob-home.farm-worker.json — there is no override; apply it on a machine whose global manifest allows itThere is no --force-kit. To allow the kit, change the global manifest, which goes through review like any other manifest edit. An unknown kit is still exit 2, checked before the role rule. claude --kit stops the launch with the same exit code.
Default kit. When a session names no kit, the machine's default applies through the same overlay lifecycle. There is no new warp-drive phase: the existing prerequisites overlay step runs
bash
bob provision overlay apply --default-kit [--plan <N>]| Situation | Result |
|---|---|
The machine declares kits.default | That kit is applied (subject to forbid), and the default is named with the manifest it came from. With --plan it composes into one plan + kit union. |
| No default declared | Clean no-op, exit 0. Nothing is written unless --plan was also given. |
A kit is already applied (claude --kit <name>, or an earlier segment of the run) | That kit stays; the default is not applied over it. |
--default-kit together with --kit <name> | Exit 2 — they are exclusive. |
session_ending restores the overlay exactly as it does for a plan.
Reporting. Every machine's resolved kits are visible without SSH:
| Surface | What it shows |
|---|---|
bob doctor | One informational line, never graded: INFO kits: role farm-worker — default bugfix, forbid vision-planning, security (provisions/_bob-home.farm-worker.json) (#2228). The bob-host-drift/1 document carries it as kits (bob-role-kits/1). |
audit.js snapshot | summary.kits: {declared, default, forbid, manifest, error} for the publishing machine. |
audit.js view | A Session kits table: MACHINE, ROLE, DEFAULT, FORBID, MANIFEST, with one row per machine whose snapshot carries the key. |
Asking what a machine resolves.
bash
node scripts/fleet/role-kits.js # this machine, one line
node scripts/fleet/role-kits.js --json --machine farm-01 # bob-role-kits/1bob-role-kits/1 carries machine, role, manifest (the resolved provisions/ file), declared, default, forbid and error. BOB_MACHINE_ID=<id> names the machine instead of matching the hostname. The provisioning engine honours it too, so a test can play any fleet machine.
The gate
bash
make check-kits # human output
node scripts/checks/check-kits.js --json # bob-kits-check/1
node scripts/checks/check-kits.js --root DIR # grade another checkout| Exit | Meaning |
|---|---|
| 0 | every kit is valid |
| 1 | at least one problem (listed) |
| 2 | the kit file or schema is missing or unparseable, or a usage error |
| Problem kind | Raised when |
|---|---|
schema | the file does not validate against schemas/kits.schema.json |
duplicate-kit | two kits share a name |
unknown-item | a kit names an item that exists in neither registry/<type>/ nor <type>/ (a runbook: not in registry/runbooks/) |
duplicate-label | two kits declare the same label, so a flight plan's kit selection would be ambiguous (#2227) |
empty-kit | a kit lists no skills, commands, agents or runbooks |
unknown-role-kit | a global manifest's kits.default or kits.forbid names a kit registry/kits.json does not declare (#2228) |
forbidden-default | a global manifest defaults to a kit it also forbids |
kits-outside-global-manifest | a project manifest declares kits — only _bob-home*.json may |
The bob-kits-check/1 document carries verdict (ok / fail / error), kits (the declared names), problems (each with kind, kit, and for unknown-item the type and item) and, on error, an error string.
Coverage report
Every registry item in the review, security, process and governance categories (the category: frontmatter key) should belong to at least one kit. The coverage report lists the ones that don't. It only reports, so it exits 0 whatever it finds, as long as the kit file can be read.
bash
make check-kits ARGS=--coverage # human list
node scripts/checks/check-kits.js --coverage --json # bob-kits-coverage/1kit coverage (review, security, process, governance): 33/44 registry item(s) in a kit, 11 uncovered — report only
[uncovered] skill agent-builder (process)
…| Rule | Why |
|---|---|
| Registry items only | Universal items load in every session, so no kit needs to list them. |
.disabled/ items are skipped | They are not provisionable. |
| Other categories are skipped | Design, docs, ops and similar items are grouped by stack and phase today; kits do not have to cover them. |
An uncovered item raises a question, not a defect: which activity is it for? Answer it by adding the item to a kit, or leave it out on purpose (for example skill-creator, which is for authoring BoB itself, not a session activity). The bob-kits-coverage/1 document carries verdict, categories, considered, covered and uncovered (each entry with type, name and category). A missing or unparseable kit file gives verdict: "error" and exit 2.
Tests: make test-kits (tests/test-kits.js, fixtures in tests/fixtures/kits/ — a valid kit file, an unknown item, a duplicate kit name).
Related
- Verification System — every
make checktarget. - Orchestrator — stack- and type-based recommendation, the axis kits complement.