Skip to content

Context architecture — the lean-surface doctrine

Canonical reference for where knowledge lives in himmel so the always-loaded surface stays small. Every token in CLAUDE.md, MEMORY.md, an enabled plugin’s catalog, or a loaded skill is paid in every session — and again in every subagent context. The discipline here keeps that fixed cost low without losing the knowledge: push detail to layers that load on demand, and make retrieval — not always-loaded prose — the default.

This doc is the anchor. The operating rules that reference it — CLAUDE.md (the layer-selection frame), operator-conventions.md (the habits), and enforcement.md (worked escalation examples) — point here rather than restating it.

The four converging rules

Distilled from a multi-author synthesis of the “CLAUDE.md patterns” corpus:

  1. Short beats long. A shorter always-loaded file is read; a long one is skimmed and then ignored — by both the operator and the model. Length erodes the authority of every rule in the file.
  2. Nested beats one giant file. Scope knowledge to where it applies (see the layering model) instead of piling it into one root file.
  3. State, not a prompt. The root file is repo memory — why / map / rules / workflows — not a personality or a coding-philosophy essay. Anything derivable from the code, git, or the README does not belong in it.
  4. Spend the first dollar of leverage. Optimize the highest-frequency, always-paid surface first (root file size, enabled-plugin count) before micro-tuning rarely-loaded layers.

Anti-patterns to keep out of the always-loaded surface: coding philosophy, architecture summaries (→ README), generic style guidance, codebase-derivable facts, and anything that contradicts another file.

The layering model — where each thing lands

Pick the cheapest layer that still fires when needed. The always-loaded root is the most expensive layer; use it only for what must shape every task.

Content typeLands inLoads
Frame-shaping, cross-cutting rulesroot CLAUDE.md (kept lean)always
Domain-local dev conventionsnested subtree CLAUDE.mdonly when working in that subtree
Cross-cutting operational (“when stuck”) rulesa load-on-trigger skill / docs/internals/ playbookon the symptom
Reference detaildocs/ via a pointeron demand
Safety-critical rulesa structural hook / pre-commit / pre-push gateenforced, not remembered
Durable facts (memory)compounded into a searchable substrate; slim indexon query

The nesting trap (the rule that reshapes rule 2)

A subtree CLAUDE.md loads only when the model is working inside that subtree. That makes nesting exactly right for domain-local dev conventions (e.g. Jira-CLI invocation rules under scripts/jira/) — and exactly wrong for cross-cutting operational rules. Push a rule that applies to every session (a shell-command-shape rule, a “prefer X over Y” routing rule) into a subtree and it stops firing the moment you are editing elsewhere.

So:

  • Domain-local → subtree CLAUDE.md. Fires where it’s relevant, costs nothing elsewhere.
  • Cross-cutting operational → load-on-trigger skill, not a subtree. Fires on the symptom (a denial, a friction point) regardless of cwd. This is why operational “when stuck” detail is kept out of both the root file and the subtree files.
  • Cross-cutting frame-shaping → the root file, kept short.
  • Safety-critical → a structural gate. Prose does not enforce; on the second drift of an instructional rule, escalate it to a hook/gate rather than writing stronger prose.

Memory as a map, not a backend

The same principle applies to durable facts. The always-loaded memory index is the map — one line per fact, pulled by relevance — while the facts themselves live in a searchable substrate and are retrieved by query. When the index nears its load budget, compound the durable facts out into the substrate and slim the index (himmel does this via the memory-compound skill); the index never becomes the store.

Three guards keep retrieval honest:

  • Curate the map. Pure semantic retrieval structurally forgets — an un-curated index silently drops what it can’t surface. The index must be maintained, not dumped into.
  • Mark staleness. A recalled fact reflects what was true when written. The memory layer supports optional confidence + verified: markers (defined in operator-conventions.md); the invariant — required with or without those markers — is that when a recalled fact is found stale, you re-check and update it (or delete it), never act on a stale claim silently.
  • Triage by shape. Eviction to the substrate works for fact-shaped memories (looked up on a symptom). It fails for directive-shaped ones — the model does not query for a rule it does not know it is violating, so a directive behind a query is dead. Directives stay resident, or become structural gates The measured failure this corrects: a 1,472-char index line chaining ~24 gotchas at ~61 chars each is the store — the exact thing this section forbids. The doctrine was right; the implementation had drifted from it.

Layer selection — lean-invoke vs default

When adding a rule or capability, pick the cheapest layer. Default to lean-invoke (the operator runs a slash command on demand) UNLESS a trigger applies:

  • Safety-critical → default-hook (PreToolUse / pre-commit / pre-push). The cost of forgetting to invoke manually beats the cost of always running. E.g. block-edit-on-main, block-read-secrets, gitleaks.
  • Frame-shaping (changes how Claude reads the whole task) → default-rule (root CLAUDE.md). E.g. “PRs require approval”, conventional commits, “prefer plugin over MCP”.
  • High frequency × low marginal cost → default-rule + installed skill. E.g. /handover, /clean, /worktree.
  • Eval-shaped (“read X, decide, write ADR”, no ship-code) → defer: file a timeboxed ticket, close Won’t Do on expiry. Do NOT install as always-on.

Default-everything is the failure mode — more always-on rules without a trigger above creates drift: the file grows, both operator and Claude stop reading it, rules lose authority. Lean-invoke keeps the cost on the operator’s side, which is the right side: the operator knows when a rule applies; Claude does not.

Enforcement strength — structural > instructional

Track the drift count per instructional rule. First drift is signal; on the second drift escalate to structural (PreToolUse hook, pre-commit/pre-push gate, classifier, dispatcher guard) — don’t wait for the third, by then the rule has lost authority and Claude is rationalising bypasses. Prose does not enforce. default-rule is a fine first layer, but its next layer after drift is structural, not “stronger prose.”

Worked escalation examples (MCP-jira, headless-billing, edit-on-main, secrets-reads) + the per-layer example set: enforcement.md.

Where the canonical pieces live

  • The habits (no operational rules in the root, verify universal claims, generic example names, specs live in the state repo, memory staleness frontmatter) → operator-conventions.md.
  • Worked escalation examples (an instructional rule hardening into a structural gate) → enforcement.md.
  • The always-on enforcement surface (hooks + gates) → enforcement.md.
  • Research, comparisons and “decided not to adopt” records (not shipped behaviour, so not in docs/; check-docs-decision-records.sh flags a new one) → the state repo at handovers/<USER_SLUG>/himmel/specs/research/ and specs/decisions/, relative to the handover root and indexed by the qmd collection luna. Search it before re-adopting a tool you think was already evaluated.

Maintenance — the surface re-accretes

The lean surface is not self-sustaining: rules pile back onto the root file over time. Treat root-file growth as debt. Before adding anything to the always-loaded surface, ask which row of the layering table it belongs to — the answer is rarely the root file. When reference detail already exists in a docs/ file, the root should carry a pointer, not a copy.

Rule tags — enforced vs prose

Every rule line (a column-0 -/* bullet) of CLAUDE.md (outside the upstream-owned ## graphify section), docs/handover/worker-preface.md and docs/handover/chief-template.md ends with a tag on its last line: [enforced: <name>] when a hook or gate backs it, else [prose]. A [prose] rule is the escalation backlog: its second drift moves it to a hook, gate or classifier. For an enforced rule the file keeps a one-line pointer; the detail lives in the hook’s own header.

  • bash scripts/lint/list-enforced-rules.sh prints the enforced-rule table (name, event, matcher) generated from the hook config and .pre-commit-config.yaml.
  • bash scripts/lint/check-rule-tags.sh fails on an untagged rule line or an [enforced: X] whose X is not a row of that table. Not yet in pre-commit.