@treeseed/dsh-chapters 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/LICENSE +14 -0
  2. package/README.md +644 -0
  3. package/cordis.patch.yml +88 -0
  4. package/docs/architecture.md +460 -0
  5. package/docs/contract.md +375 -0
  6. package/docs/development.md +437 -0
  7. package/docs/host-compaction-seam.md +117 -0
  8. package/docs/knowledge-repo.md +730 -0
  9. package/docs/provider.md +98 -0
  10. package/docs/verify.md +200 -0
  11. package/lib/archive.d.ts +88 -0
  12. package/lib/archive.js +114 -0
  13. package/lib/archive.js.map +1 -0
  14. package/lib/arrival.d.ts +23 -0
  15. package/lib/arrival.js +86 -0
  16. package/lib/arrival.js.map +1 -0
  17. package/lib/artifact-query.d.ts +53 -0
  18. package/lib/artifact-query.js +130 -0
  19. package/lib/artifact-query.js.map +1 -0
  20. package/lib/client.js +234 -0
  21. package/lib/client.js.map +1 -0
  22. package/lib/commands.d.ts +28 -0
  23. package/lib/commands.js +305 -0
  24. package/lib/commands.js.map +1 -0
  25. package/lib/compose.d.ts +30 -0
  26. package/lib/compose.js +131 -0
  27. package/lib/compose.js.map +1 -0
  28. package/lib/continue-core.d.ts +153 -0
  29. package/lib/continue-core.js +252 -0
  30. package/lib/continue-core.js.map +1 -0
  31. package/lib/engine-core.d.ts +219 -0
  32. package/lib/engine-core.js +405 -0
  33. package/lib/engine-core.js.map +1 -0
  34. package/lib/engine.d.ts +211 -0
  35. package/lib/engine.js +571 -0
  36. package/lib/engine.js.map +1 -0
  37. package/lib/enrich-queue.d.ts +66 -0
  38. package/lib/enrich-queue.js +100 -0
  39. package/lib/enrich-queue.js.map +1 -0
  40. package/lib/enrich-store.d.ts +31 -0
  41. package/lib/enrich-store.js +103 -0
  42. package/lib/enrich-store.js.map +1 -0
  43. package/lib/enrich-wire.d.ts +73 -0
  44. package/lib/enrich-wire.js +209 -0
  45. package/lib/enrich-wire.js.map +1 -0
  46. package/lib/enrich.d.ts +73 -0
  47. package/lib/enrich.js +150 -0
  48. package/lib/enrich.js.map +1 -0
  49. package/lib/gitops.d.ts +107 -0
  50. package/lib/gitops.js +539 -0
  51. package/lib/gitops.js.map +1 -0
  52. package/lib/index.d.ts +81 -0
  53. package/lib/index.js +305 -0
  54. package/lib/index.js.map +1 -0
  55. package/lib/indexing.d.ts +85 -0
  56. package/lib/indexing.js +212 -0
  57. package/lib/indexing.js.map +1 -0
  58. package/lib/injections.d.ts +66 -0
  59. package/lib/injections.js +83 -0
  60. package/lib/injections.js.map +1 -0
  61. package/lib/notice.d.ts +60 -0
  62. package/lib/notice.js +84 -0
  63. package/lib/notice.js.map +1 -0
  64. package/lib/provider.d.ts +44 -0
  65. package/lib/provider.js +66 -0
  66. package/lib/provider.js.map +1 -0
  67. package/lib/redact.d.ts +24 -0
  68. package/lib/redact.js +85 -0
  69. package/lib/redact.js.map +1 -0
  70. package/lib/registry.d.ts +120 -0
  71. package/lib/registry.js +130 -0
  72. package/lib/registry.js.map +1 -0
  73. package/lib/render.d.ts +40 -0
  74. package/lib/render.js +280 -0
  75. package/lib/render.js.map +1 -0
  76. package/lib/repo.d.ts +78 -0
  77. package/lib/repo.js +184 -0
  78. package/lib/repo.js.map +1 -0
  79. package/lib/rules-commands.d.ts +24 -0
  80. package/lib/rules-commands.js +138 -0
  81. package/lib/rules-commands.js.map +1 -0
  82. package/lib/rules.d.ts +90 -0
  83. package/lib/rules.js +185 -0
  84. package/lib/rules.js.map +1 -0
  85. package/lib/search.d.ts +48 -0
  86. package/lib/search.js +152 -0
  87. package/lib/search.js.map +1 -0
  88. package/lib/signature.d.ts +28 -0
  89. package/lib/signature.js +149 -0
  90. package/lib/signature.js.map +1 -0
  91. package/lib/store.d.ts +130 -0
  92. package/lib/store.js +235 -0
  93. package/lib/store.js.map +1 -0
  94. package/lib/sync.d.ts +183 -0
  95. package/lib/sync.js +719 -0
  96. package/lib/sync.js.map +1 -0
  97. package/lib/tools.d.ts +84 -0
  98. package/lib/tools.js +581 -0
  99. package/lib/tools.js.map +1 -0
  100. package/lib/treedx/client.d.ts +57 -0
  101. package/lib/treedx/client.js +82 -0
  102. package/lib/treedx/client.js.map +1 -0
  103. package/lib/treedx/provider.d.ts +39 -0
  104. package/lib/treedx/provider.js +479 -0
  105. package/lib/treedx/provider.js.map +1 -0
  106. package/lib/treedx/state.d.ts +27 -0
  107. package/lib/treedx/state.js +65 -0
  108. package/lib/treedx/state.js.map +1 -0
  109. package/lib/types.d.ts +100 -0
  110. package/lib/types.js +19 -0
  111. package/lib/types.js.map +1 -0
  112. package/lib/vocabulary.d.ts +41 -0
  113. package/lib/vocabulary.js +118 -0
  114. package/lib/vocabulary.js.map +1 -0
  115. package/package.json +132 -0
  116. package/presets/chapters/agent.cordis.yml +272 -0
  117. package/presets/chapters/preset.yml +3 -0
  118. package/presets/probe-roots/agent.cordis.yml +11 -0
  119. package/presets/probe-roots/preset.yml +3 -0
@@ -0,0 +1,730 @@
1
+ # Knowledge Repository Design Record
2
+
3
+ The governing design for the **git-based shared knowledge pool**: multiple DSH harness instances,
4
+ on multiple machines, feeding and searching one private per-project repository of past
5
+ conversations — chapters, artifacts, a topic index, and rules — while keeping every individual
6
+ session's context small. Written to *govern*: implementation plans derive from this file, and
7
+ amendments are human decisions recorded in git history (see **Amendments** below).
8
+
9
+ Status: **agreed 2026-09-17, pre-implementation.** Phased build in §13.
10
+
11
+ ## 0. What this is, and what it is not
12
+
13
+ **What it is:** the plugin's existing artifacts (chapters, deferred tool results, the TOC
14
+ continuation mechanic) become the contents of a private repository per project, synced across
15
+ every harness the user connects to it — over **pluggable transport**: a git repo (§3, the
16
+ default) or a TreeDX service (`treedx+…`, Amendment 2026-09-21, transport). Compaction and forking remain the
17
+ *knowledge generators*; the repo is the *transport and the shared corpus*. Agents gain one
18
+ new capability: search and load of past work across their own and other machines' sessions,
19
+ plus a bounded set of
20
+ human-approved rules to live by.
21
+
22
+ **What it is not:**
23
+
24
+ - Not a general vector store or embedding search. Scoring is deterministic and inspectable (§8).
25
+ - Not a system-prompt injection channel. Rules enter context only through the continuation
26
+ notice (§7) — per-continuation, budgeted, append-only — never as a contributed header section
27
+ (invariant 3, `AGENTS.md`).
28
+ - Not a rewrite of what shipped. The MVP loop (engine, registry, TOC continuation, fork button)
29
+ is the foundation this record extends; §1 lists the invariants that must survive the extension.
30
+ - Not real-time collaboration. Sync points are deliberate (§5); the repo is a pool, not a shared
31
+ live document.
32
+
33
+ ## 1. Invariants the extension must not erode
34
+
35
+ Carried from `AGENTS.md` and this project's measured history, restated because the knowledge
36
+ layer tempts each one:
37
+
38
+ 1. **The compaction/fork critical path stays zero-model-token.** Model enrichment is strictly
39
+ post-archive, async, optional (§6). A fork must remain instant even when the model is down,
40
+ busy, or slow — the deterministic path is the product; enrichment is garnish.
41
+ 2. **Refuse with numbers, never truncate.** Applies to the rules core set rendered into the
42
+ notice (§7) and to search output packing (§8): over budget → refuse/explain with the
43
+ measured numbers, or the user curates; silent clipping is banned.
44
+ 3. **The local store is append-only.** Chapters are never renumbered, rewritten, or
45
+ re-chaptered after archive. Composition must be right at archive time (§4); enrichment can
46
+ relabel, never re-chapter.
47
+ 4. **Model proposes, plugin commits — extended.** The model proposes topics, summaries, and
48
+ rule text. The plugin owns the canonical vocabulary, the index, and rule status (§6, §7).
49
+ The model never writes to the repo directly; the plugin is the only writer.
50
+ 5. **Per-machine write namespaces.** Two machines never write the same file. Zero merge
51
+ conflicts by construction, not by hope (§3.3).
52
+ 6. **Budget the added context, never the corpus.** Continuation notices stay O(1) in corpus
53
+ size (§7.3). The corpus grows without bound; the head it pays does not.
54
+ 7. **Verbatim stays verbatim** except the redaction carve-out (§9) — and the carve-out is
55
+ credentials-only, marked, and deterministic.
56
+
57
+ ## 2. The knowledge repository
58
+
59
+ ### 2.1 One private repo per project, user-created
60
+
61
+ The user creates a private repository per project and registers it with
62
+ `/chapters-link` (four modes, live): **no args** shows the upstream + mirror state,
63
+ **a local path** binds a directory upstream, **a URL + token** binds a network git
64
+ upstream, **`treedx+<url>/<repo>` + token** binds a TreeDX service (managed repository
65
+ resolved-or-created by the link itself — the explicit human act; §15.1).
66
+ Profile config (`knowledgeRemote`) supplies the same URL as a lazy default; it never
67
+ creates anything by itself. A personal cross-project repo is the *user's* choice of an
68
+ additional project entry, not a built-in mode.
69
+
70
+ Per-harness credentials are **scoped, revocable tokens limited to that repository** —
71
+ deliberately better than SSH deploy keys: revocable, per-repo, no shell access.
72
+ **Credentials are mandatory for network upstreams** (loopback exempt, so local test
73
+ forges work) and **never needed for local-path upstreams**. Storage is under the DSH
74
+ home — `<DSH_HOME>/dsh-chapters/credentials/<projectKey>`, 0600 — never the project
75
+ tree: the workspace is the agent's reading room and only the plugin touches the token
76
+ (a same-uid hygiene boundary, not a cryptographic one; the hard guarantee is that no
77
+ code path ever echoes it into a context).
78
+
79
+ Why per-project: natural secret boundary (§9), natural permission boundary, and the
80
+ `projectKey` (§2.3) is what keeps a small model's session from wading into a foreign corpus by
81
+ accident.
82
+
83
+ ### 2.2 Repository layout
84
+
85
+ ```
86
+ <knowledge-repo>/
87
+ project.yml # projectKey, slug, created-by, created-at (plugin-maintained)
88
+ chapters/
89
+ <projectKey>/
90
+ <sessionId>/
91
+ <chapter-file>.md # frontmatter + verbatim body (today's format, §2.4)
92
+ artifacts/
93
+ <projectKey>/
94
+ <sha256-prefix>/<sha256> # deferred tool results, content-addressed (today's format)
95
+ collections/
96
+ <projectKey>/
97
+ <sessionId>.jsonl # per-turn signatures, one line per completed turn (§4.1)
98
+ index/ # LAYER 2 — derived, plugin-built, do not hand-edit (§10.2)
99
+ <topic-shard>.md # one shard per canonical topic, alphabetical
100
+ manifest.json # build manifest: file hash → last processed state (§10.2)
101
+ edits/
102
+ <harness-id>/
103
+ curation.jsonl # LAYER 1 — per-machine editable index facts (§10.1)
104
+ rules/
105
+ <projectKey>/
106
+ <rule-file>.md # rules-as-chapters: kind: rule (§7)
107
+ topics/
108
+ vocabulary.json # plugin-owned canonical vocabulary + aliases (§6.3)
109
+ ```
110
+
111
+ Machine-specific and derived state lives in machine-namespaced or manifest-tracked files (§10);
112
+ human-meaningful content (chapters, rules, vocabulary) is globally readable and git-diffable so
113
+ the user can see what any harness decided and revert it.
114
+
115
+ ### 2.3 `projectKey`: UUIDv5 over the normalized remote
116
+
117
+ The key must be **identical on every machine** for the same project — that is the whole point,
118
+ since local paths differ (`/Users/adrian/repo` vs `/home/adrian/Projects/repo`).
119
+
120
+ Canonicalization, exact:
121
+
122
+ 1. Take the project's git remote URL (`origin`, first if several).
123
+ 2. Lowercase.
124
+ 3. Normalize form: strip `https://` and `ssh://` schemes; convert `git@host:org/repo` →
125
+ `host/org/repo`; strip trailing `/` and `.git`.
126
+ 4. **Keep the host.** Two hosts, same path = different projects.
127
+ 5. `projectKey = UUIDv5(NAMESPACE_OID, canonical-string)` — standards-based, deterministic,
128
+ name-derived; the same canonical string always yields the same key, anywhere.
129
+
130
+ Fallbacks, all recorded in `project.yml` and the notice so they are visible:
131
+
132
+ - **Non-git workspace:** UUIDv5 over the absolute path, flagged `path-derived` (unstable across
133
+ machines — the notice says so).
134
+ - **User override:** a `projectKey:` line in config wins, for monorepo subdirectories and
135
+ multi-remote situations.
136
+
137
+ ### 2.4 Chapter frontmatter (extended)
138
+
139
+ Today's frontmatter (title, summary, ranges, sha, …) gains:
140
+
141
+ ```yaml
142
+ projectKey: 1c3f… # §2.3 — every chapter carries its project
143
+ kind: chapter # chapter | rule (§7)
144
+ harnessId: adrian-mbp # writing machine, human-readable
145
+ topics: [auth, middleware] # deterministic signature topics at archive time (§4);
146
+ # model-enrichment may later add semantic topics
147
+ generated: # provenance per field class (user-confirmed, §6)
148
+ title: {by: deterministic}
149
+ summary: {by: model, model: qwen3.8-flash-next, at: 2026-09-17T…} # or {by: deterministic}
150
+ topics: {by: deterministic}
151
+ ```
152
+
153
+ Provenance is load-bearing: it is what makes the emergent vocabulary auditable and enrichment
154
+ re-runnable (§6).
155
+
156
+ ## 3. Storage vs transport
157
+
158
+ ### 3.1 The store stays in the workspace; the clone is a mirror
159
+
160
+ Chapters must remain reachable by the existing `read` tool — a property proven load-bearing in
161
+ the r22 read-back test, and one the agent sandbox may gate for outside-workspace paths.
162
+ Therefore:
163
+
164
+ - **Storage** stays exactly where it is: `.dsh-chapters/<rootSessionId>/…` in the workspace.
165
+ - **Transport** is a git clone in the same workspace: `.dsh-knowledge/` (hidden; the plugin
166
+ writes/updates a gitignore hint in the project on first sync so the project's own repo
167
+ ignores it).
168
+ - Sync = copy-out (store → clone, commit, push) and copy-in (clone → store, new files only).
169
+ The clone is *transport*, never the read path's source of truth for the live session.
170
+
171
+ Failure domains stay separate: a broken remote never breaks compaction/fork (sync is
172
+ best-effort, §5.3), and a broken clone never corrupts the store.
173
+
174
+ ### 3.2 Git client: pure-JS SDK, not the executable
175
+
176
+ **Decision (user-directed):** no dependency on a `git` binary. A pure-JS client
177
+ (isomorphic-git-class) over **HTTPS** — which is what makes the scoped-token credential model
178
+ of §2.1 possible. Logged risks: pure-JS fetch/push is slower than native git (mitigation: the
179
+ knowledge repo is small relative to source repos; lazy-clone on first use); the repo grows a
180
+ dependency to own. Fallback, config-gated and documented: the CLI, for hosts where the SDK
181
+ chokes. SSH-only remotes are a known gap in this decision (HTTPS tokens are the supported path).
182
+
183
+ **Local-path upstreams bypass HTTP entirely** (`/chapters-link <path>`, added with the
184
+ P1 close-out): the pool is a directory (bare repo created on first sync if absent) and
185
+ transfer is isomorphic-git's own pack layer — `packObjects` fed an **explicitly computed
186
+ reachability closure** (commits → trees → blobs; packObjects packs exactly what it is
187
+ given, measured) written straight into the target's `objects/pack/` and `indexPack`ed,
188
+ then refs moved under the same ff-only rule. Zero network, zero credentials, still zero
189
+ git-binary. Two machines converging through one shared path — including a real fork
190
+ resolved by mirror rebuild — is covered by `tests/integration/sync-local-upstream.test.ts`.
191
+
192
+ **§3.1/§3.2 are the git provider's rules.** Since the 2026-09-21 transport amendment the transport behind them
193
+ is one implementation of a six-verb seam (`src/provider.ts`); the TreeDX service provider
194
+ keeps §3.1's store/mirror split verbatim (the mirror is provider-materialized content, not a
195
+ clone) and §5.2's rebuild-on-divergence verbatim. The per-verb mapping, failure classes, and
196
+ mirror state file are specified in [docs/provider.md](provider.md); API facts and the
197
+ [LIVE-GATED] list live in [spikes/treedx/FINDINGS.md](../spikes/treedx/FINDINGS.md).
198
+
199
+ ### 3.3 The no-conflict rule
200
+
201
+ Two machines never write the same file:
202
+
203
+ - `chapters/`, `artifacts/`, `collections/` are partitioned by **sessionId** (UUID — collision-free)
204
+ under the project key; a session belongs to exactly one machine.
205
+ - `edits/<harness-id>/` is partitioned by **harness id** — one writer per file, ever.
206
+ - `index/`, `topics/vocabulary.json`, `project.yml` are **plugin-derived, single-consumer
207
+ writes**: built by the syncing machine from append-only inputs. If a pull ever finds a
208
+ divergent derived file, the plugin **rebuilds it locally and commits the rebuild** — never
209
+ merge-resolves it (§10.2). Merge conflicts are structurally impossible for content and
210
+ structurally repaired for derived state.
211
+
212
+ ## 4. Chapter composition: topic-sequential, deterministic
213
+
214
+ The user requirement: **consecutive message collections that reference the same task merge into
215
+ one chapter; a chapter splits only when the task changes or the chapter outgrows a configured
216
+ limit.** This replaces size-only splitting as the primary axis and is the load-bearing new
217
+ algorithm of the whole record.
218
+
219
+ ### 4.1 Collection and signature
220
+
221
+ - **Collection = one completed turn**: the user message(s) of the turn, its assistant
222
+ messages, and its `turn/end`. (The "user + first + last assistant" slice is the *enrichment
223
+ input*, §6.1; composition works on the whole collection.)
224
+ - **At `turn/end`, synchronously** (millisecond-scale, no queue), the plugin extracts the
225
+ collection's **deterministic signature** and appends one line to
226
+ `collections/<projectKey>/<sessionId>.jsonl`:
227
+
228
+ ```json
229
+ {"seqs":[41,42,43,44,45],"paths":["src/auth/middleware.ts","src/auth/token.ts"],
230
+ "commands":["npm test","git diff"],"terms":["auth","refresh","jwt","middleware"],
231
+ "size":1830,"by":"deterministic","at":"2026-09-17T…"}
232
+ ```
233
+
234
+ - `paths`: file paths mentioned in tool calls/results (highest-signal source in coding
235
+ sessions).
236
+ - `commands`: command/argv tokens, normalized.
237
+ - `terms`: stopword-filtered keyword frequency, top-N.
238
+ - `size`: estimated tokens (cross-machine heuristic — fine for composition, not for
239
+ display; the TOC shows message counts, not tokens, §7.3, because tokenization is not
240
+ comparable across models).
241
+
242
+ Because the signature is computed *when the turn completes*, **no compaction or fork ever
243
+ needs unprocessed material**: every completed collection is already signed, and an
244
+ unfinished collection (the in-flight turn) is never in scope — the host compacts at
245
+ `agent/pre-step` only, never mid-step (measured r13/r17), and its retained tail always
246
+ contains the current turn. This is the answer to "compaction can happen at any step."
247
+
248
+ ### 4.2 Composition (at archive time)
249
+
250
+ When a compaction or fork archives a span of completed collections, chapters are composed by a
251
+ **greedy sequential merge**, deterministic and O(collections in span):
252
+
253
+ 1. Open chapter 1 at collection 1; accumulate its running signature (union, with
254
+ per-collection weighting).
255
+ 2. For each next collection: **merge** if
256
+ `overlap(collection, running) ≥ τ` **or** `primaryMatch(collection, running)` — **and**
257
+ `chapterSize < chapterLimit` — otherwise **close the chapter and open a new one**
258
+ at this collection.
259
+ 3. `overlap` is a normalized term overlap: shared paths (heavy weight — same file is
260
+ near-probably same task), shared command families, shared terms, over the union.
261
+ `τ` (merge threshold) and `chapterLimit` are config (§12), both in the same
262
+ "no hardcoded tunables" register as `chapterTokenTarget`.
263
+ 4. `primaryMatch`: the first path of either running member (`paths[0]` — the file a
264
+ turn is *about*, by causal order of touch) names the same file as the incoming
265
+ collection's primary, with a spelling tolerance (`src/render` = `src/render.ts`).
266
+ **Amendment r28 (2026-09-18, measured live):** the first real topic test — five
267
+ research turns on this repo — showed overlap ALONE never reaches τ for legitimate
268
+ same-topic pairs: real turns touch 5–6 files, one shared path dilutes in a union
269
+ of ~10 (measured 0.077 vs τ 0.3), while cross-topic pairs score 0.0–0.02. The
270
+ intent of rule 3's "same file is near-probably same task" is thus carried
271
+ explicitly: a shared primary is a first-class merge signal, not a diluted weight.
272
+ τ and the size cap are unchanged; every decision still logs the rule that fired.
273
+
274
+ Boundary, measured live (r29/r36): the host's pressure selection ends at
275
+ tool-pair balance points, blind to turn boundaries — shallow repairs slice
276
+ collections, and the completeness guard then correctly falls back to legacy
277
+ single-chapter fragments. Topic-sequential composition therefore governs the
278
+ turn-aligned archive moments (fork/continue, where the span is anchored at a
279
+ `turn/end`); the engine wiring (r29) is the opportunistic upgrade for deep
280
+ selections (first breach of a heavily-over window), never a fragment
281
+ regression. If engine-path merges ever need to be reliable under steady
282
+ pressure, the fix is cross-chapter collection stitching in the index (P2),
283
+ not selection tuning.
284
+
285
+ Honest quality bar: strong for coding sessions (files and commands leave loud signatures),
286
+ degrading to time/size splitting for pure-prose conversations — never worse than today's
287
+ behavior, never blocking.
288
+
289
+ ### 4.3 The completeness guard
290
+
291
+ Chapters include **only collections with a `turn/end`** inside the span. If a compaction
292
+ boundary ever lands mid-collection (the manual-`/compact` edge case — the automatic path
293
+ cannot produce one, §4.1), the composition clamps to the last completed collection and the
294
+ TOC notes the clamp. The unfinished collection stays in the live context, per the user
295
+ requirement, and composes at the next archive.
296
+
297
+ ### 4.4 Consequence: composition is archive-time-only
298
+
299
+ Because chapters are append-only (invariant 3), the merge threshold must be right *when the
300
+ archive happens*. The per-collection signature is stored with provenance (§4.1) precisely so
301
+ any merge or split is **inspectable after the fact** — and so `τ` can be tuned for future
302
+ archives without pretending to rewrite old ones. Enrichment (§6) may later *relabel* a
303
+ chapter's topics but may never re-chapter it.
304
+
305
+ ## 5. The sync loop
306
+
307
+ ### 5.1 Points
308
+
309
+ | Event | Action |
310
+ |---|---|
311
+ | New chapters-preset session created | **pull** (fresh corpus before the first turn; §5.2 ordering) |
312
+ | Fork (button or command) | **pull → compose/archive → push** |
313
+ | Compaction finalization | **push** (debounced — see 5.2) |
314
+ | Idle (debounce expired after the last archive) | **push** |
315
+ | Pull failure / push failure | log, surface via `/chapters-status`, **never fail the archive** |
316
+ | `/chapters-link <path>` | binds a directory upstream (bare repo materialized on first sync); no credentials |
317
+
318
+ Compaction and fork are simultaneously push points *and* the right refresh points — the loop
319
+ is pull → archive new span → push, so a fork point always sees a current index.
320
+
321
+ ### 5.2 Ordering and serialization
322
+
323
+ - A **per-repo writer lock** serializes (a) store transactions (reserve → write → commit) and
324
+ (b) clone operations. A pull never lands mid-archive-write and a push never ships a
325
+ half-committed store.
326
+ - Push = `copy-out → commit → fetch → if diverged: rebase-apply (append-only ⇒ fast-forward
327
+ safe) → push`, with bounded retry. All git calls run with no TTY prompt (
328
+ `GIT_TERMINAL_PROMPT=0` equivalent in the SDK), hard timeouts, and network treated as
329
+ best-effort.
330
+ **Implementation note (r35, via §15):** divergence is resolved by REBUILDING the mirror,
331
+ not rebasing it — `remove → fresh clone → re-publish from the store → push`. Same
332
+ semantics (the local backlog reaches the remote without force; append-only content merges
333
+ trivially), fewer moving parts, and no rebase primitive needed (isomorphic-git 1.42 has
334
+ none). The rebuild is safe only because the mirror is transport (§3.1) — the workspace
335
+ store is the truth it re-publishes from. Diverged-vs-network is a machine-readable
336
+ failure CODE, not prose matching: a network error's message once contained the word
337
+ "diverge" and falsely triggered this path (caught by the real-HTTP test).
338
+ - Debounce: consecutive archive events within the configured window coalesce into one push.
339
+ The triggers live in both planes: the host plugin pulls before, and schedules after,
340
+ fork/continue archives; the realm engine schedules after compaction finalization and
341
+ pulls at each session's first `turn/start` (the "new session" refresh point, read
342
+ process-scoped). The file lock is what makes the two schedulers safe side by side.
343
+
344
+ ### 5.3 Failure semantics
345
+
346
+ Remote down, token revoked, clone corrupted — each degrades to *local-only mode*: the store
347
+ keeps working, pending sync accumulates, `/chapters-status` says so with numbers. Nothing in
348
+ the conversation path waits on the network.
349
+
350
+ ## 6. Model enrichment: async, provenance-tagged, re-runnable
351
+
352
+ ### 6.1 The pass
353
+
354
+ For each archived chapter, one structured call whose input is the cheap slice the user
355
+ specified — **the user request(s) plus first and last assistant message**, each capped, plus
356
+ the first ~200 tokens of the chapter body for grounding — and whose output is validated JSON:
357
+
358
+ ```json
359
+ {"title": "…one sentence…", "summary": "…short…", "topics": ["…up to 10…"]}
360
+ ```
361
+
362
+ - **Validation before commit:** schema-check; on failure, retry once; on second failure, keep
363
+ the deterministic values (title from the existing derivation, excerpt-labeled summary,
364
+ signature topics). A slow or failing model never blocks or blanks a chapter.
365
+ - **Thinking models:** the pass runs at low reasoning effort and tight `maxTokens`; on the
366
+ local server this is the only model work this feature ever forces, and it is batched,
367
+ below.
368
+ - **Topics are proposals:** they enter the chapter's frontmatter only through the
369
+ canonicalizer (§6.3) — unknown labels pass as `unvetted`.
370
+
371
+ ### 6.2 When it runs: idle-batched, never per-turn
372
+
373
+ **Measured constraint (r29-era):** the local server is **single-slot**
374
+ (`maxConcurrent: 1`). Per-turn model work would queue against the user's own conversation
375
+ turns. Therefore:
376
+
377
+ - **Per turn:** deterministic signature only (§4.1) — free, synchronous, no model.
378
+ - **Model enrichment:** batched at natural idle points — after a compaction/fork push
379
+ completes, or on an idle debounce — processing *archived* chapters only. It can be
380
+ disabled per-harness in config; a harness on a contended machine can run
381
+ signatures-only and still contribute a fully functional corpus.
382
+ - **Re-runnable:** a command re-annotates a chapter (or the whole project) with a newer model
383
+ or corrected vocabulary, overwriting only the model-owned fields (frontmatter provenance
384
+ records each overwrite). The verbatim body is never touched.
385
+
386
+ ### 6.3 Self-bootstrapping vocabulary (no seed list, no hand-editing)
387
+
388
+ Per the user requirement, the vocabulary is **plugin-owned and emergent** — the user neither
389
+ authors a seed list nor edits files:
390
+
391
+ 1. **First runs:** no canonical vocabulary. Topic labels are raw deterministic candidates;
392
+ enrichment topics enter flagged `unvetted`.
393
+ 2. **Merge signals**, watched by the plugin against its own corpus:
394
+ - two labels **co-occur** in the same chapter repeatedly;
395
+ - two labels share **high normalized token overlap** across chapters;
396
+ - **alias patterns** (substring/singular-plural: `auth` ⊂ `authentication`).
397
+ Past threshold, the plugin writes a **curation entry** into `edits/<harness-id>/`
398
+ (Layer 1, §10.1): `author: plugin`, `reason: <signal + counts>`, `merge: {from, to}`.
399
+ 3. The derived index and `topics/vocabulary.json` reflect curation entries on the next build;
400
+ the vocabulary file stays **diffable and git-revertable** — the user can see every
401
+ decision the plugin made, and undo one, without ever having to make one.
402
+ 4. Escape hatch (P3, optional): `/chapters-topic merge <a> <b>` — the same curation entry,
403
+ human-authored.
404
+
405
+ Provenance in frontmatter (§2.4) is what makes all of this auditable: any label on any
406
+ chapter traces to a deterministic pass, a specific model run, or a specific curation entry.
407
+
408
+ ## 7. Rules: chapters of a different kind
409
+
410
+ ### 7.1 Model
411
+
412
+ Rules reuse the chapter machinery end-to-end — same file format, registry, budget preflight,
413
+ search, and sync. Frontmatter: `kind: rule`, plus `category`, `status`, and provenance.
414
+ **Rules are another form of chapter to search when needed** (user's framing, adopted); the
415
+ plugin's modularity is what makes this nearly free — the chapter code paths are parameterized
416
+ by `kind`, not duplicated.
417
+
418
+ ### 7.2 Lifecycle: proposed → core, approved in-session
419
+
420
+ - **Proposal:** a user command (`/chapters-rule add <category> <text>`) or an agent's async
421
+ derivation from a completed session (P3, always `status: proposed`, provenance-tagged with
422
+ the source session).
423
+ - **Review/approve:** entirely in-session DSH commands — the user never leaves the session,
424
+ and **command output never enters the context window** (verified 2026-09-17: command results
425
+ persist as `command/run` / `command/done` event types — a distinct type, not
426
+ user/assistant/tool — which are neither prefilled to the model nor priced by the compaction
427
+ engine; 24 such pairs observed in the r29 session's durable log from the e2e suite):
428
+ - `/chapters-rule list` (`--proposed`, `--category <name>`) — table: id, status, category,
429
+ one-line text, provenance.
430
+ - `/chapters-rule approve <id>` — `proposed → core`.
431
+ - `/chapters-rule revoke <id>` — core back to the file, out of the notice.
432
+ - **`core` is the only tier that renders into the continuation notice** (§7.3).
433
+ **Amended 2026-09-20 (§15):** `approve` does not rewrite the rule file — it appends a
434
+ `rule-status` curation fact to the APPROVING machine's own `edits/<harness>/curation.jsonl`.
435
+ Effective status is per-machine (file otherwise `proposed`); a rule affects no other
436
+ machine's notice until that machine's human approves. Reason: authorship partition — a
437
+ shared-file status would let any machine rewrite another's authored bytes, which r37
438
+ showed is exactly where transport bugs live. The human
439
+ in the loop is the one who types `approve`; the git diff of the rule file is the audit
440
+ trail. This is the trust boundary for shared state (§11.3).
441
+
442
+ ### 7.3 The rules section of the continuation notice
443
+
444
+ The notice carries (all O(1), §1 invariant 6):
445
+
446
+ - **Core rules:** the `status: core` set, rendered verbatim, **size-capped** by the same
447
+ refuse-don't-truncate rule — if core rules exceed their budget, the notice names the
448
+ overflow with numbers and the user curates (`revoke`, or split a category). Never silent
449
+ clipping.
450
+ - **Rules category index:** category names + one-line essence + paths, with the instruction
451
+ *load a rule category when this session's topics overlap a category* — i.e., the agent
452
+ reads the full category from the repo when (and only when) relevant.
453
+ - **Search instructions** for the chapters index (one paragraph, pointing at
454
+ `chapters_search` and the shard paths).
455
+ - **`Project: <slug> · <projectKey>` — prominently near the top** (user requirement): the
456
+ agent always knows which corpus it is in and where searches point.
457
+ - The existing sections (chapters list with single-sentence title, short summary, file link,
458
+ **message count** — not token count, since tokenization is not comparable across models —
459
+ and parent/fork links) unchanged.
460
+
461
+ ### 7.4 Later: topic-matched auto-inclusion
462
+
463
+ When a continuation is created, the plugin can compute topic overlap between the archived
464
+ span's topics and rule categories and inline matching *category-level* rules within the same
465
+ budget. P3b; gated by the same cap-and-refuse rule.
466
+
467
+ ## 8. Search: `chapters_search`
468
+
469
+ ### 8.1 Contract
470
+
471
+ ```
472
+ chapters_search { query: string, maxTokens?: number, topic?: string, crossProject?: boolean }
473
+ → { matches: [...], shown: n, total: m, budget: {requested, used, remaining} }
474
+ ```
475
+
476
+ - `maxTokens` is **the agent's request** (user requirement: the model knows its own window;
477
+ the plugin measures and packs to it, the same way the continuation budget preflight does).
478
+ Default: small (config), overridable upward by the agent.
479
+ - **Deterministic scorer** (no model, reproducible, transparent):
480
+ - term hits on **topic (×2 weight), title, summary, category** (rules: category + text);
481
+ - **recency** boost: exponential decay on chapter date;
482
+ - small bonus for `kind: rule, status: core`.
483
+ - **Output lines:** `score | date | kind | title | path (topics: …)` — packed until
484
+ `maxTokens` is spent, most-relevant first; the response reports `total` so the agent can
485
+ re-request with a larger `maxTokens` to page (transparent, no hidden truncation).
486
+ - **`topic: <name>`** reads an entire index shard (a bounded, alphabetical file).
487
+ - **Default filter: the caller's `projectKey`** — cross-project search is an explicit
488
+ `crossProject: true`, so a small-model session never wanders into a foreign corpus by
489
+ accident.
490
+ - **Zero new trust surface:** every `path` returned is a plain file the existing `read` tool
491
+ opens (chapters in `.dsh-chapters/`, shards in `.dsh-knowledge/index/`). The search tool
492
+ adds a scorer and a formatter; it adds no new way to read.
493
+
494
+ ## 9. Redaction: minimal, deterministic, marked
495
+
496
+ User position, adopted: **credentials only, deterministic patterns only, everything else
497
+ byte-identical** — the character of the conversation is historical value and stays intact.
498
+
499
+ - **Patterns (deterministic):** `AKIA…` (AWS), `sk-…` (OpenAI-shape), `ghp_/gho_/
500
+ github_pat_`, JWT `eyJ…` shape, `Authorization: Bearer …`, `token=…` in URLs, PEM private
501
+ key blocks. Deliberately conservative; new patterns are config additions, not magic.
502
+ - **Marker format:** `⟦redacted:credential sha256=8f3a1c2b⟧` — (a) obviously not a real value
503
+ to any model reading the chapter, (b) **deterministic per secret** (same secret → same
504
+ marker, so diffs and cross-chapter greps stay stable), (c) leaks nothing but a prefix hash,
505
+ which is also what lets a user ask "which secrets touched my sessions" without exposing
506
+ them.
507
+ - **Applied once, at the archive chokepoint** — render → redact → write — covering chapters
508
+ *and* artifacts, so a value is redacted at the moment it leaves the session log, wherever it
509
+ appears.
510
+ - **Honest limit:** pattern-based redaction is a speed bump, not a guarantee. The doc that
511
+ ships with the repo (and the notice) says the corpus is **plaintext memory**: per-project,
512
+ private, user-created, with a known-small redaction carve-out.
513
+
514
+ ## 10. The two-layer index
515
+
516
+ ### 10.1 Layer 1 — editable, per-machine, append-only
517
+
518
+ `edits/<harness-id>/curation.jsonl` — one JSON line per index fact, **one writer per file,
519
+ ever** (§3.3), zero conflicts by construction:
520
+
521
+ ```json
522
+ {"type":"topic-alias","from":"auth","to":"authentication","author":"plugin",
523
+ "reason":"co-occurrence in 14 chapters","session":"…","at":"…"}
524
+ {"type":"group","chapters":["…a","…b"],"label":"jwt-refresh-work","author":"user","at":"…"}
525
+ {"type":"weight","topic":"auth","weight":2,"author":"user","at":"…"}
526
+ ```
527
+
528
+ Fact types: `topic-alias` (merge/rename), `topic-pin`, `group` (sibling chapters that
529
+ belong together — the semantic grouping that composition cannot do alone), `weight`,
530
+ `note`. Authors are `plugin` or `user` (user entries via commands, P3). This is the
531
+ "improve over time" layer: curation accumulates as facts, and the derived index reflects
532
+ them — performance improves without anyone hand-editing, and every fact is inspectable and
533
+ revertable in git.
534
+
535
+ ### 10.2 Layer 2 — compressed, derived, incremental
536
+
537
+ `index/<topic-shard>.md` — one shard per canonical topic (alphabetical segmentation:
538
+ bounded files, and writes from different chapters usually touch different shards). Shard
539
+ content: a header line (topic, count, last-updated) then one line per chapter:
540
+ `date | title | path | (kind) | recency-hint`.
541
+
542
+ **Build is incremental** (user requirement: generation time must not scale with the corpus):
543
+ `manifest.json` records, per input file, the content hash last processed. A build touches
544
+ only (a) new chapters/artifacts/collections and (b) new curation entries since the last
545
+ build — O(delta). Because inputs are append-only, "changed" ≈ "new" in practice.
546
+
547
+ **Full rebuild stays possible** (deterministic from the tree) as the *recovery* path — a
548
+ machine with a lost manifest regenerates, and any machine can repair a divergent derived
549
+ file by rebuild-and-commit rather than merge-resolving (§3.3). Incremental is the fast path;
550
+ rebuild is the repair path.
551
+
552
+ ## 11. Trust and security
553
+
554
+ ### 11.1 Credentials
555
+
556
+ Scoped, revocable, per-repo, per-harness (§2.1). No SSH. A compromised harness exposes at
557
+ most one repo token, revocable in one click.
558
+
559
+ ### 11.2 Data exposure
560
+
561
+ The corpus is plaintext memory (chapters are verbatim by design). Mitigations are the
562
+ boundaries, not illusion: per-project repos, private, user-created; redaction carve-out
563
+ (§9); per-harness namespacing so one machine's sessions are always attributable.
564
+
565
+ ### 11.3 The blast-radius rule
566
+
567
+ Everything in the corpus that a *model* can influence and that other machines will *follow*
568
+ needs a human gate:
569
+
570
+ - **Chapters:** low blast radius — evidence, provenance-tagged, attributable. No gate.
571
+ - **Topics/vocabulary:** medium — affects search, not behavior. Plugin-autonomous with
572
+ git-visible curation entries (§6.3).
573
+ - **Rules:** high — they are prompts by another name, shared across machines. **Human
574
+ `approve` is mandatory** (§7.2). A proposed rule that is never approved affects nothing.
575
+
576
+ Git history + provenance makes every tier auditable and every mistake revertible.
577
+
578
+ ## 12. Configuration surface
579
+
580
+ No hardcoded tunables (existing hard rule). New config, all with defaults documented in
581
+ `dev/` templates:
582
+
583
+ | Key | Meaning | Default |
584
+ |---|---|---|
585
+ | `knowledgeRemote` | the project's repo URL (or `/chapters-link`) | none (local-only) |
586
+ | `projectKeyOverride` | wins over §2.3 derivation | none |
587
+ | `mergeThreshold` (τ) | collection-merge overlap, §4.2 | tuned in P1 |
588
+ | `chapterLimit` | max chapter size (tokens, heuristic) before forced split | 8000 (today's `chapterTokenTarget`) |
589
+ | `coreRulesBudgetTokens` | cap on the notice's core-rules section | refused-with-numbers on overflow |
590
+ | `syncDebounceMs` | coalesce window for pushes | 30000 |
591
+ | `toolResultArtifactTokens` | arrival-time artifacting floor (architecture.md amendment 2026-09-19): tool results at/above this many estimated tokens are stored as artifacts and reference-stubbed BEFORE the next request composes — the blob never enters any prompt | 8000 |
592
+ | `elicitedPlot` | when a compaction region holds no model-authored `PLOT:` note, spend ONE bounded stream call (≤8K chars in, ≤220 tokens out) to mint the working plot carried into the checkpoint; the approved, config-killed exception to zero-inference | true |
593
+ | `enrichment.enabled` / `.model` | per-harness enrichment toggle + model pin (built flat: `enrichmentEnabled`, `enrichmentModel`) | enabled, conversation model |
594
+ | `enrichment.trigger` / `.idleMs` / `.batchCap` | P2 queue policy seam (built: `enrichmentTrigger/enrichmentIdleMs/enrichmentBatchCap`); realm owns auto, host plane is manual-only; `/chapters-enrich model` overrides durably | both / 60000 / 5 |
595
+ | `vocabulary.*` | built flat: `vocabApply` (false = **shadow**: candidates reported in the sync status line, nothing written), `vocabCoMin` (3), `vocabOverlapMin` (0.5); applied merges land as §10.1 `topic-alias` facts under `edits/<harness>/`; `topics/vocabulary.json` derived | shadow / 3 / 0.5 |
596
+ | `coreRulesBudgetTokens` | P3 §7.3 cap on the notice's core-rules block; overflow refuses with per-rule numbers | 1200 |
597
+ | `rulesCoreBonus` | P3 §8 query-time rank bonus for rules effective-core on this machine (never baked into shared shards) | 0.15 |
598
+ | `redaction.patterns` | additive to the built-in list (§9) | built-ins |
599
+ | `search.defaultMaxTokens` | default pack size for `chapters_search` | small, config |
600
+ | `knowledgeProvider` | transport policy (Amendment 2026-09-21, transport): `auto` dispatches on the remote scheme; an explicit `git`/`treedx` must match it — contradictions refuse, nothing falls back into a wrong pool | auto |
601
+ | `treedxFetchTimeoutMs` / `treedxWorkspaceTtlSeconds` / `treedxLeaseRetries` / `treedxLeaseRetryDelayMs` | TreeDX transport tunables (provider.md); lease retries bound the one-writable-lease-per-branch contention window | 15000 / 900 / 3 / 1000 |
602
+
603
+ ## 13. Phasing (each phase independently useful)
604
+
605
+ **P1 — Transport + deterministic corpus (zero new inference).**
606
+ Clone/sync loop (§3, §5); `projectKey` (§2.3); per-turn signatures at `turn/end` (§4.1);
607
+ topic-sequential composition replacing size-only splitting (§4.2–4.4) for the fork path
608
+ first (compaction spans use the same composer); derived sharded index + incremental manifest
609
+ (§10.2); `chapters_search` (§8); redaction at the archive chokepoint (§9); notice v2 —
610
+ `Project:` line, message counts, search instructions (§7.3).
611
+ *Exit criterion: two machines, one repo; a fork on machine A sees machine B's chapters via
612
+ search; sync failure degrades to local-only without breaking the fork.*
613
+
614
+ **P2 — Model enrichment + emergent vocabulary.**
615
+ Idle-batched enrichment pass (§6.1–6.2) with validation/fallback; provenance frontmatter
616
+ final; self-bootstrapping vocabulary + curation entries (§6.3); Layer 1 edit types finalized.
617
+ *Exit criterion: enriching a corpus is idempotent and re-runnable; vocabulary merges are
618
+ git-visible and revertible; disabling enrichment leaves a fully working corpus.*
619
+
620
+ **P2 STATUS (2026-09-20): BUILT.** The ladder (slice → validate → retry-once → keep
621
+ deterministic; `src/enrich.ts`), queue with the pluggable trigger seam (`both`/60s defaults,
622
+ realm owns auto, host is manual + `/chapters-enrich run|model|report`), provenance chains +
623
+ the body-hash-guarded frontmatter write as the ONLY sanctioned chapter mutation
624
+ (`enrich-store.ts`), the vocabulary canonicalizer shadow-by-default with applied merges as
625
+ git-visible `topic-alias` entries + derived `topics/vocabulary.json` (`vocabulary.ts` in the
626
+ sync pass), and index-level fragment stitching (pure read-view `stitchFragments` — adjacent
627
+ legacy fragments cohere into one search entry citing all members, nothing merged on disk).
628
+ Idempotent re-run / kill-switch parity / never-blank failure: unit + integration; the `enrich`
629
+ e2e project replays the whole ladder through the model tape. CLOSED 2026-09-20: two-machine grade proven (two-machine-enrich test — which caught and fixed r37, a commit-invisible-modification bug in the transport), and the four-project replay chain is green with zero misses.
630
+
631
+ **P3 — Rules.**
632
+ Rules-as-chapters (`kind: rule`); the in-session command family `/chapters-rule
633
+ list|add|approve|revoke` (§7.2 — command output verified context-free); core-rules section +
634
+ category index in the notice (§7.3); agent-side rule proposals (async, always `proposed`);
635
+ `/chapters-topic` escape hatch; P3b topic-matched auto-inclusion (§7.4).
636
+ *Exit criterion: a rule proposed by a machine affects no other machine until a human types
637
+ `approve`; the core set overflows by refusing with numbers, never clipping.*
638
+
639
+ **P3 STATUS (2026-09-20): BUILT.** `src/rules.ts` (write-once proposed files, per-machine
640
+ status resolution, budgeted section with refuse-with-numbers) + `rules-commands.ts`
641
+ (`/chapters-rule add|list|approve|revoke`, host plane) + `chapters_rule_propose` tool +
642
+ notice seam (`assembleNotice` rulesSection — byte-identical ABSENT, golden-tested so the
643
+ tape corpus is untouched by construction) + transport (rules tree publishes; commit is
644
+ attempted even at copied===0 so fact-only passes travel — the change that made approvals
645
+ shareable) + scorer (category 1.5, rule bodies, per-machine core bonus at query time).
646
+ Exit-criterion tests: unit ('machine B sees nothing core'), transport-grade (three machines
647
+ over a real pool), e2e (`rules` project: UI add → approve → fork → the child's own log
648
+ carries the rule verbatim). §7.4 auto-inclusion remains P3b, unbuilt by design.
649
+
650
+ ## 14. Open risks (honest register)
651
+
652
+ | Risk | State |
653
+ |---|---|
654
+ | Pure-JS git SDK performance at repo scale; SSH-only remotes | accepted (user-directed); CLI fallback config-gated |
655
+ | Deterministic topic quality on pure-prose (non-code) sessions | degrades to today's time/size behavior; enrichment improves recall later |
656
+ | Pattern redaction is a speed bump, not a guarantee | documented in-repo; patterns are config-extensible |
657
+ | Single-slot model server contention | enrichment is idle-batched and per-harness disableable; signatures are model-free |
658
+ | Cross-model topic vocabulary drift | canonicalizer + provenance + re-run (§6.3) — mitigated by design, observed in practice at P2 |
659
+ | `mergeThreshold` mis-tune at archive time (composition is archive-time-only, §4.4) | signatures are stored with provenance; tune for future archives, inspect past ones |
660
+ | TreeDX service down or token expired (Amendment 2026-09-21, transport) | same §5.3 degradation (offline mirror, deferred push, auth re-link surfaced with numbers); no corpus loss |
661
+ | TreeDX corpus pull is per-file (`paths/list` + `files/read`): N+1 requests at large pools | acceptable at per-project corpus sizes (the record's own scaling premise); snapshot/`context` APIs are the Phase-F optimization |
662
+
663
+ ## 15. Amendments
664
+
665
+ This record governs implementation. An amendment is a **human decision** — discussed,
666
+ accepted, and edited in with a commit message stating the reason — because the record's value
667
+ is that every architectural fact in it was chosen, not generated. Machine-derived knowledge
668
+ lives in the *corpus* (chapters, curation entries, vocabulary with provenance); machine
669
+ opinions about *this document* do not.
670
+
671
+ ### Amendment 2026-09-21 — transport is pluggable; git is one provider
672
+
673
+ Accepted with the human-approved provider-plugin plan. §3.1/§3.2 stand as the git
674
+ provider's rules; they are now one implementation behind a seam
675
+ (`src/provider.ts`, contract in [provider.md](provider.md)). The six transport verbs
676
+ `runSync` already injected (`GitDriver`) define the provider interface; a second
677
+ implementation, `kind: 'treedx'`, transports the same mirror layout over TreeDX's
678
+ no-clone HTTP API (`src/treedx/`), selected by the `treedx+<url>/<repo>` remote
679
+ scheme (`/chapters-link mode 4`; §12 keys `knowledgeProvider`, `treedx*`). What the
680
+ amendment changes in the record, precisely:
681
+
682
+ - §3.1 holds unchanged: the store is truth, the mirror is transport — in TreeDX
683
+ mode the mirror is a provider-materialized plain directory instead of a clone.
684
+ Search, rules, index build, notice assembly, and the `read` path see no
685
+ difference.
686
+ - §5.2's divergence rule generalizes: any head movement detected at pull or commit
687
+ routes through the same rebuild-the-mirror-from-the-store path. Never a merge.
688
+ - §2.3's identity rule extends: TreeDX keys derive from the canonical
689
+ `host[:port]/repo` string under a `treedx/` namespace prefix, so a service at a
690
+ git-colliding URL is never the same pool.
691
+ - §11.3's blast-radius gate is provider-independent: per-machine rule approval is
692
+ a local curation fact — no server, federated or otherwise, can pre-approve.
693
+ - §5.3's degradation semantics are identical, plus one auth class (a rejected
694
+ token surfaces a re-link instruction; it never enters the conversation path).
695
+
696
+ The TreeDX backend's API-level facts (and what remains [LIVE-GATED] pending a real
697
+ container) are pinned in [spikes/treedx/FINDINGS.md](../spikes/treedx/FINDINGS.md);
698
+ transport-grade proof runs over the stub service (`treedx-{provider,sync,link}.test.ts`)
699
+ and the live exit criterion over `treedx-live.test.ts` when `scripts/treedx-local.sh`
700
+ brings the service up.
701
+
702
+ ### Amendment 2026-09-21 — host-injected context is not conversation
703
+ Accepted with the same plan (the human directive: the knowledge layer must not
704
+ depend on any project's instruction files — every target project ships its own,
705
+ unpredictably). The archive guarantee restates: **every byte of the CONVERSATION
706
+ remains retrievable** in chapters or artifacts; host-injected context — project
707
+ instruction files (`agent-instructions` events and the `instructions from:`
708
+ reminder form), the runtime-context snapshot, the skill catalog, and this plugin's
709
+ own TOC notice — is re-derivable project state and is EXCLUDED at the render
710
+ chokepoint (`src/injections.ts`), each omission leaving a countable marker.
711
+ `stats.messages` counts conversation only, so TOC `(N msgs)` stays honest. The
712
+ durable session log is untouched by this (invariant: append-only) — originals live
713
+ in the parent session; the screen is archive-time bytes, applied before redaction
714
+ (§9) at the same chokepoint. The signature layer has applied the same predicate
715
+ since r28; the two now share one implementation so they can never drift. The
716
+ model-tape masking rule (AGENTS-BLIND, 2026-09-20) is the test-side half of the
717
+ same position: development artifacts, never product facts.
718
+
719
+ ### Amendment 2026-09-20 — rule approval is a per-machine curation fact
720
+
721
+ Accepted as the P3 plan's single decision point (human-approved with the plan). §7.2's
722
+ file-status rewrite replaced by `rule-status` facts in each machine's own edits file
723
+ (see §7.2 note for the reason). Everything else in §7 stands.
724
+
725
+ ---
726
+
727
+ *Cross-references: invariants and the four invariants — `AGENTS.md`; budget math —
728
+ `docs/contract.md` (continuation budget) and `docs/architecture.md` (storage); the engine
729
+ seam the composer hooks into — `docs/host-compaction-seam.md`; measured claims cited as
730
+ `rNN` — `spikes/probe/FINDINGS.md`.*