Research 029: the agent configured through its module, as a plugin the mesh serves
Skills, subagents and hooks have no machine-wide place but a plugin, and hand-copied homes have drifted and gone stale on every machine. Records the evidence, what the vendor allows, and the options a decision must settle.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
---
|
||||
status: active
|
||||
initiated: 2026-10-04
|
||||
touches:
|
||||
- 03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md
|
||||
- 03-DESIGN/00-as-is/15-the-agent-and-its-licences.md
|
||||
- 02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md
|
||||
- 02-DECISIONS/0201-a-module-keeps-its-current-state-in-key-value-buckets-it-declares-and-reaches-through-the-runtime.md
|
||||
- 02-DECISIONS/0210-a-tools-configuration-is-its-seat-holders-and-every-other-module-extends-it-through-the-seat.md
|
||||
- 02-DECISIONS/0213-the-operator-sets-the-agents-managed-settings-through-the-agent-module.md
|
||||
became: []
|
||||
---
|
||||
|
||||
# 029 — The agent configured through its module
|
||||
|
||||
## What
|
||||
|
||||
Everything about the operator's coding agent that can be configured is configured through the agent
|
||||
module's tools, and reaches the machines as **one plugin the mesh serves**:
|
||||
|
||||
- subagents, skills, slash commands, hooks, output styles and tool servers;
|
||||
- the agent's settings and its instructions.
|
||||
|
||||
Each item is registered once, from any machine, and goes to one machine, several, or all of them,
|
||||
including a machine that joins later. The mechanism is the one the module already uses for tool
|
||||
servers: the registration is kept in the module's state on the bus, and each machine's instance writes
|
||||
what applies to it. The operator chose the plugin route on the day this effort opened.
|
||||
|
||||
## Why
|
||||
|
||||
The vendor gives the agent a machine-wide directory for its settings, its tool servers and one
|
||||
instruction file, and **nothing machine-wide for skills, subagents, commands or hooks**. Those exist
|
||||
only in a home or a project. So today they are copied into each home by hand, and they drift and go
|
||||
stale. [01](01-what-is-configured-today.md) measures that on four machines.
|
||||
|
||||
A plugin is the vendor's own unit for carrying all of those at once. A machine-wide setting can name
|
||||
a marketplace and enable a plugin from it. If the module serves the plugin and its own managed settings
|
||||
enable it, the mesh gets a machine-wide place for everything the vendor left home-only, and the home
|
||||
stays the person's ([ADR 0182](../../02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md)).
|
||||
|
||||
## What it touches
|
||||
|
||||
- **The agent module's design** ([to-be 36](../../03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md)):
|
||||
its managed directory, its state, and its tools.
|
||||
- **Module state** ([ADR 0201](../../02-DECISIONS/0201-a-module-keeps-its-current-state-in-key-value-buckets-it-declares-and-reaches-through-the-runtime.md)):
|
||||
where registrations are kept, and which file content fits in a bucket.
|
||||
- **Contributions** ([ADR 0210](../../02-DECISIONS/0210-a-tools-configuration-is-its-seat-holders-and-every-other-module-extends-it-through-the-seat.md)):
|
||||
a module other than the agent's, say the forge's, wanting the agent to have a skill for it. That is a
|
||||
contribution to the agent's seat, not a file it writes.
|
||||
- **The managed settings key the operator sets** ([ADR 0213](../../02-DECISIONS/0213-the-operator-sets-the-agents-managed-settings-through-the-agent-module.md)),
|
||||
which a settings tool would write rather than a hand-composed settings layer.
|
||||
|
||||
## Documents
|
||||
|
||||
- [01 — What is configured today](01-what-is-configured-today.md): the evidence.
|
||||
- [02 — What the vendor allows](02-what-the-vendor-allows.md): plugins, marketplaces and managed
|
||||
settings, as documented, with sources.
|
||||
- [03 — Options](03-options.md): where each kind of item goes, how it is registered and stored, and
|
||||
the questions a decision has to answer.
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
# 01 — What is configured today
|
||||
|
||||
Measured on 2026-10-04 on four machines that run the agent module: two workstations, the control node
|
||||
and a home server. The figures count what sits in each operator account's agent directory in its home,
|
||||
outside the module's managed directory.
|
||||
|
||||
## What sits in the homes
|
||||
|
||||
| what | workstation A | workstation B | control node | home server |
|
||||
|---|---|---|---|---|
|
||||
| rule files (`rules/`) | 4 | 2 | 2 | 1 |
|
||||
| skills of the person's own (`skills/`, beside the vendor's synced ones) | 6 | 2 | 2 | 2 |
|
||||
| subagents (`agents/`) | 0 | 1 | 0 | 0 |
|
||||
| slash commands (`commands/`) | 0 | 0 | 0 | 0 |
|
||||
| plugin marketplaces known | 1 | 2 | 1 | 1 |
|
||||
|
||||
## What that shows
|
||||
|
||||
- **Six files the design says the operator removes are still on every machine.** To-be 36 §1 lists the
|
||||
predecessor's rule files and skills and leaves their removal to the operator, "once, on each
|
||||
workstation". On all four machines, the two predecessor skills are present, byte-identical to each
|
||||
other:
|
||||
- one that switches licences through tools that no longer exist;
|
||||
- one that names the predecessor's forge.
|
||||
|
||||
So a session can still load a skill whose every instruction fails.
|
||||
- **One instruction, three versions.** The predecessor's node-identity rule file is on three machines,
|
||||
with three different contents. It was written per machine and then left alone.
|
||||
- **Two instruction sets that contradict each other, loaded together.** On a workstation, one session
|
||||
reads two sets of instructions:
|
||||
- the module's managed instruction file says to search the mesh's records first;
|
||||
- the predecessor's rule files in the home say to search the predecessor's knowledge base first,
|
||||
through tools that are no longer served.
|
||||
|
||||
Both are loaded, and neither says the other is stale.
|
||||
- **A subagent exists on one machine only.** A reviewer for module definitions was written on one
|
||||
workstation. The other three machines cannot use it, and nothing says it exists.
|
||||
- **Settings are per home, and so per machine.** The agent's auto-mode environment, the rules that
|
||||
decide which actions the agent may take unasked, is written in one home's settings file. It
|
||||
describes another organisation's cloud, and it answers for this mesh's forge only through a list of
|
||||
trusted domains. When the agent refused a merge the operator had approved, the only lawful fix was
|
||||
a managed key ([ADR 0213](../../02-DECISIONS/0213-the-operator-sets-the-agents-managed-settings-through-the-agent-module.md)).
|
||||
The agent could not change its own settings, and nothing else in the mesh could either.
|
||||
|
||||
## What already works the way this effort wants
|
||||
|
||||
Tool servers. A server registered through the module's register tool is kept in the module's state
|
||||
on the bus, keyed `all.<server>` or `<node>.<server>`. Every instance watches that state and writes what
|
||||
applies to it into the managed tool-server file, and a machine that joins later takes it at its first
|
||||
start. Today one server is registered there, for one workstation. That is the shape this effort extends
|
||||
to everything else.
|
||||
@@ -0,0 +1,106 @@
|
||||
# 02 — What the vendor allows
|
||||
|
||||
Read from the vendor's documentation on 2026-10-04; the agent installed on the machines measured in
|
||||
[01](01-what-is-configured-today.md) was a 2.1 release. Each fact names the page it came from. Where the
|
||||
documentation is silent, this says so. A fact that a design rests on is to be confirmed on one machine
|
||||
before it is built on (see [03](03-options.md), *What to confirm first*).
|
||||
|
||||
## What a plugin can carry
|
||||
|
||||
A plugin is a directory with a manifest in `.claude-plugin/plugin.json` and, beside it, any of:
|
||||
|
||||
- skills, slash commands and subagents;
|
||||
- hooks;
|
||||
- tool servers (`.mcp.json`) and language servers;
|
||||
- output styles, workflows, themes and monitors;
|
||||
- a `bin/` directory;
|
||||
- a `settings.json`.
|
||||
|
||||
Its components are namespaced by the plugin's name, so a subagent `reviewer` in a plugin `mesh` is
|
||||
`mesh:reviewer`, and it never collides with a person's own of the same name.
|
||||
— *plugins/manifest-reference, plugins/loading (name conflicts)*
|
||||
|
||||
**What a plugin cannot carry:**
|
||||
|
||||
- **Settings.** Only two keys of a plugin's `settings.json` take effect: the default agent and the
|
||||
subagent status line. The rest are dropped. — *plugins/manifest-reference, settings*
|
||||
- **Permission rules.** Not documented as a plugin capability.
|
||||
- **Instructions.** A `CLAUDE.md` at a plugin's root is not loaded, and the validator warns about it.
|
||||
Instructions reach a session through skills only. — *plugins/manifest-reference, standard layout*
|
||||
|
||||
## Marketplaces, and a marketplace on the machine's own disk
|
||||
|
||||
A marketplace is a `marketplace.json` listing plugins and where each comes from. Its sources include:
|
||||
|
||||
- a relative path inside the marketplace;
|
||||
- a forge repository, a git URL or a subdirectory of one;
|
||||
- a package from a registry;
|
||||
- an archive over HTTPS;
|
||||
- the output of a command.
|
||||
|
||||
**A marketplace can be a directory on the machine.** Its plugins with relative paths are **loaded in
|
||||
place**, not copied into the cache. An edit takes effect at the next session start, or at
|
||||
`/reload-plugins` in a running session, and the plugin's version need not change.
|
||||
— *plugins/marketplace-reference (marketplace sources), plugins/loading (in-place and copied plugins)*
|
||||
|
||||
A plugin from any other source is copied into a cache in the home, under
|
||||
`plugins/cache/<marketplace>/<plugin>/<version>/`. — *plugins/loading*
|
||||
|
||||
## What managed settings do with plugins
|
||||
|
||||
These keys work in the machine-wide managed settings file — *plugins/org*:
|
||||
|
||||
| key | what it does |
|
||||
|---|---|
|
||||
| `extraKnownMarketplaces` | registers a marketplace on every session of the machine |
|
||||
| `enabledPlugins` | `true` installs and enables a plugin; `false` blocks and hides it at every scope. The managed value outranks every other scope |
|
||||
| `strictKnownMarketplaces`, `blockedMarketplaces` | allow-list or block-list of marketplace sources |
|
||||
| `strictPluginOnlyCustomization` | refuses skills, subagents, hooks and tool servers that come from neither a plugin nor managed settings |
|
||||
| `allowManagedHooksOnly` | runs only the hooks from managed sources |
|
||||
| `disableSideloadFlags` | blocks loading a plugin from the command line |
|
||||
| `syncClaudeAiPlugins` | stops plugins synced from the vendor's web account |
|
||||
|
||||
**Installed without anyone being asked.** Once the settings reach a machine, the marketplace is
|
||||
registered and the plugins installed at the next session start. A non-interactive run installs them in
|
||||
the background. Managed plugins do not wait for the workspace trust prompt. — *plugins/org*
|
||||
|
||||
## What the managed settings file honours besides
|
||||
|
||||
`permissions` (with its default mode and the switch that disables bypassing it), `autoMode`, `hooks`,
|
||||
`env`, `model`, `statusLine`, `outputStyle`, `apiKeyHelper`, and the managed-only switches for permission
|
||||
rules, hooks and tool servers. — *managed-settings*
|
||||
|
||||
That `autoMode` is honoured from the managed file is documented. That it changes what the agent
|
||||
refuses on these machines is still to be seen ([ADR 0213](../../02-DECISIONS/0213-the-operator-sets-the-agents-managed-settings-through-the-agent-module.md)
|
||||
left that open).
|
||||
|
||||
## Tool servers: the exclusive file wins over a plugin's
|
||||
|
||||
When the managed tool-server file is present, as the module writes it, it is **exclusive**: only its
|
||||
servers load. The vendor's web connectors load too when a managed key allows them. **A plugin's
|
||||
`.mcp.json` servers are blocked.** — *managed-mcp (exclusive control)*
|
||||
|
||||
So a tool server registered through the module stays in the managed tool-server file. Putting it in
|
||||
the plugin would silently stop it loading.
|
||||
|
||||
## Variables inside a plugin
|
||||
|
||||
- `${CLAUDE_PLUGIN_ROOT}`: the plugin's directory.
|
||||
- `${CLAUDE_PLUGIN_DATA}`: a directory that survives updates.
|
||||
- `${CLAUDE_PROJECT_DIR}`: the project's root.
|
||||
|
||||
These resolve in hook commands, tool and language server configuration, and the content of skills,
|
||||
subagents and commands. A plugin's declared options (`userConfig`) can be marked sensitive; the agent
|
||||
asks the person for them and stores them itself. — *plugins/manifest-reference (environment variables)*
|
||||
|
||||
## Reload
|
||||
|
||||
A running session does not see a changed plugin until `/reload-plugins` or a new session.
|
||||
`/reload-plugins` reloads skills, subagents, hooks and servers. It does not restart monitors.
|
||||
— *plugins/loading*
|
||||
|
||||
## Not documented
|
||||
|
||||
- a machine-wide directory for bare skills, subagents or commands. Only a plugin enabled by managed
|
||||
settings puts them machine-wide;
|
||||
- permission rules or instructions carried by a plugin.
|
||||
@@ -0,0 +1,122 @@
|
||||
# 03 — Options
|
||||
|
||||
The route is chosen: a plugin the mesh serves. What is left open is where each kind of item goes, how it
|
||||
is registered and kept, and what the module does about what it finds in the homes.
|
||||
|
||||
## Where each kind of item goes
|
||||
|
||||
[02](02-what-the-vendor-allows.md) puts a hard limit on the plugin: it carries skills, subagents, commands,
|
||||
hooks, output styles and language servers, but no settings, no permission rules, no instructions, and
|
||||
no tool server the exclusive managed file does not list. So there are four places, not one:
|
||||
|
||||
| kind | goes to | why there |
|
||||
|---|---|---|
|
||||
| skills, subagents, slash commands, output styles | **the mesh's plugin** | the only machine-wide place the vendor has for them |
|
||||
| hooks | **the mesh's plugin**, with the scripts beside them | a hook's script can live in the plugin and be named through `${CLAUDE_PLUGIN_ROOT}`. A hook in the managed settings would need its script placed somewhere else |
|
||||
| tool servers | **the managed tool-server file**, as today | the exclusive file blocks a plugin's servers |
|
||||
| settings and permission rules | **the managed settings file**, beside the mesh's keys ([ADR 0213](../../02-DECISIONS/0213-the-operator-sets-the-agents-managed-settings-through-the-agent-module.md)) | a plugin's settings are dropped |
|
||||
| instructions | **the managed instruction file**, in sections | a plugin's instruction file is not loaded |
|
||||
|
||||
The plugin is reached through two managed keys the module already owns the file for:
|
||||
`extraKnownMarketplaces`, naming a marketplace directory the module writes, and `enabledPlugins`, set to
|
||||
`true` for the mesh's plugin. Neither is the operator's to set. Like the attribution key, they are the
|
||||
mesh's keys and outrank whatever the operator sets.
|
||||
|
||||
### Option A — one plugin
|
||||
|
||||
Everything the mesh serves is in one plugin, `mesh`, so every invocation reads `mesh:<name>`. That is
|
||||
simple, and the name says where an item came from.
|
||||
|
||||
### Option B — a plugin per source
|
||||
|
||||
One plugin for what the operator registers, and one for what other modules contribute (below). An item
|
||||
then says in its name whether a person or a module definition put it there. But the operator has two
|
||||
prefixes to remember, and an item has two owners to ask about.
|
||||
|
||||
*Leaning:* A. Where an item came from belongs in the module's list tool, not in the item's name.
|
||||
|
||||
## Who registers an item
|
||||
|
||||
- **The operator, through the module's tools**, from any machine, for one, several or all of them. The
|
||||
pattern is the tool-server register tool's, extended to every kind:
|
||||
- `claude_code_<kind>_list`, `_register`, `_unregister` for skills, subagents, commands, hooks, output
|
||||
styles and instruction sections;
|
||||
- `claude_code_settings_get` / `_set` and `claude_code_permission_allow` / `_deny` / `_ask` /
|
||||
`_remove` for the managed settings.
|
||||
- **Another module, through the agent's seat** ([ADR 0210](../../02-DECISIONS/0210-a-tools-configuration-is-its-seat-holders-and-every-other-module-extends-it-through-the-seat.md)).
|
||||
The forge's module wanting the agent to know how pull requests are made here contributes a skill. It
|
||||
declares the contribution in its definition, and the controller renders it to the agent's holder on
|
||||
each machine where both run. That depends on the agent module holding a seat; today it holds none.
|
||||
|
||||
The two meet in the one plugin. A contribution and a registration with the same name are refused at
|
||||
registration, and the list tool shows the owner of each.
|
||||
|
||||
## Where a registration is kept
|
||||
|
||||
The tool-server registrations live in a key-value bucket the module declares
|
||||
([ADR 0201](../../02-DECISIONS/0201-a-module-keeps-its-current-state-in-key-value-buckets-it-declares-and-reaches-through-the-runtime.md)),
|
||||
keyed `all.<name>` or `<node>.<name>`. Skills differ: a skill is a folder, and it can carry scripts
|
||||
and reference files beside its main file. Every message on the bus is limited to about a megabyte.
|
||||
|
||||
1. **One value per item.** The item and its files go in one value, refused above a limit well under
|
||||
the bus's. It is simple, it fits the module's existing state, and every item measured in
|
||||
[01](01-what-is-configured-today.md) takes 20 KB or less on disk,
|
||||
the vendor's synced skills aside. But a skill with a large reference file cannot
|
||||
be registered at all.
|
||||
2. **The bus's object store for files, the bucket for the item.** Large files are stored in pieces and
|
||||
the item names them. Nothing in the mesh uses the object store yet, so ADR 0201 would need
|
||||
extending.
|
||||
3. **A repository on the forge.** The plugin is built from a repository, and registering an item is a
|
||||
commit. This is reviewable and versioned. But a register tool would have to write to the forge,
|
||||
and the forge would sit on the path to every machine.
|
||||
|
||||
*Leaning:* 1 now, with the limit stated and checked at registration. 2 when an item outgrows it. 3
|
||||
mixes the operator's configuration into the code review cycle, which it does not need.
|
||||
|
||||
## Where the plugin is written
|
||||
|
||||
The module owns the managed directory, so the marketplace goes under it, written whole by the
|
||||
module's code:
|
||||
|
||||
- the marketplace file;
|
||||
- one plugin directory beside it.
|
||||
|
||||
It is loaded in place, so a change takes effect at the next session, or at `/reload-plugins` in a
|
||||
running one. Nothing is copied into the home.
|
||||
|
||||
## What the module does about the homes
|
||||
|
||||
[01](01-what-is-configured-today.md) found stale predecessor files on every machine. Under
|
||||
[ADR 0182](../../02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md)
|
||||
the mesh never writes inside a home except what it owns. So the module can:
|
||||
|
||||
- **report** what it finds: a status tool listing the home's skills, subagents, commands and rule files,
|
||||
and naming those that duplicate a mesh item or name tools no longer served;
|
||||
- **import** on request: `claude_code_<kind>_import` takes an item from one machine's home and registers
|
||||
it, so a skill written by hand on one workstation becomes the mesh's in one call. Removing the home
|
||||
copy stays the person's act.
|
||||
|
||||
`strictPluginOnlyCustomization` would make the homes' items stop loading altogether. That is the
|
||||
operator's choice to make through the managed settings, not a default of the module.
|
||||
|
||||
## What to confirm first, on one workstation
|
||||
|
||||
1. A directory marketplace named in the managed settings, with its plugin enabled there, loads with
|
||||
no prompt, in place, in an interactive session and in a non-interactive one.
|
||||
2. The plugin's skills, subagents and commands are offered under `mesh:`, beside the home's own
|
||||
items, with no collision.
|
||||
3. A hook in the plugin runs, with its script found through `${CLAUDE_PLUGIN_ROOT}`.
|
||||
4. The exclusive tool-server file still loads the console, and a server in the plugin does not load,
|
||||
as documented.
|
||||
5. `autoMode` in the managed settings changes what the agent refuses (ADR 0213's open point).
|
||||
6. The account can read the marketplace in the managed directory, which root owns.
|
||||
|
||||
## Questions a decision has to answer
|
||||
|
||||
- One plugin or one per source (leaning: one).
|
||||
- How a registration is kept, and the size limit (leaning: one value per item, with a stated limit).
|
||||
- Whether the managed settings are set through tools writing the module's state, or through the
|
||||
controller's settings layer as ADR 0213 has it. If both, which one wins on the same key.
|
||||
- Whether the agent module holds a seat, so that other modules can contribute to it.
|
||||
- Whether the module may say anything about a home beyond reporting, for example a switch the operator
|
||||
sets to make home items stop loading.
|
||||
Reference in New Issue
Block a user