Agents: per-instance Claude Code plugin enablement (bake once, enable per instance) #72

Closed
opened 2026-04-18 23:39:51 +00:00 by claude-desktop · 0 comments
Collaborator

User story

As the operator, I want to pick which Claude Code plugins a given
agent instance runs with — independent of its type — so that a
dev instance touching the Python MCP can have Pyright LSP
enabled while another dev instance touching only TypeScript has
TypeScript LSP enabled, and a designer instance working on
brutalist mockups can have Frontend Design without forcing it on
every designer dispatch.

This is the next axis on the milestone-16 per-instance customization
surface, next to model override (#48), prompt appendix (#51),
match-labels (#50), and the dashboard CRUD that surfaces them
(#53).

Design principle: separate install from enable

Install is image-side. The container image bakes in every
vetted Anthropic-verified plugin we might want — one-time build
cost, cheap storage (~100 MB per LSP, skills are a few KB each).

Enable is instance-side. Each agent instance's isolated
CLAUDE_CONFIG_DIR carries a settings.json that lists only the
plugins that instance should activate. The SDK's Claude CLI ignores
installed-but-not-enabled plugins, so unused ones cost nothing at
runtime.

This means:

  • Adding a new plugin to the approved set = image rebuild (once).
  • Changing which instances use which plugins = dashboard edit (seconds),
    no rebuild.
  • Removing a plugin from an instance = dashboard edit, no risk of
    breaking other instances.

Current state (2026-04-19)

  • Agent instances today are identified by type only — boss,
    dev, reviewer, designer, design-reviewer — each with a
    fixed row in config/agents.json.
  • No Anthropic-verified plugins are currently installed in the
    image; the fork-maintained forgejo-mcp + penpot-mcp are baked
    in but the wider Claude Code plugin ecosystem is untouched.
  • #48's SQLite store (per-instance overrides) is the planned home
    for this axis; a plugins column / join fits cleanly.

Acceptance criteria

Image — bake in the approved plugin set (one-time)

  • Dockerfile installs the vetted plugins during build via
    claude plugin install <id> steps, idempotent and offline-
    capable. Each plugin pins a version.
  • Initial approved set (picked from
    claude.com/plugins, all Anthropic-
    verified):
    - frontend-design
    - security-guidance
    - claude-md-management
    - typescript-lsp
    - pyright-lsp
    - pr-review-toolkit
    - skill-creator
    - hookify
    - plugin-developer-toolkit
    - mcp-server-dev
    - agent-sdk-dev
  • scripts/smoke-creds.sh grows a plugin-presence probe:
    docker exec claude-hooks-<agent> claude plugin list should
    include every approved plugin's id. Regression gate if an image
    build drops a plugin.

Config — per-type defaults + per-instance overrides

  • config/agents.json grows a plugins: [<id>, …] key per
    type. Acts as the default enable-set new instances of that
    type inherit.
  • Proposed type defaults:
    - boss / dev: security-guidance, typescript-lsp, plus
    pyright-lsp if the instance is labelled to touch Python.
    - reviewer: security-guidance, typescript-lsp,
    pr-review-toolkit.
    - designer: frontend-design, security-guidance.
    - design-reviewer: frontend-design, security-guidance.
  • SQLite per-instance table (landed on #48) gains a plugins
    column — JSON array of plugin ids. NULL = inherit type default.
    A non-null value fully overrides (not layered) so the
    edit-semantics stay predictable in the UI.

Runtime — per-instance CLAUDE_CONFIG_DIR carries the enable-set

  • When the service dispatches a task, it writes a
    per-instance settings.json into that instance's isolated
    CLAUDE_CONFIG_DIR with the enabled plugin list before
    spawning the SDK query().
  • The SDK picks up the isolated config (already the case via
    seedContainerClaudeJson from PR #67 / issue #56). Adding
    settings.json alongside the existing .claude.json +
    .credentials.json seed is a one-line extension of
    seedContainerClaudeJson — same idempotent pattern.
  • Container restart or rebuild is not required to change an
    instance's plugin list — just the next task dispatch reads
    the fresh settings.json.

Dashboard (hooks into #53)

  • Instance create / edit form gets a Plugins multi-select:
    checkbox per plugin, defaulted to the type's default set.
  • Shows plugin component type (Skills / Subagents / MCP /
    Hooks / Slash commands) as a chip next to the name so the
    operator knows the cost profile at a glance.
  • Dirty indicator when the selection diverges from the type
    default (UX: "override" badge).

Tests

  • Unit: buildMcpSetup equivalent for plugins — given an
    instance config, assert the emitted settings.json has the
    right enabledPlugins field and no stray enable flags.
  • Unit: type-default inheritance — instance with plugins: null
    in SQLite resolves to the type default; instance with an
    explicit empty array [] resolves to no plugins (escape
    hatch for debugging).
  • Integration: a designer instance with frontend-design
    disabled gets the abort-diagnostic if its skill tries to use
    a Frontend Design tool. A designer instance with it enabled
    resolves the tool.

Documentation

  • CLAUDE.md § Plugin management section: how to propose a
    new plugin (add to Dockerfile + approve via PR), how to enable
    it per instance (dashboard).
  • README.md: one-liner on the per-instance plugin axis.
  • penpot-mcp-server/CHANGELOG.md isn't affected — this ticket
    is service-side only.

Out of scope

  • Third-party / unverified plugins. Only Anthropic-verified
    plugins land in the baked set. A separate story if we ever want
    community plugins — the trust model is different.
  • Per-task plugin toggles. The enable-set is per-instance, not
    per-dispatch. A task can't opt in to a plugin the instance doesn't
    have. Keeps the surface clean.
  • Plugin version pinning UI. Dashboard picks from the installed
    set; bumping a plugin version = Dockerfile edit + image rebuild,
    not dashboard work. Version pins live in Dockerfile + CHANGELOG.
  • Plugin-specific configuration (e.g., TypeScript LSP's tsconfig
    auto-discovery, PR Review Toolkit's confidence threshold). Each
    plugin's own settings stay inside its manifest; this ticket only
    flips the on/off bit.

References

  • Plugin catalog: https://claude.com/plugins (filter: Anthropic Verified).
  • Milestone: Agent pool + customization (#16).
  • Companion tickets:
    • #48 — SQLite per-instance store (this adds the plugins column).
    • #51 — per-instance prompt appendix (parallel axis).
    • #50 — label-aware instance routing (an instance's label match +
      its plugin set together determine "what agent picks up this issue
      with what toolkit").
    • #53 — dashboard CRUD (the UI that surfaces the plugin multi-select).
  • Infrastructure already landed (from #56):
    • PR #67's seedContainerClaudeJson is the extension point where
      the per-instance settings.json gets written.
    • Per-agent isolated CLAUDE_CONFIG_DIR means plugin enable-sets
      are already strongly partitioned — the interactive user's config
      can't leak.

Dependencies

  • Blocked by #48 — needs the SQLite per-instance table first.
  • Pairs naturally with #51 and #53 — ideal implementation PR
    lands all three axes' dashboard edit surfaces in one pass.
  • Branch off: main, after #48 lands.

Suggested breakdown

  1. Dockerfile bake-in — install the approved plugins, extend
    smoke-creds.sh with a presence probe. PR includes the plugin
    list as a constant somewhere the service can read.
  2. Type defaults in config/agents.json — add plugins key
    per type, document the semantics.
  3. seedContainerClaudeJson extension — also seed
    settings.json with the resolved enable-set per task.
  4. SQLite plugins column (on top of #48) + resolver:
    instance → type → default.
  5. Dashboard multi-select (on top of #53).
  6. Docs pass.
## User story As the **operator**, I want to pick which Claude Code plugins a given agent *instance* runs with — independent of its type — so that a `dev` instance touching the Python MCP can have **Pyright LSP** enabled while another `dev` instance touching only TypeScript has **TypeScript LSP** enabled, and a `designer` instance working on brutalist mockups can have **Frontend Design** without forcing it on every designer dispatch. This is the next axis on the milestone-16 per-instance customization surface, next to **model override** (#48), **prompt appendix** (#51), **match-labels** (#50), and the **dashboard CRUD** that surfaces them (#53). ## Design principle: separate install from enable **Install is image-side.** The container image bakes in every vetted Anthropic-verified plugin we might want — one-time build cost, cheap storage (~100 MB per LSP, skills are a few KB each). **Enable is instance-side.** Each agent instance's isolated `CLAUDE_CONFIG_DIR` carries a `settings.json` that lists only the plugins that instance should activate. The SDK's Claude CLI ignores installed-but-not-enabled plugins, so unused ones cost nothing at runtime. This means: - Adding a new plugin to the approved set = image rebuild (once). - Changing which instances use which plugins = dashboard edit (seconds), no rebuild. - Removing a plugin from an instance = dashboard edit, no risk of breaking other instances. ## Current state (2026-04-19) - Agent instances today are identified by type only — `boss`, `dev`, `reviewer`, `designer`, `design-reviewer` — each with a fixed row in `config/agents.json`. - No Anthropic-verified plugins are currently installed in the image; the fork-maintained `forgejo-mcp` + `penpot-mcp` are baked in but the wider Claude Code plugin ecosystem is untouched. - #48's SQLite store (per-instance overrides) is the planned home for this axis; a `plugins` column / join fits cleanly. ## Acceptance criteria ### Image — bake in the approved plugin set (one-time) - [ ] `Dockerfile` installs the vetted plugins during build via `claude plugin install <id>` steps, idempotent and offline- capable. Each plugin pins a version. - [ ] Initial approved set (picked from [claude.com/plugins](https://claude.com/plugins), all Anthropic- verified): - `frontend-design` - `security-guidance` - `claude-md-management` - `typescript-lsp` - `pyright-lsp` - `pr-review-toolkit` - `skill-creator` - `hookify` - `plugin-developer-toolkit` - `mcp-server-dev` - `agent-sdk-dev` - [ ] `scripts/smoke-creds.sh` grows a plugin-presence probe: `docker exec claude-hooks-<agent> claude plugin list` should include every approved plugin's id. Regression gate if an image build drops a plugin. ### Config — per-type defaults + per-instance overrides - [ ] `config/agents.json` grows a `plugins: [<id>, …]` key per type. Acts as the default enable-set new instances of that type inherit. - [ ] Proposed type defaults: - `boss` / `dev`: `security-guidance`, `typescript-lsp`, plus `pyright-lsp` if the instance is labelled to touch Python. - `reviewer`: `security-guidance`, `typescript-lsp`, `pr-review-toolkit`. - `designer`: `frontend-design`, `security-guidance`. - `design-reviewer`: `frontend-design`, `security-guidance`. - [ ] SQLite per-instance table (landed on #48) gains a `plugins` column — JSON array of plugin ids. NULL = inherit type default. A non-null value fully overrides (not layered) so the edit-semantics stay predictable in the UI. ### Runtime — per-instance `CLAUDE_CONFIG_DIR` carries the enable-set - [ ] When the service dispatches a task, it writes a per-instance `settings.json` into that instance's isolated `CLAUDE_CONFIG_DIR` with the enabled plugin list before spawning the SDK `query()`. - [ ] The SDK picks up the isolated config (already the case via `seedContainerClaudeJson` from PR #67 / issue #56). Adding `settings.json` alongside the existing `.claude.json` + `.credentials.json` seed is a one-line extension of `seedContainerClaudeJson` — same idempotent pattern. - [ ] Container restart or rebuild is **not required** to change an instance's plugin list — just the next task dispatch reads the fresh `settings.json`. ### Dashboard (hooks into #53) - [ ] Instance create / edit form gets a **Plugins** multi-select: checkbox per plugin, defaulted to the type's default set. - [ ] Shows plugin component type (Skills / Subagents / MCP / Hooks / Slash commands) as a chip next to the name so the operator knows the cost profile at a glance. - [ ] Dirty indicator when the selection diverges from the type default (UX: "override" badge). ### Tests - [ ] Unit: ``buildMcpSetup`` equivalent for plugins — given an instance config, assert the emitted `settings.json` has the right `enabledPlugins` field and no stray enable flags. - [ ] Unit: type-default inheritance — instance with `plugins: null` in SQLite resolves to the type default; instance with an explicit empty array `[]` resolves to **no** plugins (escape hatch for debugging). - [ ] Integration: a designer instance with `frontend-design` disabled gets the abort-diagnostic if its skill tries to use a Frontend Design tool. A designer instance with it enabled resolves the tool. ### Documentation - [ ] `CLAUDE.md` *§ Plugin management* section: how to propose a new plugin (add to Dockerfile + approve via PR), how to enable it per instance (dashboard). - [ ] `README.md`: one-liner on the per-instance plugin axis. - [ ] `penpot-mcp-server/CHANGELOG.md` isn't affected — this ticket is service-side only. ## Out of scope - **Third-party / unverified plugins.** Only Anthropic-verified plugins land in the baked set. A separate story if we ever want community plugins — the trust model is different. - **Per-task plugin toggles.** The enable-set is per-instance, not per-dispatch. A task can't opt in to a plugin the instance doesn't have. Keeps the surface clean. - **Plugin version pinning UI.** Dashboard picks from the installed set; bumping a plugin version = Dockerfile edit + image rebuild, not dashboard work. Version pins live in `Dockerfile` + `CHANGELOG`. - **Plugin-specific configuration** (e.g., TypeScript LSP's tsconfig auto-discovery, PR Review Toolkit's confidence threshold). Each plugin's own settings stay inside its manifest; this ticket only flips the on/off bit. ## References - Plugin catalog: https://claude.com/plugins (filter: Anthropic Verified). - Milestone: **Agent pool + customization** (#16). - Companion tickets: - #48 — SQLite per-instance store (this adds the `plugins` column). - #51 — per-instance prompt appendix (parallel axis). - #50 — label-aware instance routing (an instance's label match + its plugin set together determine "what agent picks up this issue with what toolkit"). - #53 — dashboard CRUD (the UI that surfaces the plugin multi-select). - Infrastructure already landed (from #56): - PR #67's `seedContainerClaudeJson` is the extension point where the per-instance `settings.json` gets written. - Per-agent isolated `CLAUDE_CONFIG_DIR` means plugin enable-sets are already strongly partitioned — the interactive user's config can't leak. ## Dependencies - **Blocked by #48** — needs the SQLite per-instance table first. - **Pairs naturally with #51 and #53** — ideal implementation PR lands all three axes' dashboard edit surfaces in one pass. - **Branch off:** `main`, after #48 lands. ## Suggested breakdown 1. **Dockerfile bake-in** — install the approved plugins, extend `smoke-creds.sh` with a presence probe. PR includes the plugin list as a constant somewhere the service can read. 2. **Type defaults in `config/agents.json`** — add `plugins` key per type, document the semantics. 3. **`seedContainerClaudeJson` extension** — also seed `settings.json` with the resolved enable-set per task. 4. **SQLite `plugins` column** (on top of #48) + resolver: instance → type → default. 5. **Dashboard multi-select** (on top of #53). 6. **Docs pass.**
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks#72
No description provided.