Design space for shipped-content namespacing
Scratch space enumerating the axes of the decision. Not a recommendation — input for one.
Axis 1: Namespacing shape
| Option | Shape | Pro | Con |
|---|---|---|---|
| A. No change | kb/notes/, kb/reference/, kb/instructions/ shipped verbatim |
Zero churn | User/library collision; no isolation; current state |
| B. Marker file, same paths | Same paths as A, plus .commonplace marker at each shipped root |
Minimal churn; provenance queryable | Doesn't solve collision; users still share our namespace |
| C. File-level prefix | cp-distillation-is-transformation.md etc., still in kb/notes/ |
Visible provenance; no directory changes | Rewrites every internal link once; filename noise; doesn't isolate COLLECTION.md or types |
| D. Directory prefix per collection | kb/cp-notes/, kb/cp-reference/, kb/cp-instructions/ |
Preserves flat collection structure; peer-siblings with user collections; shorter paths | Six top-level dirs; weaker isolation signal than a boundary directory |
| E. Single namespace directory | kb/commonplace/{notes,reference,instructions}/ |
One boundary to reason about; natural home for a .commonplace marker; scales to future additions |
Deeper paths in shipped content |
| F. Separate vault | kb-commonplace/ alongside kb/ |
Strongest isolation | Two vault roots; most skill/config rework |
Decision (2026-04-23): Option E — kb/commonplace/{notes,reference,instructions,agent-memory-systems}/. Reversed from the earlier D lean after the path audit (path-audit-option-e.md) showed E has strictly lower translation burden than D: sibling-relative links (../notes/..., ../reference/...) are invariant under E's nested-namespace wrapping, but would all need rewriting to ../cp-notes/... etc. under D. E preserves more existing links intact and the namespace directory gives a single boundary to reason about for read-only enforcement.
Axis 2: Read-only convention
| Option | Mechanism | Pro | Con |
|---|---|---|---|
| R1. Convention only | README at kb/commonplace/ says "don't edit" |
Zero enforcement cost | Easy to violate |
| R2. Marker file | .commonplace at shipped root; commonplace-init checks it before overwrite |
Detects modification; enables clean re-sync | User must understand the marker |
| R3. Checksum manifest | kb/commonplace/.manifest with file hashes; init detects drift |
Exact drift detection | Manifest maintenance cost; noisy on intentional local edits |
| R4. File permissions | Chmod readonly at scaffold time | OS-level enforcement | Bad UX on grep/search; cross-platform fragility; doesn't survive git |
| R5. Git submodule | kb/commonplace/ is a submodule pinned to a commonplace release |
Strongest isolation; versioned; re-sync is git pull in submodule |
Submodule UX friction; requires users to handle submodule init |
Decision (2026-04-23): R2 — marker file. A .commonplace marker at kb/commonplace/ root. commonplace-init writes it on fresh install; on subsequent runs, init checks that the shipped tree matches the marker's recorded version before overwriting. If modifications are detected, init refuses to overwrite and prints a diff summary asking the user to confirm. No filesystem permissions, no git submodule — users who want stronger isolation can add that later.
Axis 3: Path translation at ship time
Our source paths reference kb/notes/..., kb/reference/..., kb/instructions/.... In a user's install, shipped content lives at kb/cp-notes/... (Option D) or kb/commonplace/notes/... (Option E). Not every reference needs translation, though — the 85-reference raw count splits into two populations with very different translation needs:
Category 1: generic "the collection you're operating on" references. Instructions that describe collection-generic behavior: "documents live in a kb/ collection such as kb/notes/", "determine the source collection from the path (kb/notes/... → notes)", "read kb/<collection>/COLLECTION.md". In a user install, these resolve correctly to the user's own collections. No translation needed — this is precisely the dogfooding win from authoring in the user's working position. Probably the majority of the 85 references.
Category 2: references to specific shipped artifacts. A specific ADR number (kb/reference/adr/010-review-state...), a specific shipped instruction file (kb/instructions/run-review-bundle-on-note.md), shipped infrastructure (kb/instructions/review-gates/{lens}/...). These point at files that live at the shipped path post-install. These do need translation.
The exact Category-1 / Category-2 split requires an audit of the 85 references. First-sample readings suggest Category 1 dominates, which significantly reduces the translation burden compared to the earlier "rewrite everything" framing.
| Option | Mechanism | Pro | Con |
|---|---|---|---|
| T1. Rewrite at ship time | Package step translates kb/notes/ → kb/commonplace/notes/ in all shipped text |
Source stays user-shaped; ship is mechanical | Translation step is code that can break; source-vs-ship divergence is a correctness gap |
| T2. Rewrite paths in source | Our own instructions reference kb/commonplace/notes/... — but we don't have that directory in our repo |
Source = ship | Our own links are broken in our repo |
| T3. Path variables | Instructions reference <cp-root>/notes/...; skills resolve at runtime |
Source = ship; no text rewriting | Every skill must understand the variable; tools like grep and "click to open file" break |
| T4. Symlink in source | Our repo has kb/commonplace symlinked to kb/ — source paths resolve through the symlink |
Source = ship | Symlinks on Windows; doubles grep hits; fragile |
| T5. Dogfooding mirror | Our repo maintains kb/commonplace/ as a duplicate-for-testing |
Lets us test shipped instructions in our tree | Maintenance cost; contradicts user's "dogfood as a user" framing |
Decision (2026-04-23): T1, scoped narrowly. Translation step exists but is small. Concrete translation operations:
- Rewrite Pattern E (long-relative
../kb/...) and Pattern F (absolute URL[...](kb/...)) links to Pattern D (sibling-relative../notes/..., etc.) — one-time pass across shipped content. - Migrate B2 frontmatter (28 pointers) from absolute to file-relative (
./types/...or../types/...) — one-time pass. - Update
AGENTS.md.template's three specific library references tokb/commonplace/.... - Move shipped trees under
kb/commonplace/inSCAFFOLD_TREES.
Accept: cosmetic drift in Pattern C (code-formatted prose paths like "kb/instructions/run-review-bundle-on-note.md" — small population, rewrite display text at ship time if it becomes a support issue).
Axis 4: Skill root resolution
Skills today assume a single root (the project root) and look under kb/notes/, kb/reference/, kb/instructions/ relative to it. Post-shipping, they need to know about two collection roots.
| Option | Mechanism | Pro | Con |
|---|---|---|---|
| K1. Hardcoded fallback | Each skill checks the library path if target missing in user path | No config | Scattered logic across skills; brittle |
| K2. Config file | kb/.commonplace-config.yaml names library roots |
Centralized; explicit | New config surface; must be maintained |
| K3. Env variable | $COMMONPLACE_LIBRARY_ROOT |
Runtime configurable | Silent if unset |
| K4a. Presence check on single dir | Always look for kb/commonplace/ alongside user collections; if present, library root. Else single-root. |
No config; zero-setup | Directory-name lock-in for the namespace |
| K4b. Prefix-glob convention | Library collections are kb/cp-*/; user collections are kb/*/ minus prefix matches. Glob at skill init. |
No config; no central dir | Prefix lock-in; every skill needs the glob logic |
Decision (2026-04-23): K4a. Skills look for kb/commonplace/ alongside user collections. If present, it's the library root — skills include it when scanning for link targets, loading type specs, etc. If absent, single-root mode. No config file. Follows from Axis-1 Option E.
Axis 5: COLLECTION.md duplication
Each collection has a COLLECTION.md (register, title conventions, link vocabulary, types). Post-shipping there are six possible ones:
kb/notes/COLLECTION.md(user's own)kb/reference/COLLECTION.md(user's own)kb/instructions/COLLECTION.md(user's own)kb/commonplace/notes/COLLECTION.md(shipped)kb/commonplace/reference/COLLECTION.md(shipped)kb/commonplace/instructions/COLLECTION.md(shipped)
User collections need their own because the register may differ. Shipped collections need theirs because skills that read shipped content (e.g. cp-skill-connect as a link-target scan) need the register to interpret candidates correctly.
| Option | Scaffolding behavior | Pro | Con |
|---|---|---|---|
| C1. No scaffold | User's kb/notes/ has no COLLECTION.md; user writes from scratch |
Clean start; no implied register | User has no starting template; write skills fail-fast until authored |
| C2. Minimal template | commonplace-init scaffolds a minimal COLLECTION.md with placeholders for register choice |
Discoverable; guides the user | Encodes choices we may want the user to make consciously |
| C3. Copy shipped | Scaffold copies kb/commonplace/notes/COLLECTION.md to kb/notes/COLLECTION.md as a default |
Immediate working state | User inherits our register by accident; strong default that's hard to notice |
Decision (2026-04-23): C2 — minimal template. commonplace-init scaffolds a minimal COLLECTION.md into each user collection (kb/notes/, kb/reference/, kb/instructions/) with explicit register prompts ("theoretical / descriptive / prescriptive — pick one") and placeholder sections. User fills in title conventions, outbound link rules, and type offerings. Shipped kb/commonplace/*/COLLECTION.md remain authoritative for the library itself.
Axis 6: Source-vs-ship divergence cost
If our repo keeps kb/notes/ and users receive kb/commonplace/notes/, there are paths where divergence matters:
- Testing shipped instructions in our repo. We can't simply run a shipped skill in our repo and expect it to find
kb/commonplace/notes/COLLECTION.mdbecause we don't have that directory. - Documentation embedded in shipped artifacts. An instruction that says "see
kb/notes/COLLECTION.mdfor register rules" must be translated tokb/commonplace/notes/COLLECTION.mdat ship time. - Link stability. Internal links within the shipped tree (note-to-note) stay relative and survive unchanged. Cross-collection links within shipped content need translation.
Mitigations:
commonplace-ship-previewthat packages the tree into a temp dir with translations applied, so shipped instructions can be tested against a realistic user tree- A linting rule in the build pipeline: no hardcoded
kb/notes/in shipped instructions — they must go through the translation step - A manifest of translation rules checked into the repo so the translation logic is auditable
Current working hypothesis: accept the divergence; add commonplace-ship-preview as a validation tool. The translation rules are simple enough to be mechanical; the source-path convention in our repo stays user-shaped.
Prefix (Option D) vs. namespace directory (Option E): deep comparison
This is the central open question. The two options have similar implementation cost and the same translation burden; the difference is structural positioning and the affordances it creates. Captured here so the argument survives the chat that produced it.
Arguments for the cp- prefix (Option D)
- Preserves flat collection structure.
kb/stays "a directory of collections" rather than gaining a nested grouping layer. The user'skb/notes/and the shippedkb/cp-notes/are structural peers at the same depth. - Copy-as-example is frictionless. A user comparing conventions opens
kb/cp-notes/COLLECTION.mdright next to their own — same depth, side-by-side. This matters because shipped collections are the best worked examples of each register (theoretical, descriptive, prescriptive) we have. - Shorter shipped paths.
kb/cp-notes/definitions/x.mdvs.kb/commonplace/notes/definitions/x.md— one fewer segment in the references that do need translation (Category 2 — specific shipped artifacts; see Axis 3). - Scales to multi-source libraries. If a third party publishes
foo-commonplace, their collections ship askb/foo-notes/,kb/foo-reference/. No convention to renegotiate. The namespace-directory approach (Option E) implicitly claimskb/commonplace/as "the library root" — harder to extend without a second-level namespace convention (kb/commonplace/foo/...?). - Simpler skill root resolution.
glob("kb/cp-*/")identifies the library; everything else is user. Same depth for both, name pattern only. - No artificial grouping layer. With Option E,
kb/commonplace/contains collections but is not itself a collection. That introduces a new category of directory ("a collection-of-collections") that the KB otherwise doesn't have.
Arguments against the cp- prefix (Option D)
- No single boundary to operate on. "Re-sync the library," "what's shipped?", "remove the library" — all of these refer to a globbed pattern (
kb/cp-*) rather than one directory. Option E gives a single filesystem object to reason about. - Weaker isolation signal. A filename convention is easier to violate than a directory boundary. A user is more likely to hand-edit
kb/cp-notes/foo.md("it's just in my kb") thankb/commonplace/notes/foo.md(where every path announces "not yours"). - "Examples to copy" cuts both ways. The same proximity that makes the library easy to learn from invites in-place editing — the exact failure mode we want to prevent. Option E makes "copy out before modifying" more structurally obvious.
- Marker-file placement gets awkward. With Option D there is no single "library root" to put a
.commonplacemarker at; either three markers (one per prefixed collection) or a top-levelkb/.commonplace-manifestlisting prefix patterns. Option E has an obvious single marker location. - Top-level dir count doubles. User sees
kb/notes/,kb/reference/,kb/instructions/,kb/cp-notes/,kb/cp-reference/,kb/cp-instructions/— six instead of three or four.ls kb/gets busier. - Prefix lock-in. The
cp-prefix becomes part of the skill/instruction contract. Changing it later means rewriting everything that resolves library roots. (Option E has the same cost forkb/commonplace/→ anything else.)
What's the same in both options
- Translation burden. Both benefit from the dogfooding win: Category-1 "generic collection" references resolve correctly to the user's collection without translation. Both need translation for Category-2 specific-shipped-artifact references. The scope is the same for both options.
- Source-vs-ship divergence. In either case, our repo works with
kb/notes/and the user seeskb/cp-notes/orkb/commonplace/notes/. Thecommonplace-ship-previewmitigation applies to both. COLLECTION.mdduplication. Both options put shipped and userCOLLECTION.mdfiles in distinct locations (the prefix or namespace-dir handles the isolation).- Read-only convention enforcement. Both rely on convention + a marker + an init-time drift check. Neither uses filesystem permissions.
- Type copy-to-extend workflow. Users copy types from the shipped collection into their own
types/directory, in either layout.
Framing the tension
- Option D optimizes for "collections are collections; origin is metadata." Flat, symmetric, easy to learn from by comparison.
- Option E optimizes for "shipped content is a bounded thing with a clear edge." Explicit boundary, single operational unit, stronger provenance signal.
Both are defensible. The choice is about what we want the user's first impression of ls kb/ to convey — "here are a bunch of collections, yours and ours" vs. "here are your collections and, over there, the library."
Final disposition: Option E
After the path audit (path-audit-option-e.md), the initial lean toward D reversed. Reason: E has strictly lower translation cost.
- Sibling-relative links (
../notes/...,../reference/...,../agent-memory-systems/...) are invariant under E's wrapping becausekb/commonplace/{notes,reference,...}preserves the sibling relationship. Under D, every one of those links would need../notes/→../cp-notes/. - File-relative type pointers (B2b, 28 sites) work invariantly under E with one type-resolver extension.
- Global type pointers (B1, 270 sites) stay absolute
kb/types/...and work under both options. - The namespace directory gives a single boundary for the
.commonplacemarker and read-only convention.
The abstract argument for D (flat collection structure, examples-as-peers) didn't survive contact with the concrete path-invariance math. E ships cheaper and stays cleaner.