Source (mirrored)wicomponents

skills-layer — the per-box command surface; only the plugin's own `skills/` reach every box

Mirrored from the fleet knowledge base: wi/components/skills-layer.md

Updated

Mirrored verbatim from utopia-models/knowledge wi/components/skills-layer.md by the allowlisted publish pipeline. Edit it there, not here.

component: skills-layer
serves: all
runs-on: everywhere
adrs: []
owns:
  - brain-agent:skills
  - brain-agent-dust:skills
  - net/.claude/skills

skills-layer — the per-box command surface; only the plugin's own skills/ reach every box

WHAT IT IS

The /-invokable commands an agent has: a directory per skill under ~/.claude/skills/, each holding a SKILL.md whose frontmatter description is what the model matches on for auto-invocation. It is where the fleet's process doors live — /plan-start, /execution-start, /feedback-loop — so removing it does not stop any master executing, but it removes every entry point the Architect uses to start or continue a plan.

🔴 Its defining property is negative: a skill reaches EXACTLY ONE BOX. "One push, every master pulls" is true of the plugin's hooks and scripts — and, since fleet-doctrine 0.93.0 (knowledge#9837, PLAN-405 P2), of the skills under the plugin's own skills/ directory. /plan-start was the first to ship IN the plugin (skills/plan-start/, disable-model-invocation: true), and its brain-agent copy is deleted, because a same-named ~/.claude/skills/ skill shadows a plugin skill on the bare /name (measured 2026-10-23). On 2026-10-31 the plugin's skills/ holds seven: net-status, plan-close, plan-continue, plan-park, plan-start, unslop, upwork. Every other skill is still per-box.

🔴 A PROJECT's skills never live in ~/.claude/skills/ — they live in <project>/.claude/skills/ in this repo (knowledge#9928). A user-level skill is listed, name and description, in EVERY session on its box, so five cleaning skills rode every wi session on the Architect's box. Claude Code attaches a NESTED .claude/skills/ — one below the session's cwd — the first time the model Reads a file under its parent directory, and lists it from then on (measured on 2.1.302 and 2.1.307: absent from the startup listing, a dynamic_skill attachment right after a Read of 8f444ccd79:cleaning/context.md). 🔴 It does NOT attach when the cwd's own git repo gitignores the path — the Architect's ~ is a repo with .gitignore: *, so there the Read attaches nothing (respectGitignore:false does not change it; --add-dir does). project-session.sh load prints a SKILLS section that asks for the Read AND lists each skill's SKILL.md — running a skill is reading that file and following it, so a project session can use them on every box, and a wi session never sees them. Outside /plan-start, /add-dir ~/dev/utopia-models/knowledge/<project> loads them as / commands. Probe: ls */.claude/skills/. The grader is _infra/scripts/project-census.py --transcript <t> --expect <project>, which fails on another project's skill row.

An account-level skill (anthropic-skills:*, from claude.ai) has no directory to nest, so it cannot be scoped per session. skillOverrides in the box's settings.json ("user-invocable-only") takes it out of the model's listing in every session while /anthropic-skills:<name> still runs when typed; a permission deny on Skill(...) blocks the call but leaves the row listed (both measured 2026-10-23). Plugin skills are not affected by skillOverrides.

HOW IT WORKS TODAY

flowchart TD
    subgraph PLUGIN["fleet-doctrine plugin — utopia-models/fleet-plugins"]
      HK["hooks/hooks.json + scripts/<br/>+ skills/ (plan-start and the other plugin skills)"] -->|"one push, every box pulls,<br/>SHA-pinned + version-gated"| ALL["all six boxes"]
    end
    subgraph SKILLS["every other skill — NOT in the plugin"]
      BR[("that box's OWN brain repo<br/>e.g. utopia-models/brain-agent")] -->|"brain-pull, git merge --ff-only"| ONE["exactly ONE box"]
      ONE --> SD["~/.claude/skills/&lt;name&gt;/SKILL.md"]
    end
    subgraph DOCTRINE["doctrine — neither"]
      KB[("knowledge clone on the box")] -->|"~/.claude/rules/bundle.md — ONE symlink,<br/>at that box class's compiled bundle"| ALL
    end
    SD --> INV["/name typed, or auto-invoked<br/>from the description"]
    INV --> FR["_process/steps/&lt;n&gt;-&lt;step&gt;.md<br/>the process the door opens"]

Three delivery layers, three reaches, and conflating any two is the recurring error. The plugin ships mechanism to every box. Doctrine is symlinked live out of each box's knowledge clone, so a rule change lands without a version bump — but the symlink itself is tracked in that box's BRAIN repo, which is the third layer wearing the second layer's clothes and is where #7362 came from: the link was repointed by hand on five boxes and committed on none, so every checkout restored nine symlinks into a directory the rebuilt tree no longer has, and sessionstart-pull-brain.sh skipped its auto-pull on the uncommitted deletions. A repoint is not delivered until it is committed. A skill ships in one brain repo and reaches one box, at that box's next brain-pull — which is why /upgrade-notes sat on one box and off the Architect's for all of PLAN-203.

A skill belongs to the agent that owns the folder. ~/.claude/ on a box is that agent's; a project agent's skills, commands and rules never live there. A skill touching more than one file loads context and shows a status table before any write, and AskUserQuestion is never step 1. Side-effect skills set disable-model-invocation: true. The house verbs are audit (read-only), self-enhance (autonomous), update/fix (scoped).

A door is thin; the process is a frame. /execution-start resolves which thing you mean and hands off to its frame; the process lives in the frame, never in the door. /plan-start asks mode, project and recipe, then project-session.sh load prints that ONE project's context, the recipe and _process/steps/plan-start-frame.md, and writes ~/.claude/state/plan-issue/<session_id> — the state the [session] start-line hook re-reads on startup, resume, /clear and compaction. A master never types either.

🔴 A fact a skill would ASSERT becomes a shell command that READS it. In SKILL.md, a ! immediately followed by a backticked command runs in a shell before the content is sent and its output replaces the placeholder — verified including with no allowed-tools and with disable-model-invocation: true. A sentence stating a count, a path or a version is a sentence that rots; the live read cannot.

🔴 Master boxes have no user-level skills, and that is the current design, not a gap (a project's <project>/.claude/skills/ attaches to a master's session only if it reads a file under that project). A master reads its spec and its checklist; anything it needs from a process arrives written into the issue body at filing time (see subprocess-catalogue).

Why this doc's owns: names two repos and no path. The partition has to name the real things, and every real thing here is in a different repo — brain-agent:skills and brain-agent-dust:skills. Written with a / they would be graded as repo-relative paths and fail R9 for being absent from a repo they were never in; written as bare names they are correctly declined, the same way a unit or a port is. serves: all is the enum's answer to "which portfolio", not a claim that a skill reaches every box — it does not, and that is this component's whole first paragraph.

BOUND VALUES

Nothing about this component is in this repo, so there is no number here to bind. What CAN be bound is the in-git pointers at it: if a skill is renamed and the KB is not updated, these go red.

  • ✅ /plan-continue — still called by _process/steps/
  • 🔴 ~/.claude/skills/execution-start/SKILL.md — NOT CALLED by _process/steps/: present on disk, wired to nothing (#4742)
  • 🔴 ~/.claude/meta/workflow/start-planning.md — NOT CALLED by _process/steps/: present on disk, wired to nothing (#4742)
  • ✅ plan-start — still called by wi/subprocesses/dispatch/process.md
  • ✅ execution-start — still called by wi/subprocesses/dispatch/process.md
  • ✅ execution-done — still called by wi/subprocesses/dispatch/process.md
  • ✅ plan-stop — still called by wi/subprocesses/dispatch/process.md
  • ✅ /feedback-loop — still called by _process/feedback-corpus.md
  • ✅ task-observer — still called by _process/feedback-corpus.md
  • ✅ _rules/a-skill-belongs-to-the-agent-that-owns-it.md

DETERMINE CURRENT STATE

Every fact about this component is off-git. There is nothing here to bind, so there is nothing here stated as an answer.

questioncommand
what does THIS box havels ~/.claude/skills/
what does a given box's brain repo carrygh api repos/utopia-models/<brain-repo>/contents/skills --jq '.[]|select(.type=="dir").name'
which brain repos carry any skills at allrun the above for brain-agent · brain-agent-dust · brain-agent-sf · brain-win · brain-nova · brain-cloud · brain-poppi — a 404 means the repo has no skills/
what one skill actually doesgh api repos/utopia-models/<brain-repo>/contents/skills/<name>/SKILL.md --jq .content | base64 -d
which skills does the plugin carrygh api repos/utopia-models/fleet-plugins/contents/plugins/fleet-doctrine/skills --jq '.[].name'
which frames a door can opengh api repos/utopia-models/brain-agent/contents/meta/workflow --jq '.[].name'
is a box on main's plugin payloadbash _infra/scripts/plugin-deploy-check.sh

🔴 Grade the POPULATION, never one box. "The fleet has skill X" is only ever true of boxes individually, and four of them are not readable from any one master's account. A verification that read one box and called the layer sound has already shipped once.

KNOWN BROKEN

  • 🔴 Only two brain repos in the fleet hold a skills/ directory at all. Measured 2026-10-31:

    for r in brain-agent brain-agent-dust brain-win brain-nova brain-cloud brain-agent-sf brain-poppi; do
      printf '%s: ' "$r"
      gh api "repos/utopia-models/$r/contents/skills" --jq 'length' 2>/dev/null | grep -E '^[0-9]+$' || echo 'no skills/ (404)'
    done
    brain-agent: 11
    brain-agent-dust: 39
    brain-win: no skills/ (404)
    brain-nova: no skills/ (404)
    brain-cloud: no skills/ (404)
    brain-agent-sf: no skills/ (404)
    brain-poppi: no skills/ (404)

    (grep -E '^[0-9]+$' is not decoration: gh api writes its 404 body to stdout, so a bare || echo prints the JSON error and the fallback on one line.)

    (brain-agent counts 10 directories plus SKILL-TEMPLATE.md; brain-agent-dust's 39 are all directories.) And on nova, ls -la ~/.claude/skills → "No such file or directory". So the brain-repo layer's real population is two boxes, and the 39 on dust's are reachable by nobody else. Apart from the plugin's own skills/, nothing ships a skill to the fleet.

  • ⚠ The two boxes' skill sets have diverged with nothing measuring the gap. 10 against 39 named skills, no shared manifest, no linter, no index. There is no command that answers "which skills exist in the fleet" — the probe table above is seven separate reads because that single command has never existed.

On this page