# A plugin's capabilities are LOADED, never copied **Status: designed, not built.** What exists today is the copying this replaces. ## The problem, as it is on disk right now `capabilities/` is a git repository. The intent is that anyone can fork it, keep their own tasks, skills and affordances in it, and pull core's on top. That only works if the directory contains exactly two things: what core ships, and what the owner wrote. It does not. Four publishers copy a plugin's files into it at install time — assets, affordances, notification catalogues and skills — so a fork accumulates files it did not author. On this machine today `skills/email-db/` and `extensions/` sit untracked in that repository, published there by plugins. Commit them and the fork carries another plugin's work; leave them and `git status` is permanently noisy. There is no third answer, because nothing on disk says which files came from where. Copying is also wrong in a second way that has nothing to do with git. **A copy goes stale.** `plugins:update` pulls new plugin code and the copied capabilities keep describing the old version until something republishes them. And a copy survives `plugins:disable`, so a disabled plugin's skill stays in the agent's index — the plugin is off and the agent still believes it is there. ## The design **A plugin keeps its capabilities in its own directory, and the platform reads them where they are.** ``` capabilities/ the owner's fork: core's, plus their own. Nothing else, ever. tasks/ skills/ affordances/ notifications/ plugins// the plugin's own repository, cloned on install tasks/ skills/ affordances/ notifications/ ``` Nothing is copied. Nothing needs a namespace to keep the two apart, because they are already apart — which is the whole reason this beats scoping the paths. A namespace makes provenance *readable*; separate directories make it *structural*. Four consequences, and each of them is a bug that stops existing: - **A fork stays clean.** `capabilities/` contains only what its owner is responsible for, so pulling from upstream merges without touching anything a plugin put there. - **Capabilities update with the plugin.** `plugins:update` is a `git pull` in the plugin's directory, and the capabilities are in that directory. There is nothing left to go stale. - **Disable actually disables.** The load list is built from plugins that are installed AND enabled, so a disabled plugin contributes nothing. Today its copies remain. - **Uninstall needs no cleanup step.** The directory goes and its capabilities go with it. No list of filenames to be careful with, and no way to delete the owner's own work by getting that list wrong. ## What has to change **The loaders take a list of roots.** `loadSkills(root)` and `loadAffordances(root)` already take one with a default, so this is a small change rather than a rewrite. Notifications the same. **The list is resolved per session, not at module load.** `affordanceBlock()` and `skillsBlock()` are called once per session in `sidecar/claude/claude-manager.ts` — deliberately, so a capability written an hour ago is offered on the next session. That is also why they can afford to ask the database which plugins are installed: once per session, not once per turn. **Tasks are the awkward one, because that store is read-write.** `api/tasks/task-files.ts` creates, edits and deletes as well as reads. Reading must span every source; writing must only ever touch the owner's directory. A plugin's task is read-only by nature and the UI has to know, or it will offer an Edit that silently writes into a directory the next `plugins:update` overwrites. A task's directory name is also stored data — `taskDirName` in `pipeline_jobs` — so lookup by name has to search every source rather than assuming one root. **The four publishers are deleted**, along with their install and uninstall wiring: `affordances/publish.ts`, `notifications/publish.ts`, `skills/publish.ts`, `api/tasks/publish.ts`. The last was written on 2026-08-23 and is superseded before it ever shipped. `app-store/assets` is a different case and stays. Those are images served over HTTP from a platform-managed path, not capabilities read from disk, and the copy is what makes them reachable. ## The migration One-off, and it is deletion rather than movement. The copies already in `capabilities/` have nothing to remove them once the publishers are gone — `skills/email-db/` and `extensions/` on this machine, and whatever the equivalent is per host. They are copies of files that still exist in each plugin's own directory, so deleting them loses nothing. Check each host rather than assuming this one is representative. ## What this does not decide **Where the owner's own capabilities live inside their fork.** Flat at the top of each type directory is what happens today and needs no change. Scoping them is a separate question and this design does not force it either way. **Whether a plugin's capabilities should be namespaced among themselves.** Two plugins can still ship a task with the same directory name, and the loader has to break that tie — first-come, as the items store has always done, or by qualifying with the plugin name at read time. Worth settling when a second plugin ships a task; not before.