Why: AGENTS.md is preloaded into every session via CLAUDE.md's @AGENTS.md import, so every word is a tax paid whether or not it is relevant. It had grown to 3,371 words (~4,550 tokens), 75% of it a single "Setup and testing" section that was mostly neither: roughly 1,600 words were gate forensics — why a hook is shaped the way it is, what was tried and rejected, verification numbers — and only ~390 words were genuine one-time setup. Implementation notes: split three ways by audience rather than by heading. One-time setup, prerequisites and test commands move to a new root README.md, which did not previously exist. The gate forensics move to a new docs/spec/gates.md, preserving every constant, hook ID, error string and tried-and-rejected note (policy.fetch_failure_default, the removed fallback frontmatter reader, extending skill-size-check's files: pattern to agents, tidying .claude/settings.json out of the pretty-format-json exclude list) — these exist to stop settled decisions being re-litigated, so none were dropped. AGENTS.md keeps only what applies every session, at one clause of rationale per rule. The .apm/-mirror section collapses to a pointer because docs/spec/architecture.md already covers it in full. The hot ADR-0020 gate counts stay in AGENTS.md deliberately: they block any skill edit today. Also corrects docs/spec/architecture.md's stale claim of 19 ADRs; ADR-0020 landed without updating the count. Impact: per-session preload drops from ~4,550 to ~1,116 tokens, a 75% cut. agentsmd-audit passes: secrets clean, drift clean, 4 informational findings for sections deliberately relocated to the README.
7.7 KiB
Architecture
Layered model
this repo (global defaults)
└── scripts/install.sh → ~/.claude/ (Claude Code config + content)
project repo (local overrides)
└── .claude/settings.json, CLAUDE.md (overrides global)
Content deployment model
scripts/install.sh is a deployer, not a composer. It sources scripts/deploy-manifest.sh and deploys three categories:
- Files (
DEPLOY_FILES):providers/claude-code/CLAUDE.md→~/.claude/CLAUDE.md;providers/claude-code/settings.json→~/.claude/settings.json;core/AGENTS.md→~/.agents/AGENTS.md - Executables (
DEPLOY_EXECUTABLES):providers/claude-code/statusline-command.sh→~/.claude/statusline-command.sh(with+x) - Directories (
DEPLOY_DIRS):core/→~/.claude/core/(destination fully replaced on each deploy)
Skills are not deployed by install.sh. They are distributed as plugins and installed separately — in this repo by apm install against the dependencies.apm entries in the root apm.yml, which lands them in .claude/skills/ and .claude/agents/ (ADR-0018); elsewhere by claude plugin install <name>@holocron.
~/.claude/CLAUDE.md is a thin adapter, not a content source. It imports ~/.agents/AGENTS.md (always-on rules) and governance.md (always-on governance), then lists the content index. All always-on content lives in AGENTS.md files so other providers can import the same source without duplication.
Plugin model
Skills, agents, MCP servers, and hooks are distributed as self-contained plugin units under plugins/, installed independently — via apm install here, or claude plugin install <name>@holocron for a host consuming the marketplace natively (ADR-0018). Each plugin is an apm package: plugins/<name>/apm.yml plus a hand-authored plugins/<name>/.apm/{skills,agents,hooks,commands,instructions,extensions}/ tree (ADR-0015). There is no hand-maintained plugin.json — every manifest and every host-visible content directory is compiled from that source.
Two compilers produce the plugin roots you see in the tree:
apm packcompiles the manifests (ADR-0015). Per plugin:.claude-plugin/plugin.jsonand.github/plugin/plugin.json, both generated fromplugins/<name>/apm.yml. Repo-wide, from the rootapm.yml'smarketplace:block:.claude-plugin/marketplace.json(apm'sclaudeoutput profile) and.agents/plugins/marketplace.json(itscodexprofile, a differently-shaped file). Those two are the only marketplace outputs apm has profiles for — the third root manifest,.github/plugin/marketplace.json(Copilot CLI's legacy path), is a byte-identical mirror of the Claude one maintained byscripts/sync-marketplace-mirror.shand gated by thecheck-marketplace-mirror-syncpre-push hook.scripts/sync-plugin-content.shcompiles the content mirror (ADR-0017). It wrapsapm pack --format pluginand copies the resulting bundle's flatagents/,skills/,commands/,instructions/,extensions/, and mergedhooks/hooks.jsonback to the plugin root. Claude Code's installer convention-scans those flat paths and has no.apm/awareness whatsoever, so the mirror exists solely to satisfy the host's discovery contract.
.apm/ is the sole hand-edited authoring source for plugin content. An edit made in the flat mirror is discarded by the next sync and is reported as drift by the check-plugin-content-sync pre-push hook. Hand-authored material that is not an .apm/ primitive — README.md, docs/, bin/, sources.md, .mcp.json, and per-plugin extras such as plugins/git/config.example.json, plugins/gitea/references/ and plugins/bin/evals/ — lives at the plugin root and is untouched by either compiler.
That immunity is positional, not by filename. Anything placed inside a mirrored directory is destroyed regardless of what it is: sync_dir runs rm -rf "$dst" before every copy, and sync_hooks_json does the same to hooks/. A hand-written README.md under plugins/<name>/hooks/ or plugins/<name>/skills/ is deleted by the next sync with no drift report, because a file with no .apm/ counterpart is simply absent from the regenerated tree. This has already cost the repo one document — plugins/kyberforge/hooks/README.md, since restored to plugins/kyberforge/docs/hooks.md. Plugin-root documentation belongs in docs/.
Governance layer
core/instructions/governance.md is the always-on governance instruction file. Unlike the on-demand instruction files in the content index, governance.md is loaded into every Claude session via @import in providers/claude-code/CLAUDE.md. This is a technical guarantee, not a behavioural instruction — @import causes Claude Code to expand and load the file at launch, before any interaction begins.
The governance layer has two phases:
- Phase 1 (complete): instruction and documentation layer —
governance.mdloaded via@import;docs/ai-constitution.mdanddocs/wiki/HUMANS.mdas human-facing reference;CONTEXT.mdextended with governance domain language. - Phase 2 (planned): deterministic enforcement layer — pre-commit hooks, CI gates, secret scanning, licence scanning. Specified in
docs/research/governance_principles/CONTROLS.md.
AGENTS.md pattern
This repo uses two AGENTS.md files as the provider-agnostic source of always-on rules (ADR-0003):
- Repo-level
AGENTS.md— instructions for agents working inside this repo (structure, key rules). Imported by repoCLAUDE.mdvia@AGENTS.md. - Global
core/AGENTS.md— Communication and Behavior rules that apply across all projects. Deployed to~/.agents/AGENTS.md; imported by~/.claude/CLAUDE.mdvia@~/.agents/AGENTS.md.
Both CLAUDE.md files are thin adapters: they import from their respective AGENTS.md and add only Claude Code-specific syntax (@import, content index paths). They carry no original always-on content.
This repo also has a CLAUDE.md at its root — the Claude Code entry point for working in this repo. It imports AGENTS.md and nothing else; there is no @CONTEXT.md import. It is not import-only either: below the import sits a fenced <!-- rtk-instructions v2 --> … <!-- /rtk-instructions --> block carrying the RTK command-prefix convention, which is tool-specific content with no AGENTS.md source. This is distinct from providers/claude-code/CLAUDE.md, which is the global config deployed to ~/.claude/.
CONTEXT.md is therefore not always-loaded. AGENTS.md instructs agents to read it at session start, which is a behavioural instruction, not an @import guarantee — LESSONS.md's 2026-05-17 entry proposed adding the import and it was never applied. Treat that entry as open work rather than a record of a landed change.
Provider model
core/ is never tool-specific. providers/ is never shared. When adding a new provider, write an adapter in providers/<name>/ that translates core content into the tool's expected format and location. The core content itself does not change.
Architectural decisions
Key hard-to-reverse decisions are recorded as ADRs in docs/adr/. There is no index file — the directory holds 20 numbered ADRs whose filenames state their decision, so ls docs/adr/ is the index. Read a superseding ADR before the one it supersedes: ADR-0015 (apm as the authoring source of truth) supersedes ADR-0001 and moots ADR-0006, ADR-0017 corrects ADR-0015's host-discovery gap, and ADR-0019 supersedes one claim in ADR-0018 (that .claude/settings.json's committed content is exactly {"hooks": {}}) while keeping the rule behind it. Entry points for the structure described on this page: ADR-0002 (two-tier CLAUDE.md), ADR-0003 (AGENTS.md as the provider-agnostic entry point), ADR-0015 and ADR-0017 (the two compilers behind the plugin roots).