Plugin + marketplace packaging — distribution as the agency wedge, built from the canonical .claude/ primitives

0014-plugin-marketplace-packaging

decision read as Explain confidence asserted status active 2026-06-14 owner platform-engineer
Reversibility
two-way door

DEC-0014 — Plugin + marketplace packaging (distribution as the agency wedge, built from the canonical .claude/)

Reversibility: two-way door — on curation lists, plugin/marketplace versioning, and serving topology; the build-from-.claude/ SSOT pattern and the plugin-as-agency-product framing are the durable parts.

Context

Claude-primitives-first build strategy named the final packaging primitive: Claude Code plugin(s) for "packaging & distribution to agencies (via marketplace)." Every layer below it now exists in code — the five DEC-0008 packages (okf, extraction, ingestion, mcp, runtime; see MCP agentic foundation — tenant-scoped GraphRAG over the OKF KB, Ingestion connector seam — assemble, don't build, and ingestion owns the input contract), the eval harness (Live extraction eval harness — what we measure is what extraction optimizes for), the expert team and skills (Establish the expert/principal agent team and first skills), and the audit hook (Establish the learning-loop & audit architecture). The missing piece was the delivery mechanism: the single artifact an agency installs to get the whole loop. This decision builds it — the distribution layer that makes "delivered through the agencies that already serve them" (Dossier — Mission & North Star) real.

The build (verified this session, no network beyond docs fetch): the engineer fetched the current schema from code.claude.com/docs (plugins-reference, plugins, plugin-marketplaces) and built to it. A plugin/dossier/ plugin + a top-level .claude-plugin/marketplace.json catalog. Both the plugin AND the marketplace pass claude plugin validate --strict with no warnings. Repo-wide tests remain 268 passed / 1 gated-skip — the plugin is additive packaging; nothing in the runtime changed.

Options considered

1. Where the plugin's agents/skills/hook live — and how they stay in sync with .claude/.

  • (a) Hand-maintain a second copy of the curated agents/skills/hook inside plugin/dossier/, edited directly. Simplest to start — but it creates a diverging second source of truth: every edit to a .claude/ primitive must be mirrored by hand, and the two copies silently rot apart. This is the exact anti-pattern the whole platform exists to kill (single-source-of-truth; OKF as system of record, Adopt OKF as Dossier's canonical knowledge format).
  • (b) Make the plugin the canonical home and have the repo reference it for its own agents/skills. Inverts the recursive moat — the factory (Claude-primitives-first build strategy: "we build with the same primitives we ship") would consume the product's packaging, not the other way around, and the curated agency subset is narrower than the factory's full roster, so the repo would lose the build-Dossier-itself roles.
  • (c) The plugin is a BUILD ARTIFACT assembled from .claude/ by a script (chosen). scripts/build-plugin.mjs is the only producer of the bundled agents/skills/hook: it wipes the generated dirs and re-copies the curated subset from .claude/, so the output is a pure function of .claude/ + the curation lists. Generated dirs are gitignored; only the hand-authored manifests/config/README/script are committed. A --check mode re-runs the build and fails on any content drift — a CI guard that makes the committed-vs-generated split unforgeable. .claude/ stays the one place these primitives are authored; the plugin self-contains a copy because Claude Code copies a marketplace plugin to a cache and refuses paths that escape the plugin dir.

2. What the plugin ships — the full roster vs. an agency-curated subset.

  • (a) Ship all agents/skills. Maximum surface — but it leaks the build-Dossier-itself roles to an audience that runs the loop rather than re-builds the runtime, conflating our factory with the operator's toolkit.
  • (b) Curate the agency-operator subset (chosen). Ship the agency-facing loop — onboard → curate → serve → audit a client KB: 4 skills (ingest-client, author-okf, scaffold-vertical, verify-kb) + 6 agents (knowledge-architect, ingestion-engineer, extraction-engineer, mcp-engineer, qa-reviewer, log-auditor). Exclude the build-Dossier-itself roles principal-architect and platform-engineer, and the parallel ux-engineer (Establish the design system and the UX-engineering function): an agency runs the loop, it doesn't re-build the runtime. The curation lists live in the build script and are justified in the README.

Decision

1. The plugin is a build artifact; .claude/ is the single source of truth. scripts/build-plugin.mjs assembles plugin/dossier/ by copying the curated subset out of .claude/. It is idempotent (wipes generated dirs first, so output = pure function of .claude/ + curation lists) and the only producer of the bundle. Committed (hand-authored, the plugin's contract): plugin.json, .mcp.json, hooks/hooks.json, README.md, and .claude-plugin/marketplace.json. Generated (gitignored, never edited by hand): agents/, skills/, hooks/capture-trace.mjs. node scripts/build-plugin.mjs --check re-runs and fails on drift — the CI guard against a diverging second copy.

2. The bundle is curated for the agency operator, not the product factory. Ships the onboard → curate → serve → audit loop: 4 skills + 6 agents (above). Excludes principal-architect, platform-engineer, ux-engineer. The operator gets the toolkit to run a client's learning loop; they do not get (or need) the roles that build the Dossier runtime itself.

3. Tenant-scoped MCP at install. plugin/dossier/.mcp.json declares the dossier-okf server (npx -y @dossier/mcp) with userConfig that prompts the operator at install for the client's OKF repo (--repookf_repo, a directory) and the tenant id (--clientclient_id), plus optional known_external_ids. Enabling the plugin therefore wires per-client retrieval tools, and --repo is the only directory read — the MCP agentic foundation — tenant-scoped GraphRAG over the OKF KB one-server-one-tenant isolation boundary, now expressed as the install contract (the userConfig description states it: "reads ONLY this directory… one client = one repo"; known_external_ids is "never a read backdoor").

4. Built to the current verified schema; strict-validated. Built against the schema fetched this session from code.claude.com/docs; both the plugin and the marketplace pass claude plugin validate --strict with no warnings.

Rationale

  • SSOT survives packaging — the recursive moat is encoded, not asserted. Making the plugin a pure function of .claude/ (with a --check drift guard) means there is no diverging second copy to rot. This is the Claude-primitives-first build strategy recursive-moat principle made literal at the distribution layer: we ship the same primitives we build with, and a CI failure — not human vigilance — is what catches drift. The same single-source-of-truth discipline the platform sells to clients (Adopt OKF as Dossier's canonical knowledge format) governs our own packaging.
  • The plugin's audience is the operator, not us — so the bundle is the operator's job, not our factory. An agency runs a client's loop; it does not re-build the Dossier runtime. Curating to the 6 agents + 4 skills that onboard/curate/serve/audit a client KB — and dropping principal-architect/platform-engineer/ux-engineer — makes the product's shape match its audience. The exclusion is a positioning statement: the plugin is a product, not a mirror of the repo.
  • One install = the whole agency wedge. This is the mechanism behind Dossier — Mission & North Star's "delivered through the agencies that already serve them": an agency installs one plugin from the marketplace and gets the entire onboard-a-client toolkit plus the tenant-scoped knowledge server — the GTM (Claude-primitives-first build strategy) made concrete and self-serve.
  • Isolation becomes the install contract. Threading the DEC-0011 { clientId, okfRepoPath } through userConfig means the sovereignty/isolation invariant isn't just enforced inside the server — it's what the operator is asked for at install time, and --repo is the only dir served. The boundary is visible at the front door.
  • asserted, not verified. Built to the current schema and strict-validated with no warnings (both plugin and marketplace), additive to a green repo (268 passed / 1 gated-skip) — but it has not been installed from a real published marketplace and the @dossier/mcp package it npx-launches is private: true (unpublished), so the end-to-end npx install path is unexercised. This is design-and-tooling conviction backed by strict validation, not a field install.

Consequences

  • The build arc is closed. With the five DEC-0008 packages built, the eval harness, and now the plugin + marketplace, Dossier is an end-to-end, installable system — ingest → extract → OKF → serve, packaged as a single plugin an agency can install. The platform is a runnable, distributable product, not a design.
  • Editing a .claude/ primitive requires a rebuild. Any change to a bundled agent/skill/hook must be followed by node scripts/build-plugin.mjs; CI's --check fails the build otherwise. The curation lists in the script are the one place to change what ships.
  • Publishing is the remaining gate (TODO). The marketplace repo slug is now confirmed as twofoldtech-dakota/dossier (matching the git remote) and set across the manifests. The packages remain private: true — to enable the real npx @dossier/mcp install path they must still be published to a registry. Until then the plugin strict-validates and the marketplace resolves from the git repo, but the npx-based install cannot run end-to-end (local-build fallback documented in the plugin README).
  • known_external_ids stays a narrow affordance at the install surface too. Exposed in userConfig only to suppress dangling-edge flags — it must never widen into a cross-repo read path, same constraint as MCP agentic foundation — tenant-scoped GraphRAG over the OKF KB.
  • The deferred work is unchanged and behind established seams. The vendor/network backends — the 3 OSS connectors (Ingestion connector seam — assemble, don't build, and ingestion owns the input contract), the live Embedder (MCP agentic foundation — tenant-scoped GraphRAG over the OKF KB), the AgentSdkOrchestrator (Runtime orchestration & per-tenant control plane — the learning loop becomes a runnable system) — plus publishing, are all that remain. No new architecture is owed.
  • Two-way vs. durable. The curation lists, plugin/marketplace versioning, and the multi-tenant serving topology are expected to evolve (two-way door). The build-from-.claude/ SSOT pattern (one producer, gitignored generated dirs, --check drift guard) and the plugin-as-agency-product framing (operator subset, not the factory roster) are the durable, harder-to-reverse commitments.

Review

Revisit the curation when the operator's loop changes shape (a new agency-facing skill or role earns a place, or an excluded role turns out to be needed client-side), and the multi-tenant serving topology if one-server-one-tenant-per-install proves operationally heavy at agency scale (the MCP agentic foundation — tenant-scoped GraphRAG over the OKF KB review trigger, now also a packaging concern). Concretely: publish the packages (drop private: true) and confirm the real marketplace repo slug to enable and exercise the npx install path end-to-end — at which point consider promoting confidence to verified.