Consumption Model
The BeWith engineering platform ships as one Claude Code plugin published from this repo (bewith-dev/bewith-docs). A developer installs it once per machine; from then on every Claude Code session, in any repo, loads the platform's agents, skills, commands, hooks, policies, and team-rules. Git enforcement reaches every repo through a globally configured hooks path. CI mirrors the enforcement so PRs from tools that do not run the local hooks (Copilot, Claude mobile, manual developers) still hit the same gates.
This page is the operational guide โ what lives where, what loads when, and how to add a new file without it silently failing to ship. The strategic context (federation as a principle) is in ai-engineering-strategy.md ยง6 and deterministic-ai.md principle 7.
.claude/ is the single source of truth โ the plugin root re-exposes itโ
Every artifact lives once, under .claude/ (or policies/, .claude/rules/). Nothing is copied per repo or per tool. The one subtlety is how Claude Code discovers the native components, because the plugin loader and the in-repo loader look in different places:
- Inside a Claude Code session, project config is read from
.claude/(agents, skills, commands, rules). - A plugin's components are discovered at the plugin ROOT (
agents/,commands/,skills/,hooks/hooks.json) โ not under.claude/.
If we only kept the files under .claude/, the plugin would expose nothing to other repos โ which is exactly the bug that shipped silently until #156: claude plugin details bewith showed Skills(0) / Agents(0) / Hooks(0), so every other repo got an empty shell โ no session safety, no agents. The fix keeps .claude/ as the source of truth and re-exposes each category at the root with a per-category symlink:
bewith-docs/ (= the plugin root; marketplace source is "./")
โโโ .claude-plugin/
โ โโโ plugin.json (name: bewith โ no version โ SHA-versioned, always-latest)
โ โโโ marketplace.json (name: bewith-dev, plugins[].source: "./")
โโโ .claude/ โ SINGLE SOURCE OF TRUTH
โ โโโ agents/ skills/ commands/
โ โโโ hooks/ (scripts + hooks.json)
โ โโโ rules/team/ (accepted team rules)
โโโ agents โ .claude/agents โ
โโโ commands โ .claude/commands โ per-category symlinks: the loader
โโโ skills โ .claude/skills โ discovers these at the root while
โโโ hooks โ .claude/hooks โ the files stay under .claude/
โโโ policies/ (Policy Engine YAML โ read from here at runtime)
โโโ git-hooks/ (pre-commit / pre-push โ wired via core.hooksPath)
hooks/hooks.json (reached through the hooks symlink) declares the Claude Code hooks; each entry runs as bash ${CLAUDE_PLUGIN_ROOT}/.claude/hooks/<name>.sh, so it resolves correctly in every repo.
The guarantee against silent breakage: scripts/validate-plugin-symlinks.mjs (wired into pnpm validate, so it runs in CI) fails if any component category under .claude/ lacks its root symlink. The symlinks are per-category, not per-file, so a new agent/skill/command/hook file is exposed automatically โ only a brand-new component category needs a one-time new symlink, and the validator is what tells you so.
What loads into context, and whenโ
Shipping everything globally sounds heavy; it is not, because almost all of it is lazy. This is the model to keep in your head โ what is read, and what is not:
| Component | Exposed via | Loaded into context? | When |
|---|---|---|---|
| Agents | agents/ symlink | Description only (~100 tok) always-on; the body loads when invoked (@agent-X or the auto-router) | lazy |
| Skills | skills/ symlink | name + description always-on; the body loads when the Skill tool invokes it | lazy |
| Commands | commands/ symlink | name + description always-on; the body loads when the user types /cmd | lazy |
| Hooks | hooks/hooks.json | Zero context cost โ they are scripts that run out-of-band on events; only what a hook emits (additionalContext) enters context | on event |
Policies (policies/*.yaml) | not a native type | Not auto-loaded. The model reads the YAML only when agent-guard / /continue step 4 explicitly reads it | on demand |
Team rules (.claude/rules/team/*.md) | not a native type | In bewith-docs: Claude Code auto-loads .claude/rules/ natively. In a consumer: the team-rules-inject hook injects them | always-on |
In short: always read = every agent/skill/command description plus whatever the SessionStart hooks inject (team-rule guidance, capability awareness, conversation language, and the accepted team rules). Read only on demand = agent/skill/command bodies and the policy YAML. Never read into context = the hook scripts themselves (they execute; only their output may be injected).
Policies and team-rules: the two non-native typesโ
A plugin distributes only agents / skills / commands / hooks natively. Policies and team-rules are not native component types, so they need a deliberate distribution path (#158):
- Policies โ read from the plugin.
agent-guardand/continuestep 4 resolve the policy directory from the installed plugin โ thebewith@bewith-deventry'sinstallPathin~/.claude/plugins/installed_plugins.json, plus/policiesโ with a working-tree fallback inside bewith-docs. Thetriggered_byglobs always match the consumer's changed files; only the policy definitions come from the plugin. - Team rules โ injected at SessionStart. Claude Code auto-loads a repo's own
.claude/rules/**natively, so the rules load for free inside bewith-docs (which is why they work there with no rootCLAUDE.md). A consumer has no such files, and@.claude/rules/team/X.mdimports are repo-relative (they resolve to nothing in a consumer). Theteam-rules-injectSessionStart hook reads the accepted rules from${CLAUDE_PLUGIN_ROOT}/.claude/rules/team/*.mdand injects them; it no-ops inside the platform repo to avoid double-injection.
Both mechanisms glob the plugin directory, so a new policy or accepted rule ships automatically on the next plugin update โ the same "add a file, it's distributed" guarantee the per-category symlinks give native components.
How a developer installs and updatesโ
| Step | Command / mechanism | Frequency |
|---|---|---|
| Register the plugin | bewith platform install (the @bewith-dev/cli) โ verifies GITHUB_TOKEN/GH_TOKEN, then merges the bewith-dev marketplace + bewith@bewith-dev into ~/.claude/settings.json. (bewith onboard is the fuller new-machine flow โ stack + seed + MCP + menu bar โ gated behind a confirmation; use platform install for just the plugin.) | Once per machine |
| Wire git enforcement | Automatic โ the plugin's git-hooks-sync SessionStart hook points global core.hooksPath at the plugin's current git-hooks/ and keeps it fresh across always-latest updates. Safe-by-default: no-op on non-bewith repos (bewith-repo-guard.sh), and it won't clobber a custom core.hooksPath (warns; scripts/wire-global-git-hooks.sh --force overrides). | Automatic on first session |
| Scaffold a consumer repo | scripts/install-to-consumer.sh โ creates CLAUDE.md + .github/copilot-instructions.md + the CI caller if absent; never commits | Once per repo |
The runbook is install-bewith-plugin.md. After install, the developer is set โ a repo cloned tomorrow already has enforcement.
Updating is automatic (always-latest). The bewith plugin carries no explicit version in plugin.json or its marketplace entry, so Claude Code resolves the version to the git commit SHA of the GitHub source and auto-updates at session start โ every merge to master reaches everyone on their next session, with no manual step (INS-2, adopted 2026-06-08). Because the marketplace repo is private, startup auto-update requires a GITHUB_TOKEN (or GH_TOKEN) in the environment; interactive installs/updates use your existing git credentials instead. Manual update when needed: claude plugin update bewith@bewith-dev โ the @marketplace qualifier is required (a bare claude plugin update bewith reports "not found"). There is no built-in release gating: to gate, pin the marketplace entry to a ref (tag or stable branch) instead of tracking master. Acceptable while the platform has no external users; revisit with a staged channel when it does.
Local-marketplace note (platform devs only). A platform dev can instead register
bewith-devas a directory marketplace pointing at the working tree, soclaude plugin marketplace update bewith-devre-reads local edits without a GitHub round-trip โ handy for testing plugin changes before merge. The refresh for a directory source isuninstall+ reinstall (it does not auto-update). Teammates consuming the GitHub source get always-latest and do not need this.
How each tool reaches the platformโ
| Tool | Mechanism |
|---|---|
| Claude Code (CLI / desktop / IDE) | The installed plugin โ components auto-discovered at the root, lazy-loaded per task; hooks declared in hooks/hooks.json; policies + team-rules via the mechanisms above. |
| GitHub Copilot | Reads <repo>/.github/copilot-instructions.md, which points at CLAUDE.md and the platform's GitHub URLs. References, not copies. |
| Claude mobile / Claude.ai web | No hook runtime; suggestions only. CI catches anything that reaches a PR. |
| Manual developer (no AI) | Git hooks fire on commit / push; CI fires on every PR. |
How to add a new file safelyโ
This is the practical payoff โ what makes "drop a file and trust it ships" true:
- A new agent / skill / command / hook file โ drop it under
.claude/<category>/. It is exposed automatically (the category symlink already exists).pnpm validateconfirms the symlink set is intact. - A new policy โ drop
policies/<name>.yaml.agent-guardglobs the directory; no wiring. (validate-policies.mjschecks it againstschema.json.) - A new accepted team rule โ land it under
.claude/rules/team/via/share-rule; the inject hook globs the directory. Do not add an@-import to the consumerCLAUDE.mdโ those are repo-relative and broken in consumers. - A brand-new component category (the only manual step) โ add the root symlink for it. If you forget,
validate-plugin-symlinks.mjsfails the build and tells you exactly which category is unexposed. This is the deliberate backstop: the only thing that can silently fail to ship is caught by CI.
After any of these, bump nothing structural โ the distribution is automatic. Consumers pick it up on their next plugin update.
Overlay: repo-specific behaviorโ
Claude Code applies project-level (<cwd>/.claude/) over the plugin by name shadowing โ when names collide, project wins; different names both load (Anthropic โ sub-agents scope priority).
- Add a repo-specific agent: drop
<consumer>/.claude/agents/<unique-name>.mdโ loads in addition to the platform's. - Override a platform agent for one repo: drop
<consumer>/.claude/agents/<platform-name>.mdโ shadows the platform's version in that repo. - For git hooks, a consumer's
git-hooks/config.envoverridesREQUIRE_*toggles for that repo only.
No custom merge logic โ we rely on Claude Code's native scope priority.
CI is the mandatory gateโ
Local hooks fire only for tools that respect them; Copilot, Claude mobile, and manual developers do not. CI is therefore the final enforcement layer. Consumers wire the reusable workflows via their own caller (uses: bewith-dev/bewith-docs/.github/workflows/<wf>.yml@<ref>). The @ref pins which version of the platform's workflow logic the repo runs against โ bump to upgrade, change to roll back. See the uniform-CI runbook.
The version contract: the plugin, not a lock fileโ
There is no governance.lock. An earlier design pinned each consumer to a platform version through a per-repo governance.lock.json plus a validator workflow (governance-version-check.yml) and the governance-build.sh SessionStart hook that rebuilt a .claude/effective/ directory. All of it was removed (2026-05-27 for the lock contract; the build hook 2026-06-08) โ the audit trail is in glossary.md and ai-engineering-strategy.md ยง6.4โยง6.5. The version contract is now just two things:
- The plugin each developer has installed โ always-latest by default (SHA-versioned, auto-updated at startup; INS-2).
- The
@refon each consumer's CI wrapper workflow.
The plugin tracks the commit SHA (always-latest); the wrapper @ref pins CI logic per consumer. Together they cover the intent โ known state, one-line upgrade, revert-as-rollback โ without an extra file or a per-session build step. If audit needs ever require per-repo version recording beyond the wrapper @ref, the decision can be revisited; it would be expressed in plugin terms (a pinned marketplace ref), not a lock file.
What this model does not handleโ
- Per-repo version pinning of the local Claude Code session. The
@refpins CI; a developer's local session tracks the plugin's latest commit (always-latest, INS-2) โ there is no per-repo plugin pin short of a pinned marketplaceref. - Subagent nesting. Plugin subagents cannot spawn further subagents; "Handoff Points" in agent files are recommendations to the main session, not in-band Agent-tool calls.
- Hook scope. Plugin-shipped hooks fire in every Claude Code session on the machine. They must be self-aware โ resolve the repo root via
${CLAUDE_PROJECT_DIR}/ git and no-op cleanly where they do not apply (e.g.team-rules-injectskipping the platform repo). - The claude.ai chat product (web / mobile / Claude desktop chat app). A different runtime from Claude Code โ no hook execution, no local filesystem or git working tree, no local scripts. The plugin does not load there; only the knowledge layer can reach it, by re-publishing select skills as account-level claude.ai Skills (org owner uploads a
SKILL.mdzip). A 27-skill audit found 1 cleanly portable (mermaid-diagrams), 8 viable only as reduced advisory versions via the org's MCP connectors (GitHub/ClickUp/Jira/Confluence/Figma/Slack), and 18 local-by-design (worktrees, local scripts, the sandbox, the hook runtime). The enforcement and automation layer is inherently local. (A Claude Code session steered remotely from the mobile/web app is not this case โ it's a real local session, so the plugin runs; only permission dialogs can't be approved remotely.) Because the local gates don't fire off the Claude Code surfaces, CI is the hard floor for this case too โ every PR hits the same lint/typecheck/test + branch-name/CU-id/file-size checks regardless of where the work came from (the cloud chat, another AI tool, a no-AI commit), made non-bypassable by branch protection. So it is acceptable that work off the local surfaces is not locally gated; CI still catches it before merge.
See alsoโ
- ai-engineering-strategy.md ยง6 โ the strategic case for federation.
- Anthropic โ Plugins and Plugin marketplaces โ the mechanism behind discovery, sources, and versioning.
templates/consumer-repo/README.mdโ what the per-consumer scaffold contains and deliberately omits.scripts/validate-plugin-symlinks.mjsโ the guard that keeps every category exposed.- POC Insights โ INS-2 โ the always-latest auto-update decision (adopted).