@fractalsynapse/dsh-chapters 0.0.0-stage → 0.2.21
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.
- package/LICENSE +14 -0
- package/README.md +76 -2
- package/cordis.patch.yml +363 -0
- package/docs/architecture.md +692 -0
- package/docs/autorun-guide.md +248 -0
- package/docs/backlog.md +2022 -0
- package/docs/contract.md +938 -0
- package/docs/development.md +990 -0
- package/docs/host-compaction-seam.md +225 -0
- package/docs/host-protected-head-ask.md +80 -0
- package/docs/knowledge-repo.md +1332 -0
- package/docs/plans-carriage.md +571 -0
- package/docs/project-state.md +642 -0
- package/docs/provider.md +98 -0
- package/docs/superpowers/plans/2026-10-17-chapters-save.md +198 -0
- package/docs/superpowers/plans/2026-11-02-chapter-removal.md +151 -0
- package/docs/superpowers/specs/2026-11-02-chapter-removal-design.md +115 -0
- package/docs/verify.md +561 -0
- package/lib/archive.d.ts +161 -0
- package/lib/archive.js +229 -0
- package/lib/archive.js.map +1 -0
- package/lib/arrival.d.ts +23 -0
- package/lib/arrival.js +125 -0
- package/lib/arrival.js.map +1 -0
- package/lib/artifact-query.d.ts +62 -0
- package/lib/artifact-query.js +187 -0
- package/lib/artifact-query.js.map +1 -0
- package/lib/attach.d.ts +46 -0
- package/lib/attach.js +120 -0
- package/lib/attach.js.map +1 -0
- package/lib/auto-save.d.ts +145 -0
- package/lib/auto-save.js +334 -0
- package/lib/auto-save.js.map +1 -0
- package/lib/aux-message.d.ts +17 -0
- package/lib/aux-message.js +20 -0
- package/lib/aux-message.js.map +1 -0
- package/lib/client.js +1419 -0
- package/lib/client.js.map +1 -0
- package/lib/codegraph.d.ts +76 -0
- package/lib/codegraph.js +190 -0
- package/lib/codegraph.js.map +1 -0
- package/lib/commands.d.ts +52 -0
- package/lib/commands.js +946 -0
- package/lib/commands.js.map +1 -0
- package/lib/compose.d.ts +30 -0
- package/lib/compose.js +185 -0
- package/lib/compose.js.map +1 -0
- package/lib/continue-core.d.ts +245 -0
- package/lib/continue-core.js +405 -0
- package/lib/continue-core.js.map +1 -0
- package/lib/diag.d.ts +55 -0
- package/lib/diag.js +186 -0
- package/lib/diag.js.map +1 -0
- package/lib/engine-core.d.ts +301 -0
- package/lib/engine-core.js +531 -0
- package/lib/engine-core.js.map +1 -0
- package/lib/engine-note.d.ts +35 -0
- package/lib/engine-note.js +32 -0
- package/lib/engine-note.js.map +1 -0
- package/lib/engine.d.ts +329 -0
- package/lib/engine.js +1179 -0
- package/lib/engine.js.map +1 -0
- package/lib/enrich-queue.d.ts +66 -0
- package/lib/enrich-queue.js +126 -0
- package/lib/enrich-queue.js.map +1 -0
- package/lib/enrich-store.d.ts +31 -0
- package/lib/enrich-store.js +103 -0
- package/lib/enrich-store.js.map +1 -0
- package/lib/enrich-wire.d.ts +163 -0
- package/lib/enrich-wire.js +353 -0
- package/lib/enrich-wire.js.map +1 -0
- package/lib/enrich.d.ts +102 -0
- package/lib/enrich.js +267 -0
- package/lib/enrich.js.map +1 -0
- package/lib/gitops.d.ts +107 -0
- package/lib/gitops.js +539 -0
- package/lib/gitops.js.map +1 -0
- package/lib/index.d.ts +241 -0
- package/lib/index.js +1162 -0
- package/lib/index.js.map +1 -0
- package/lib/indexing.d.ts +98 -0
- package/lib/indexing.js +223 -0
- package/lib/indexing.js.map +1 -0
- package/lib/injections.d.ts +90 -0
- package/lib/injections.js +165 -0
- package/lib/injections.js.map +1 -0
- package/lib/last-resort.d.ts +24 -0
- package/lib/last-resort.js +27 -0
- package/lib/last-resort.js.map +1 -0
- package/lib/log-shape.d.ts +38 -0
- package/lib/log-shape.js +93 -0
- package/lib/log-shape.js.map +1 -0
- package/lib/models-command.d.ts +74 -0
- package/lib/models-command.js +100 -0
- package/lib/models-command.js.map +1 -0
- package/lib/notice.d.ts +74 -0
- package/lib/notice.js +92 -0
- package/lib/notice.js.map +1 -0
- package/lib/plans.d.ts +154 -0
- package/lib/plans.js +315 -0
- package/lib/plans.js.map +1 -0
- package/lib/preset.d.ts +186 -0
- package/lib/preset.js +212 -0
- package/lib/preset.js.map +1 -0
- package/lib/prompt-keys.d.ts +7 -0
- package/lib/prompt-keys.js +8 -0
- package/lib/prompt-keys.js.map +1 -0
- package/lib/prompt-loop.d.ts +73 -0
- package/lib/prompt-loop.js +147 -0
- package/lib/prompt-loop.js.map +1 -0
- package/lib/prompt-push.d.ts +47 -0
- package/lib/prompt-push.js +115 -0
- package/lib/prompt-push.js.map +1 -0
- package/lib/prompt-runner.d.ts +48 -0
- package/lib/prompt-runner.js +90 -0
- package/lib/prompt-runner.js.map +1 -0
- package/lib/prompt-state.d.ts +142 -0
- package/lib/prompt-state.js +101 -0
- package/lib/prompt-state.js.map +1 -0
- package/lib/prompt.d.ts +270 -0
- package/lib/prompt.js +408 -0
- package/lib/prompt.js.map +1 -0
- package/lib/provider.d.ts +44 -0
- package/lib/provider.js +66 -0
- package/lib/provider.js.map +1 -0
- package/lib/redact.d.ts +24 -0
- package/lib/redact.js +85 -0
- package/lib/redact.js.map +1 -0
- package/lib/registry.d.ts +187 -0
- package/lib/registry.js +235 -0
- package/lib/registry.js.map +1 -0
- package/lib/removal.d.ts +159 -0
- package/lib/removal.js +556 -0
- package/lib/removal.js.map +1 -0
- package/lib/render.d.ts +97 -0
- package/lib/render.js +530 -0
- package/lib/render.js.map +1 -0
- package/lib/repair-run.d.ts +76 -0
- package/lib/repair-run.js +506 -0
- package/lib/repair-run.js.map +1 -0
- package/lib/repair.d.ts +208 -0
- package/lib/repair.js +357 -0
- package/lib/repair.js.map +1 -0
- package/lib/repo.d.ts +78 -0
- package/lib/repo.js +184 -0
- package/lib/repo.js.map +1 -0
- package/lib/rules-commands.d.ts +46 -0
- package/lib/rules-commands.js +379 -0
- package/lib/rules-commands.js.map +1 -0
- package/lib/rules.d.ts +267 -0
- package/lib/rules.js +478 -0
- package/lib/rules.js.map +1 -0
- package/lib/save-core.d.ts +72 -0
- package/lib/save-core.js +220 -0
- package/lib/save-core.js.map +1 -0
- package/lib/search.d.ts +112 -0
- package/lib/search.js +282 -0
- package/lib/search.js.map +1 -0
- package/lib/session-rules.d.ts +222 -0
- package/lib/session-rules.js +454 -0
- package/lib/session-rules.js.map +1 -0
- package/lib/signature.d.ts +45 -0
- package/lib/signature.js +199 -0
- package/lib/signature.js.map +1 -0
- package/lib/store.d.ts +156 -0
- package/lib/store.js +368 -0
- package/lib/store.js.map +1 -0
- package/lib/sync.d.ts +220 -0
- package/lib/sync.js +810 -0
- package/lib/sync.js.map +1 -0
- package/lib/tools.d.ts +113 -0
- package/lib/tools.js +1090 -0
- package/lib/tools.js.map +1 -0
- package/lib/treedx/client.d.ts +57 -0
- package/lib/treedx/client.js +82 -0
- package/lib/treedx/client.js.map +1 -0
- package/lib/treedx/provider.d.ts +39 -0
- package/lib/treedx/provider.js +479 -0
- package/lib/treedx/provider.js.map +1 -0
- package/lib/treedx/state.d.ts +27 -0
- package/lib/treedx/state.js +65 -0
- package/lib/treedx/state.js.map +1 -0
- package/lib/types.d.ts +210 -0
- package/lib/types.js +31 -0
- package/lib/types.js.map +1 -0
- package/lib/version.d.ts +9 -0
- package/lib/version.js +10 -0
- package/lib/version.js.map +1 -0
- package/lib/vocabulary.d.ts +41 -0
- package/lib/vocabulary.js +118 -0
- package/lib/vocabulary.js.map +1 -0
- package/lib/warnings.d.ts +10 -0
- package/lib/warnings.js +18 -0
- package/lib/warnings.js.map +1 -0
- package/package.json +151 -3
|
@@ -0,0 +1,1332 @@
|
|
|
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
|
+
**Amendment 2026-10-22 (#61, item 6) — a delegate's report to its parent IS conversation.** The
|
|
290
|
+
injection screen (H1) drops host-injected `user/message` events, and it was right to drop the project's
|
|
291
|
+
instruction files. But the same rule was also dropping two shapes the harness produces *about this
|
|
292
|
+
session's own work*:
|
|
293
|
+
|
|
294
|
+
| source (measured on 0.2.x logs) | content | was | now |
|
|
295
|
+
|---|---|---|---|
|
|
296
|
+
| `{ kind: 'agent-message', form: 'relay' }` | "Agent <id> sent a message: Both Starlight documentation files have been written…" | screened | **archived** |
|
|
297
|
+
| `{ kind: 'subagent-settled', form: 'notice' }` | "Background subagent <id> finished and will do no further work…" | screened | **archived** |
|
|
298
|
+
| `{ kind: 'agent-instructions', form: 'instructions' }` | a project's workspace instruction file | screened | **still screened** |
|
|
299
|
+
|
|
300
|
+
Measured cost of the old rule in the first `chapters`-preset project: **44 markers, 248,150 chars ≈ 62K
|
|
301
|
+
est tokens** of delegated results thrown away, including an 18,253-char spike report — so the work that
|
|
302
|
+
crossed the delegation edge was lost through **both** doors built to capture it (the parent's archive
|
|
303
|
+
dropped the report; the child's settlement had already archived its own bytes, so the parent's chapter
|
|
304
|
+
recorded a delegation with no outcome).
|
|
305
|
+
|
|
306
|
+
Still named by **producer AND form**, exactly like the `plan-mode` precedent it joins — the list cannot
|
|
307
|
+
widen by accident into the general plugin-source exemption, and an instruction file arriving with some
|
|
308
|
+
other form still screens. `isHumanAuthored` stays strict on purpose: the topic-signature layer keeps
|
|
309
|
+
ignoring these, because the r28 lesson (injected text moves chapter boundaries) is unchanged, and a
|
|
310
|
+
2 KB report is exactly the kind of text that would drag a signature toward `docs/…` filenames.
|
|
311
|
+
|
|
312
|
+
### 4.3 The completeness guard
|
|
313
|
+
|
|
314
|
+
Chapters include **only collections with a `turn/end`** inside the span. If a compaction
|
|
315
|
+
boundary ever lands mid-collection (the manual-`/compact` edge case — the automatic path
|
|
316
|
+
cannot produce one, §4.1), the composition clamps to the last completed collection and the
|
|
317
|
+
TOC notes the clamp. The unfinished collection stays in the live context, per the user
|
|
318
|
+
requirement, and composes at the next archive.
|
|
319
|
+
|
|
320
|
+
**Amendment 2026-10-22 (#59, measured in the first real `chapters`-preset project).** This guard is
|
|
321
|
+
one-directional: it keeps unfinished work OUT, and says nothing about what may silently fail to come
|
|
322
|
+
IN. Composing from collections alone means a completed turn that never got a signature vanishes — not
|
|
323
|
+
truncated, not deferred, **absent from every chapter and from every index**, with no warning. That is
|
|
324
|
+
what happened: `session-2edf9694` had 32 `turn/end` events and 31 collections, and the 203-event span
|
|
325
|
+
between chapters 27 and 28 — a complete turn including the maintainer's "I consider it foundational to
|
|
326
|
+
the project" hosting brief and its implementation — exists in no chapter in any tree, and the three
|
|
327
|
+
verbatim phrases from it match zero files in the store.
|
|
328
|
+
|
|
329
|
+
The guard is therefore **bidirectional** now:
|
|
330
|
+
|
|
331
|
+
1. Nothing unfinished is archived (the rule as written above).
|
|
332
|
+
2. **Nothing finished is dropped.** The composed ranges must cover `[spanStart..spanEnd]`
|
|
333
|
+
contiguously; an unsigned but *completed* span is absorbed into its neighbouring chapter (a
|
|
334
|
+
wider range, never a re-titled one), and every door asserts the coverage before writing,
|
|
335
|
+
refusing with the exact seqs rather than proceeding. The claim a chapter makes is
|
|
336
|
+
"everything in this range is here", so a hole is a false statement, not a small one.
|
|
337
|
+
3. A door may not treat "no collection covered it" as "incomplete turn": completeness is a
|
|
338
|
+
`turn/end` fact, read from the log, not inferred from the presence of a signature — a lost
|
|
339
|
+
signature is a registry defect (see docs/architecture.md § The row is shared), and the note the
|
|
340
|
+
composer used to emit claimed the opposite while dropping completed work.
|
|
341
|
+
|
|
342
|
+
`coverage()` (`src/archive.ts`) existed for exactly this and was called in one door, for a warning
|
|
343
|
+
that got counted and thrown away. The same rule now covers model-submitted ranges:
|
|
344
|
+
`validateRanges` refuses a **gap**, not only an overlap — AGENTS.md already said "a gap silently
|
|
345
|
+
drops it"; the check did not exist.
|
|
346
|
+
|
|
347
|
+
### 4.4 Consequence: composition is archive-time-only
|
|
348
|
+
|
|
349
|
+
Because chapters are append-only (invariant 3), the merge threshold must be right *when the
|
|
350
|
+
archive happens*. The per-collection signature is stored with provenance (§4.1) precisely so
|
|
351
|
+
any merge or split is **inspectable after the fact** — and so `τ` can be tuned for future
|
|
352
|
+
archives without pretending to rewrite old ones. Enrichment (§6) may later *relabel* a
|
|
353
|
+
chapter's topics but may never re-chapter it.
|
|
354
|
+
|
|
355
|
+
## 5. The sync loop
|
|
356
|
+
|
|
357
|
+
### 5.1 Points
|
|
358
|
+
|
|
359
|
+
| Event | Action |
|
|
360
|
+
|---|---|
|
|
361
|
+
| New chapters-preset session created | **pull** (fresh corpus before the first turn; §5.2 ordering) |
|
|
362
|
+
| Fork (button or command) | **pull → compose/archive → push** |
|
|
363
|
+
| Compaction finalization | **push** (debounced — see 5.2) |
|
|
364
|
+
| Idle (debounce expired after the last archive) | **push** |
|
|
365
|
+
| Pull failure / push failure | log, surface via `/chapters-status`, **never fail the archive** |
|
|
366
|
+
| `/chapters-link <path>` | binds a directory upstream (bare repo materialized on first sync); no credentials |
|
|
367
|
+
|
|
368
|
+
Compaction and fork are simultaneously push points *and* the right refresh points — the loop
|
|
369
|
+
is pull → archive new span → push, so a fork point always sees a current index.
|
|
370
|
+
|
|
371
|
+
### 5.2 Ordering and serialization
|
|
372
|
+
|
|
373
|
+
- A **per-repo writer lock** serializes (a) store transactions (reserve → write → commit) and
|
|
374
|
+
(b) clone operations. A pull never lands mid-archive-write and a push never ships a
|
|
375
|
+
half-committed store.
|
|
376
|
+
- Push = `copy-out → commit → fetch → if diverged: rebase-apply (append-only ⇒ fast-forward
|
|
377
|
+
safe) → push`, with bounded retry. All git calls run with no TTY prompt (
|
|
378
|
+
`GIT_TERMINAL_PROMPT=0` equivalent in the SDK), hard timeouts, and network treated as
|
|
379
|
+
best-effort.
|
|
380
|
+
**Implementation note (r35, via §15):** divergence is resolved by REBUILDING the mirror,
|
|
381
|
+
not rebasing it — `remove → fresh clone → re-publish from the store → push`. Same
|
|
382
|
+
semantics (the local backlog reaches the remote without force; append-only content merges
|
|
383
|
+
trivially), fewer moving parts, and no rebase primitive needed (isomorphic-git 1.42 has
|
|
384
|
+
none). The rebuild is safe only because the mirror is transport (§3.1) — the workspace
|
|
385
|
+
store is the truth it re-publishes from. Diverged-vs-network is a machine-readable
|
|
386
|
+
failure CODE, not prose matching: a network error's message once contained the word
|
|
387
|
+
"diverge" and falsely triggered this path (caught by the real-HTTP test).
|
|
388
|
+
- Debounce: consecutive archive events within the configured window coalesce into one push.
|
|
389
|
+
The triggers live in both planes: the host plugin pulls before, and schedules after,
|
|
390
|
+
fork/continue archives; the realm engine schedules after compaction finalization and
|
|
391
|
+
pulls at each session's first `turn/start` (the "new session" refresh point, read
|
|
392
|
+
process-scoped). The file lock is what makes the two schedulers safe side by side.
|
|
393
|
+
|
|
394
|
+
### 5.3 Failure semantics
|
|
395
|
+
|
|
396
|
+
Remote down, token revoked, clone corrupted — each degrades to *local-only mode*: the store
|
|
397
|
+
keeps working, pending sync accumulates, `/chapters-status` says so with numbers. Nothing in
|
|
398
|
+
the conversation path waits on the network.
|
|
399
|
+
|
|
400
|
+
## 6. Model enrichment: async, provenance-tagged, re-runnable
|
|
401
|
+
|
|
402
|
+
### 6.1 The pass
|
|
403
|
+
|
|
404
|
+
For each archived chapter, one structured call whose input is the cheap slice the user
|
|
405
|
+
specified — **the user request(s) plus first and last assistant message**, each capped, plus
|
|
406
|
+
the first ~200 tokens of the chapter body for grounding — and whose output is validated JSON:
|
|
407
|
+
|
|
408
|
+
```json
|
|
409
|
+
{"title": "…one sentence…", "summary": "…short…", "topics": ["…up to 10…"]}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
- **Validation before commit:** schema-check; on failure, retry once; on second failure, keep
|
|
413
|
+
the deterministic values (title from the existing derivation, excerpt-labeled summary,
|
|
414
|
+
signature topics). A slow or failing model never blocks or blanks a chapter.
|
|
415
|
+
- **Thinking models:** the pass runs at low reasoning effort and tight `maxTokens`; on the
|
|
416
|
+
local server this is the only model work this feature ever forces, and it is batched,
|
|
417
|
+
below.
|
|
418
|
+
- **Topics are proposals:** they enter the chapter's frontmatter only through the
|
|
419
|
+
canonicalizer (§6.3) — unknown labels pass as `unvetted`.
|
|
420
|
+
- **What proves it live (#175):** the tape-backed `enrich` leg asserts the plumbing on canned
|
|
421
|
+
completions. `tests/e2e/model-activities-live.spec.ts` runs the same journey against the real
|
|
422
|
+
provider, where a REAL completion writes the `generated:` frontmatter (verbatim body preserved),
|
|
423
|
+
and the mining half asserts the durable rule file itself (§7.5). Live-class: excluded from the
|
|
424
|
+
model-free acceptance job by design, and the exclusion is stated wherever the claim is made.
|
|
425
|
+
|
|
426
|
+
### 6.2 When it runs: idle-batched, never per-turn
|
|
427
|
+
|
|
428
|
+
**Measured constraint (r29-era):** the local server is **single-slot**
|
|
429
|
+
(`maxConcurrent: 1`). Per-turn model work would queue against the user's own conversation
|
|
430
|
+
turns. Therefore:
|
|
431
|
+
|
|
432
|
+
- **Per turn:** deterministic signature only (§4.1) — free, synchronous, no model.
|
|
433
|
+
- **Model enrichment:** batched at natural idle points — after a compaction/fork push
|
|
434
|
+
completes, or on an idle debounce — processing *archived* chapters only. It can be
|
|
435
|
+
disabled per-harness in config; a harness on a contended machine can run
|
|
436
|
+
signatures-only and still contribute a fully functional corpus.
|
|
437
|
+
- **Re-runnable:** a command re-annotates a chapter (or the whole project) with a newer model
|
|
438
|
+
or corrected vocabulary, overwriting only the model-owned fields (frontmatter provenance
|
|
439
|
+
records each overwrite). The verbatim body is never touched.
|
|
440
|
+
|
|
441
|
+
### 6.3 Self-bootstrapping vocabulary (no seed list, no hand-editing)
|
|
442
|
+
|
|
443
|
+
Per the user requirement, the vocabulary is **plugin-owned and emergent** — the user neither
|
|
444
|
+
authors a seed list nor edits files:
|
|
445
|
+
|
|
446
|
+
1. **First runs:** no canonical vocabulary. Topic labels are raw deterministic candidates;
|
|
447
|
+
enrichment topics enter flagged `unvetted`.
|
|
448
|
+
2. **Merge signals**, watched by the plugin against its own corpus:
|
|
449
|
+
- two labels **co-occur** in the same chapter repeatedly;
|
|
450
|
+
- two labels share **high normalized token overlap** across chapters;
|
|
451
|
+
- **alias patterns** (substring/singular-plural: `auth` ⊂ `authentication`).
|
|
452
|
+
Past threshold, the plugin writes a **curation entry** into `edits/<harness-id>/`
|
|
453
|
+
(Layer 1, §10.1): `author: plugin`, `reason: <signal + counts>`, `merge: {from, to}`.
|
|
454
|
+
3. The derived index and `topics/vocabulary.json` reflect curation entries on the next build;
|
|
455
|
+
the vocabulary file stays **diffable and git-revertable** — the user can see every
|
|
456
|
+
decision the plugin made, and undo one, without ever having to make one.
|
|
457
|
+
4. Escape hatch (P3, optional): `/chapters-topic merge <a> <b>` — the same curation entry,
|
|
458
|
+
human-authored.
|
|
459
|
+
|
|
460
|
+
Provenance in frontmatter (§2.4) is what makes all of this auditable: any label on any
|
|
461
|
+
chapter traces to a deterministic pass, a specific model run, or a specific curation entry.
|
|
462
|
+
|
|
463
|
+
## 7. Rules: chapters of a different kind
|
|
464
|
+
|
|
465
|
+
### 7.1 Model
|
|
466
|
+
|
|
467
|
+
Rules reuse the chapter machinery end-to-end — same file format, registry, budget preflight,
|
|
468
|
+
search, and sync. Frontmatter: `kind: rule`, plus `category`, `status`, and provenance.
|
|
469
|
+
**Rules are another form of chapter to search when needed** (user's framing, adopted); the
|
|
470
|
+
plugin's modularity is what makes this nearly free — the chapter code paths are parameterized
|
|
471
|
+
by `kind`, not duplicated.
|
|
472
|
+
|
|
473
|
+
**Two scopes, one store, one format** (Amendment 2026-10-20, §15). Session rules are *not* a
|
|
474
|
+
second subsystem: they are `kind: rule` files in the same `rules/<harness>/` tree, with the same
|
|
475
|
+
write-once immutability, the same `list` surface, the same search index. What differs is **how
|
|
476
|
+
they get authored and how they become active**:
|
|
477
|
+
|
|
478
|
+
| Tier | Authored by | Activation | Transport | Carried by |
|
|
479
|
+
|---|---|---|---|---|
|
|
480
|
+
| **project** | `/chapters-rule add`, `chapters_rule_propose` (agent) | human `approve` → per-machine curation fact (§7.2) | synced to the pool | continue **and** fork |
|
|
481
|
+
| **session** | `ADD RULE:` marker (deterministic), enrichment (optional) | **derived** (§7.5) — no fact, no approval | local only; never staged (§7.5) | continue only — dropped by fork (§7.6) |
|
|
482
|
+
|
|
483
|
+
Extra frontmatter on every rule file: `scope: project | session` (absent ⇒ `project`, so every
|
|
484
|
+
pre-existing record keeps working byte-identically), `session: <sessionId>` (session tier only),
|
|
485
|
+
`derivedFrom: human | marker | model`, `pinned: true` (§7.4). Same `category`, `status`,
|
|
486
|
+
`sha256`, provenance chain as before.
|
|
487
|
+
|
|
488
|
+
**Why the split is safe.** A rule that only ever governs one machine's session tree is not shared
|
|
489
|
+
state, so §11.3's blast-radius argument does not apply to it — and the per-machine approval
|
|
490
|
+
boundary of §7.2 is left exactly as the 2026-09-20 amendment set it. Cross-checkout carry of
|
|
491
|
+
*project* rules needed no new mechanism: `projectKey` is UUIDv5 over the canonicalized remote
|
|
492
|
+
(§2.3) and `harnessId` defaults to the hostname, so several checkouts of one repository already
|
|
493
|
+
resolve to one rule namespace on one machine. §7.8 is about making the cases where that identity
|
|
494
|
+
*diverges* loud, not about building propagation.
|
|
495
|
+
|
|
496
|
+
### 7.2 Lifecycle: proposed → core, approved in-session *(the project tier)*
|
|
497
|
+
|
|
498
|
+
This section governs `scope: project` — the shared, team-visible tier. The session tier's
|
|
499
|
+
activation is derived and needs no approval (§7.5).
|
|
500
|
+
|
|
501
|
+
- **Proposal:** a user command (`/chapters-rule add <category> <text>`) or an agent's async
|
|
502
|
+
derivation from a completed session (P3, always `status: proposed`, provenance-tagged with
|
|
503
|
+
the source session).
|
|
504
|
+
- **Review/approve:** entirely in-session DSH commands — the user never leaves the session,
|
|
505
|
+
and **command output never enters the context window** (verified 2026-09-17: command results
|
|
506
|
+
persist as `command/run` / `command/done` event types — a distinct type, not
|
|
507
|
+
user/assistant/tool — which are neither prefilled to the model nor priced by the compaction
|
|
508
|
+
engine; 24 such pairs observed in the r29 session's durable log from the e2e suite):
|
|
509
|
+
- `/chapters-rule list` (`--proposed`, `--category <name>`) — table: id, status, category,
|
|
510
|
+
one-line text, provenance.
|
|
511
|
+
- `/chapters-rule view <id>` — ONE rule in full: the complete body (untruncated — `list` shows only
|
|
512
|
+
the creation-time title, the first sentence cut at 60 chars), identity, this machine's effective
|
|
513
|
+
status, pin, and for session rules `active`/`reviewOnly`/`basis` (stated vs inferred). A rule that
|
|
514
|
+
cannot be read cannot be approved; added 2026-10-29 at the maintainer's direction (#174).
|
|
515
|
+
- `/chapters-rule approve <id>` — `proposed → core`.
|
|
516
|
+
- `/chapters-rule revoke <id>` — core back to the file, out of the notice.
|
|
517
|
+
- **`core` is the only tier that renders into the continuation notice** (§7.3).
|
|
518
|
+
**Amended 2026-09-20 (§15):** `approve` does not rewrite the rule file — it appends a
|
|
519
|
+
`rule-status` curation fact to the APPROVING machine's own `edits/<harness>/curation.jsonl`.
|
|
520
|
+
Effective status is per-machine (file otherwise `proposed`); a rule affects no other
|
|
521
|
+
machine's notice until that machine's human approves. Reason: authorship partition — a
|
|
522
|
+
shared-file status would let any machine rewrite another's authored bytes, which r37
|
|
523
|
+
showed is exactly where transport bugs live. The human
|
|
524
|
+
in the loop is the one who types `approve`; the git diff of the rule file is the audit
|
|
525
|
+
trail. This is the trust boundary for shared state (§11.3).
|
|
526
|
+
|
|
527
|
+
### 7.3 The rules block: what a rebuilt head shows
|
|
528
|
+
|
|
529
|
+
One builder renders the rules block (`buildRulesSection`), and **both** places a head is rebuilt
|
|
530
|
+
use it — the continuation notice and the in-place compaction checkpoint (§7.6). The block is O(1)
|
|
531
|
+
in corpus size (§1 invariant 6) and, on an empty or unlinked corpus, renders **zero bytes**
|
|
532
|
+
(`kind: 'none'` → nothing at the call site): load-bearing for the tape corpus, which contains no
|
|
533
|
+
rules block anywhere (§13 P3).
|
|
534
|
+
|
|
535
|
+
Contents (all O(1), §1 invariant 6):
|
|
536
|
+
|
|
537
|
+
- **Active rules, verbatim,** selected by the three classes of §7.4 — pinned, then
|
|
538
|
+
relevance-selected, then index-only. Project tier and session tier render under their own
|
|
539
|
+
headers so the model can tell whose order it is reading.
|
|
540
|
+
- **A withheld line — mandatory.** When the caps drop anything, the block says so with numbers:
|
|
541
|
+
`9 project rules active, 4 shown — /chapters-rule list`. Selection by relevance is legitimate
|
|
542
|
+
(§7.4); *silent* selection is the exact failure this feature exists to remove, because a rule
|
|
543
|
+
the human believes is active and is not, is worse than a rule that was never captured.
|
|
544
|
+
- **The token ceiling stays refuse-don't-truncate:** if the selected set exceeds
|
|
545
|
+
`coreRulesBudgetTokens`, the builder returns `refusal` with per-rule token numbers, the caller
|
|
546
|
+
omits the block rather than clipping (§1.2), and the refusal is reported — never swallowed.
|
|
547
|
+
Omission stays non-blocking: rules are additive, and a broken mirror or an overflowing ruleset
|
|
548
|
+
must not stop a continuation or a compaction.
|
|
549
|
+
- **Precedence line** (§7.7): session rules refine and add; they never override a project rule.
|
|
550
|
+
- **Per-rule warrant** (§7.5, amended 2026-10-21): a session rule renders with how it was
|
|
551
|
+
established — `you said this` (marker), `stated by you, mined from ch.N`, or `inferred from ch.N
|
|
552
|
+
— nobody said it`. Inference is allowed to govern, but never anonymously.
|
|
553
|
+
- **The speak-up line**, present only when a block carries session rules: the agent names by id
|
|
554
|
+
which of them shaped its reply, or says none did. A project-only block keeps its exact pre-#50
|
|
555
|
+
bytes (verify.md R0 pins that), so nothing is spent where nothing is inferred.
|
|
556
|
+
- **Rules category index:** category names + one-line essence + paths, with the instruction
|
|
557
|
+
*load a rule category when this session's topics overlap a category* — i.e., the agent
|
|
558
|
+
reads the full category from the repo when (and only when) relevant.
|
|
559
|
+
- **Search instructions** for the chapters index (one paragraph, pointing at
|
|
560
|
+
`chapters_search` and the shard paths).
|
|
561
|
+
- **`Project: <slug> · <projectKey>` — prominently near the top** (user requirement): the
|
|
562
|
+
agent always knows which corpus it is in and where searches point.
|
|
563
|
+
- The existing sections (chapters list with single-sentence title, short summary, file link,
|
|
564
|
+
**message count** — not token count, since tokenization is not comparable across models —
|
|
565
|
+
and parent/fork links) unchanged.
|
|
566
|
+
|
|
567
|
+
### 7.4 Topic-matched selection (raised from the old P3b sketch)
|
|
568
|
+
|
|
569
|
+
The record deferred "topic-matched auto-inclusion" because always-render-everything does not scale
|
|
570
|
+
against an O(1) head. With automatic capture the corpus of *active* rules now grows on its own, so
|
|
571
|
+
selection moves from garnish to requirement, and it is built here on machinery that already existed
|
|
572
|
+
purely for pull-based search.
|
|
573
|
+
|
|
574
|
+
Three render classes, in this order, under `projectRulesDisplayCap` / `sessionRulesDisplayCap`
|
|
575
|
+
(count) and `coreRulesBudgetTokens` (tokens):
|
|
576
|
+
|
|
577
|
+
1. **Pinned** (`pinned: true`, human-set via `/chapters-rule pin`) — always rendered, first.
|
|
578
|
+
Ground rules are *old* by definition, so any scheme that ranks by recency or topicality evicts
|
|
579
|
+
them first; the pinned class is the floor that keeps that from being a surprise.
|
|
580
|
+
2. **Relevance-selected** — `rules.selectRules`, the ONE selector for what a head shows:
|
|
581
|
+
pinned first, then topicality against a term bag, then the pre-#50 category→id order. The term
|
|
582
|
+
bag is the archived span's stored turn signatures for a rebuilt head and the task query for
|
|
583
|
+
the `chapters_rules` door — both paths share the selector, so the head and the door can never
|
|
584
|
+
disagree about what applies. `search.rankEntries` stays the *corpus* scorer (§8): ranking an
|
|
585
|
+
unbounded archive against a query and selecting a bounded set for one head are different jobs,
|
|
586
|
+
and this round kept one implementation of each rather than bending a rebuilt head to read index
|
|
587
|
+
shards. What is forbidden is a **third** rule-picker living in a door or a checkpoint — a
|
|
588
|
+
source-shape row fails if either stops calling the shared builder.
|
|
589
|
+
3. **Index-only** — everything else: category + one-line essence + path, loadable. The corpus
|
|
590
|
+
grows without bound; the head does not.
|
|
591
|
+
|
|
592
|
+
`rulesRelevance: false` falls back to the pre-existing deterministic order (category, then id),
|
|
593
|
+
which is what the tapes were distilled against.
|
|
594
|
+
|
|
595
|
+
**Determinism is a requirement, not a preference.** Selection must be a pure function of (rule
|
|
596
|
+
files, stored topic signatures, caps, query) — no wall-clock timestamps, no map-iteration or
|
|
597
|
+
filesystem-readdir order — or two machines disagree about what the agent was told *and* every
|
|
598
|
+
replay prefix becomes unstable. Ranked-by-recency was rejected partly for this reason: recency
|
|
599
|
+
reads the clock.
|
|
600
|
+
|
|
601
|
+
### 7.5 Session rules: capture and derived activation
|
|
602
|
+
|
|
603
|
+
Two capture paths, deliberately different speeds (this is §6.2's single-slot constraint applied to
|
|
604
|
+
rules, not a new policy):
|
|
605
|
+
|
|
606
|
+
- **Marker — deterministic, instant, zero model tokens (§1 invariant 1).** A line in a
|
|
607
|
+
*human-authored* message beginning `ADD RULE:` (§12 `rulesMarkerPrefix`) becomes an active
|
|
608
|
+
session rule at the existing `turn/end` listener — the same one the topic signatures collect on
|
|
609
|
+
(r35j). It is not a new archive door (§12 of `docs/architecture.md`: five moments, extended in
|
|
610
|
+
place, never copied). The rule body is the human's own words, verbatim.
|
|
611
|
+
- **Enrichment — idle-batched, `rulesEnrichment` default ON.** The §6.1 pass carries a
|
|
612
|
+
`rules: [{ text, category, quote }]` field through that pass's existing ladder: validation
|
|
613
|
+
before commit, one retry, deterministic fallback, provenance chain, and the body-hash guard
|
|
614
|
+
(mining writes its own rule files; it never touches chapter prose). `rules` is **optional in the
|
|
615
|
+
output contract**, so every pre-existing stub, fake and tape still validates, and a malformed
|
|
616
|
+
`rules` value cannot cost a chapter its annotation.
|
|
617
|
+
**What decides whether a mined rule governs is POLICY, not evidence** (Amendment 2026-10-21).
|
|
618
|
+
`rulesAutoActivate` defaults ON: a rule the model inferred from context applies to the session
|
|
619
|
+
tree the moment it is written. With it off, every mined rule is written `reviewOnly: true` and
|
|
620
|
+
stays inert until this machine's `approve` fact says core — activation still rides a curation
|
|
621
|
+
fact, so the write-once file is never rewritten (§1.3).
|
|
622
|
+
**Live proof (#175):** `model-activities-live.spec.ts` puts an explicit standing order in the
|
|
623
|
+
journey's only human message, drains enrichment against the real provider, and asserts the mined
|
|
624
|
+
rule FILE — `scope: session`, `derivedFrom: model`, and the `basis` warrant (`stated`/`inferred`,
|
|
625
|
+
decided by the verbatim-quote gate above; an inferred warrant must render with its "the model
|
|
626
|
+
wrote this" caveat) — then reads the rule back in full through `/chapters-rule view`. Unit rows
|
|
627
|
+
pin the gate; this leg pins the journey, against a real completion.
|
|
628
|
+
**The quote check survives, demoted to a label.** Matching the chapter's human bytes verbatim
|
|
629
|
+
classifies a rule `stated`; not matching classifies it `inferred`; the label is rendered *beside
|
|
630
|
+
the rule text, with the chapter it came from*, and it drives nothing. Assistant prose is not
|
|
631
|
+
admissible even as a label — a "quote" found only in the model's own restatement is `inferred`.
|
|
632
|
+
Why it stopped being a gate is the amendment's subject: requiring a human to have already said it
|
|
633
|
+
makes the tier redundant with the deterministic marker, and the premise of the tier is that the
|
|
634
|
+
human may not know the rule yet.
|
|
635
|
+
Measured before shipping, on three real chapters at the session model (§13 P3c, verify.md R5): 9
|
|
636
|
+
rules captured, 6 stated / 3 inferred, every inference confined to the chapter that had no human
|
|
637
|
+
text at all, and the model's self-declared `basis` less accurate than the check. It also exposed a
|
|
638
|
+
cost fact worth a decision: the hosted route counts reasoning tokens, so mining inside the
|
|
639
|
+
annotation call shares a budget sized for annotation (`ENRICH_MAX_TOKENS`) — `rulesModel` is the
|
|
640
|
+
clean bound, and an unexamined default is not.
|
|
641
|
+
|
|
642
|
+
Three properties worth stating: mining runs **before** the hash-guarded annotation write, so a
|
|
643
|
+
chapter whose enrichment is refused still contributes its standing orders; mining may use a
|
|
644
|
+
**dedicated model** (`rulesModel` / `/chapters-rule model`), which costs one extra bounded call
|
|
645
|
+
per chapter and, when set, replaces rather than supplements the annotation pass's answer; and the
|
|
646
|
+
agent must **name the session rules it applied** in its reply (§7.3) — which is where a wrong
|
|
647
|
+
inference becomes visible to the only party who can revoke it. That is the §1.4 division — model proposes, plugin commits — with the activation gate
|
|
648
|
+
moved to machine-local, where a wrong guess costs one session and not a team's corpus.
|
|
649
|
+
|
|
650
|
+
**Activation is derived, never a curation fact.** A session rule is active iff `scope: session` ∧
|
|
651
|
+
its `session` is on the current session's ancestor path ∧ it is not revoked. No
|
|
652
|
+
`rule-status` entry is written for it: `edits/<harness>/curation.jsonl` travels in the pool, and
|
|
653
|
+
machine-local activation volume does not belong in a file reserved for human status decisions —
|
|
654
|
+
nor should it, since another machine reads only its own harness directory and would see nothing.
|
|
655
|
+
`revoke` on a session rule writes the one exception: a local fact, because the store is write-once
|
|
656
|
+
(§1.3) and a delete would be a rewrite.
|
|
657
|
+
|
|
658
|
+
**Transport exclusion happens at one point.** `planStoreToRepo` is the single store→repo path
|
|
659
|
+
mapper (`rules/<harness>/*.md → rules/<projectKey>/<harness>/*.md`), so it is the single place that
|
|
660
|
+
skips `scope: session`. No provider verb, no sync-loop edit, no second filter — proven over the
|
|
661
|
+
git integration *and* the TreeDX stub, because §5's exit criterion is transport-grade or it is
|
|
662
|
+
nothing. Session bytes therefore never become another machine's `proposed` noise, which is what
|
|
663
|
+
keeps §7.2's "stop re-reviewing a growing list" goal intact for teammates.
|
|
664
|
+
|
|
665
|
+
**Loss is expected and re-derivable.** The store root is gitignored inside a re-clonable directory
|
|
666
|
+
(`docs/architecture.md § Storage`), so deleting a checkout destroys its session rules with
|
|
667
|
+
everything else in it. The marker half re-derives deterministically by replaying the parser over
|
|
668
|
+
the durable log (invariant 1 is what makes that possible); the enrichment half re-derives by
|
|
669
|
+
re-running the pass. The correct description of that event is "the ruleset was rebuilt", never
|
|
670
|
+
"sessions were corrupted".
|
|
671
|
+
|
|
672
|
+
### 7.6 Carriage: where rules reach the model
|
|
673
|
+
|
|
674
|
+
Rules are delivered at the moments the head is rebuilt, plus on demand — **not** per request:
|
|
675
|
+
|
|
676
|
+
| Door | What renders | Mechanism |
|
|
677
|
+
|---|---|---|
|
|
678
|
+
| Continuation notice | project + session | `rulesSection` at the four door call sites (tools) — already shipped for project |
|
|
679
|
+
| Fork / branch notice | project only | the same `intent` axis that already decides plan bytes (§9–13 of `docs/plans-carriage.md`): `branch` ⇒ zero session bytes |
|
|
680
|
+
| In-place checkpoint | project + session | `rulesCarry`, the third lead section in `planSummarize`, beside `planCarry` and `plot` — both the composed and legacy paths |
|
|
681
|
+
| On demand | assembled set | `chapters_rules` tool (§8): verbatim text within budget + the withheld line |
|
|
682
|
+
|
|
683
|
+
**Why not "bundled with every request".** Two measured reasons, not stylistic. (a) Invariant 3:
|
|
684
|
+
the system prompt is process-global and `systemPrompt` is excluded from our injection list on
|
|
685
|
+
purpose — a per-session block cannot ride it, and the append-as-section precedent (`dsh-session-fork`)
|
|
686
|
+
was reviewed and rejected here. (b) Cache: on llama.cpp, the target hardware, a head-position
|
|
687
|
+
replacement reuses **nothing** (E3, r23/r28: `cacheReuse 0`), so a ruleset that churns mid-session
|
|
688
|
+
is a cache-wipe machine. Between rebuilds the human's original bytes are still live in the surface
|
|
689
|
+
anyway — they only genuinely die when the surface is replaced, and that is where they are now
|
|
690
|
+
re-stated. Plans arrived at the same conclusion for the same reason.
|
|
691
|
+
|
|
692
|
+
Empty set ⇒ the checkpoint carries zero rule bytes, which is what protects every already-committed
|
|
693
|
+
plot-carrying checkpoint (`src/engine-core.ts` records exactly that hazard).
|
|
694
|
+
|
|
695
|
+
### 7.7 Precedence and conflicts
|
|
696
|
+
|
|
697
|
+
Two live tiers means two sources of "must", and a small model degrades badly on a contradiction,
|
|
698
|
+
so precedence is stated in the block rather than left to the reader:
|
|
699
|
+
|
|
700
|
+
- **Project rules are the team's order. Session rules refine and add; they never override.**
|
|
701
|
+
- A session rule that conflicts with an active project rule is *rendered and flagged*, not
|
|
702
|
+
resolved silently — the human's options are to promote a fix to the project rule or revoke the
|
|
703
|
+
session rule.
|
|
704
|
+
- **`/chapters-rule conflicts`** spends one bounded `ctx.llm` stream call over the assembled set,
|
|
705
|
+
reusing the conversation's own system prompt as the cache-friendly prefix (`docs/contract.md`),
|
|
706
|
+
and returns a **report** of contradictory pairs with rule ids. Like `elicitedPlot`, this is an
|
|
707
|
+
explicit, config-gated exception to zero-inference. It is never a mutation: rules are write-once
|
|
708
|
+
(§1.3) and the only sanctioned change is a curation fact. Enrichment may flag the same pairs in
|
|
709
|
+
its own pass; neither path edits a rule.
|
|
710
|
+
- Command output never enters the context window (§7.2 verified), so reviewing conflicts costs a
|
|
711
|
+
human no session tokens.
|
|
712
|
+
|
|
713
|
+
### 7.8 Identity must fail loudly
|
|
714
|
+
|
|
715
|
+
The design goal "project rules carry across my checkouts of this repo" holds because identity is
|
|
716
|
+
remote-derived (§2.3). Where it does *not* hold, silence is the bug — ungoverned must never look
|
|
717
|
+
like governed:
|
|
718
|
+
|
|
719
|
+
| Divergence | Consequence | What the plugin must say |
|
|
720
|
+
|---|---|---|
|
|
721
|
+
| No readable remote ⇒ `path-derived` key | that checkout has a **private** rule namespace | `0 project rules — this checkout resolves project <key> from path <cwd>; link it with /chapters-link` |
|
|
722
|
+
| No `origin`, another remote listed first | key derives from the fork ⇒ a forked pool | report the remote string actually used |
|
|
723
|
+
| Linked via TreeDX rather than git | key is the **pool location** (`treedx/<host>/<repo>`), not the workspace remote | report `projectKeySource` so git/treedx keyed checkouts are visibly different projects |
|
|
724
|
+
| Fresh checkout, unlinked or not yet synced | no project ⇒ no rules at all | same explicit zero line, plus `/chapters-status` sync state |
|
|
725
|
+
|
|
726
|
+
`projectKeySource` (`override | remote | path-derived`) rides **`/chapters-status`** as an
|
|
727
|
+
`Identity:` line — naming the source AND where it came from (remote string / override / literal
|
|
728
|
+
path) — and the divergence branch is its own warning: `⚠ THIS CHECKOUT RESOLVES A DIFFERENT
|
|
729
|
+
PROJECT KEY … rules and chapters approved elsewhere will NOT appear here`. `/chapters-link`'s view
|
|
730
|
+
reports the same fact. The continuation head carries the zero-rules statement **only when the
|
|
731
|
+
session tier is governing that head**: bolting an "unlinked" line onto every unlinked workspace's
|
|
732
|
+
notice would spend real bytes on a warning most sessions do not need, while a head that already
|
|
733
|
+
carries local rules is exactly where a silent zero gets misread as "the team approved nothing".
|
|
734
|
+
`/chapters-rule active` prints the tier counts on demand, and `chapters_rules` names a missing
|
|
735
|
+
project in its note. A session-scoped archive depends on none of this — which is why an unlinked
|
|
736
|
+
checkout still gets its own rules.
|
|
737
|
+
|
|
738
|
+
### 7.9 Command surface, and what the agent is told
|
|
739
|
+
|
|
740
|
+
**Principle: the machinery is invisible; the query is encouraged.** Capture, activation, budgeting
|
|
741
|
+
and carriage happen without the agent doing anything — no tool it must remember to call, no
|
|
742
|
+
command it must issue, no bookkeeping it can get wrong. What it *is* told, in every rebuilt head,
|
|
743
|
+
is to **look the rules up before starting a task**: `chapters_rules` with the task description,
|
|
744
|
+
named in the notice's `## Finding things` block (§8). Everything else about rules reaches it as
|
|
745
|
+
prose it simply reads.
|
|
746
|
+
|
|
747
|
+
| Surface | Tier | Notes |
|
|
748
|
+
|---|---|---|
|
|
749
|
+
| `/chapters-rule add <category> <text>` | project | unchanged — the only human door that writes shared rule bytes |
|
|
750
|
+
| `/chapters-rule list [--scope project\|session] [--proposed] [--category <c>]` | both | table: id, scope, status, category, one-line text, provenance, source seq |
|
|
751
|
+
| `/chapters-rule active` | both | **exactly what is or will be rendered**, with the withheld count and why — the command that makes §7.3's honesty checkable |
|
|
752
|
+
| `/chapters-rule approve / revoke <id>` | project (approve) · both (revoke) | per-machine curation facts (§7.2, §15 2026-09-20) |
|
|
753
|
+
| `/chapters-rule pin / unpin <id>` | both | the §7.4 always-shown class; **human-set only** — an auto-pinned rule is an ungoverned always-shown rule |
|
|
754
|
+
| `/chapters-rule conflicts [--model <pin>]` | both | report-only (§7.7) |
|
|
755
|
+
| `chapters_rule_propose` (tool) | project | agent-facing, stays `proposed` and machine-inert until a human approves — unchanged contract, do not widen |
|
|
756
|
+
| `chapters_rules` (tool) | both | the assembly door: verbatim selected set within budget + the withheld line |
|
|
757
|
+
|
|
758
|
+
Encouragement lives in the **notice**, deliberately: notice prose costs nothing against the tape
|
|
759
|
+
corpus (measured 2026-10-11 — no committed tape prefix contains a seeded notice), while a persona
|
|
760
|
+
or tool-description edit forces a re-record of every affected project (§13). A new tool or tool
|
|
761
|
+
parameter does change the header, so this feature's schema edits are sequenced into **one**
|
|
762
|
+
re-record rather than scattered across several.
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
## 8. Search: `chapters_search`
|
|
766
|
+
|
|
767
|
+
### 8.1 Contract
|
|
768
|
+
|
|
769
|
+
```
|
|
770
|
+
chapters_search { query: string, maxTokens?: number, topic?: string, crossProject?: boolean }
|
|
771
|
+
→ { matches: [...], shown: n, total: m, budget: {requested, used, remaining} }
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
- `maxTokens` is **the agent's request** (user requirement: the model knows its own window;
|
|
775
|
+
the plugin measures and packs to it, the same way the continuation budget preflight does).
|
|
776
|
+
Default: small (config), overridable upward by the agent.
|
|
777
|
+
- **Deterministic scorer** (no model, reproducible, transparent):
|
|
778
|
+
- term hits on **topic (×2 weight), title, summary, category** (rules: category + text);
|
|
779
|
+
- **recency** boost: exponential decay on chapter date;
|
|
780
|
+
- small bonus for `kind: rule, status: core`.
|
|
781
|
+
- **Output lines:** `score | date | kind | title | path (topics: …)` — packed until
|
|
782
|
+
`maxTokens` is spent, most-relevant first; the response reports `total` so the agent can
|
|
783
|
+
re-request with a larger `maxTokens` to page (transparent, no hidden truncation).
|
|
784
|
+
- **`topic: <name>`** reads an entire index shard (a bounded, alphabetical file).
|
|
785
|
+
- **Default filter: the caller's `projectKey`** — cross-project search is an explicit
|
|
786
|
+
`crossProject: true`, so a small-model session never wanders into a foreign corpus by
|
|
787
|
+
accident.
|
|
788
|
+
- **Zero new trust surface:** every `path` returned is a plain file the existing `read` tool
|
|
789
|
+
opens (chapters in `.dsh-chapters/`, shards in `.dsh-knowledge/index/`). The search tool
|
|
790
|
+
adds a scorer and a formatter; it adds no new way to read.
|
|
791
|
+
|
|
792
|
+
**`chapters_rules` — the head's selector, assembled (§7.4, §7.9).** One door whose output is *the
|
|
793
|
+
text of the rules that apply to this task*, verbatim, within budget, plus the per-tier counts it
|
|
794
|
+
withheld. It calls `renderRulesBlock` — the renderer the continuation notice and the compaction
|
|
795
|
+
checkpoint also use — and nothing else; a rule-picker living in a door or a checkpoint is forbidden
|
|
796
|
+
by the same argument that made `exit_plan_mode` and `chapters_continue` share one builder. §7.4
|
|
797
|
+
keeps `rankEntries` (corpus ranking) and `selectRules` (head selection) as one implementation of
|
|
798
|
+
each genuinely different job.
|
|
799
|
+
|
|
800
|
+
- **It reads both corpora, which is where the trap is.** Project rules come from the pool mirror
|
|
801
|
+
under `rules/<projectKey>/<harness>/`; session rules live in the **workspace store** under
|
|
802
|
+
`rules/<harness>/` and are never staged (§7.5), so they carry no `projectKey` path prefix. The
|
|
803
|
+
`projectKey` prefix filter above must therefore not be the only scope for the session tier, or
|
|
804
|
+
session rules become invisible to the very door whose job is to find them — a silent zero, the
|
|
805
|
+
failure class §7.8 exists to eliminate. Scoping for session rules is by the ancestor-path
|
|
806
|
+
activation predicate instead, never by path prefix.
|
|
807
|
+
- **It is the encouraged step, not a required one.** Capture and carriage never depend on it
|
|
808
|
+
(§7.9); the notice names it in `## Finding things` as the thing to run before starting a task.
|
|
809
|
+
|
|
810
|
+
## 9. Redaction: minimal, deterministic, marked
|
|
811
|
+
|
|
812
|
+
User position, adopted: **credentials only, deterministic patterns only, everything else
|
|
813
|
+
byte-identical** — the character of the conversation is historical value and stays intact.
|
|
814
|
+
|
|
815
|
+
- **Patterns (deterministic):** `AKIA…` (AWS), `sk-…` (OpenAI-shape), `ghp_/gho_/
|
|
816
|
+
github_pat_`, JWT `eyJ…` shape, `Authorization: Bearer …`, `token=…` in URLs, PEM private
|
|
817
|
+
key blocks. Deliberately conservative; new patterns are config additions, not magic.
|
|
818
|
+
- **Marker format:** `⟦redacted:credential sha256=8f3a1c2b⟧` — (a) obviously not a real value
|
|
819
|
+
to any model reading the chapter, (b) **deterministic per secret** (same secret → same
|
|
820
|
+
marker, so diffs and cross-chapter greps stay stable), (c) leaks nothing but a prefix hash,
|
|
821
|
+
which is also what lets a user ask "which secrets touched my sessions" without exposing
|
|
822
|
+
them.
|
|
823
|
+
- **Applied once, at the archive chokepoint** — render → redact → write — covering chapters
|
|
824
|
+
*and* artifacts, so a value is redacted at the moment it leaves the session log, wherever it
|
|
825
|
+
appears.
|
|
826
|
+
- **Honest limit:** pattern-based redaction is a speed bump, not a guarantee. The doc that
|
|
827
|
+
ships with the repo (and the notice) says the corpus is **plaintext memory**: per-project,
|
|
828
|
+
private, user-created, with a known-small redaction carve-out.
|
|
829
|
+
|
|
830
|
+
## 10. The two-layer index
|
|
831
|
+
|
|
832
|
+
### 10.1 Layer 1 — editable, per-machine, append-only
|
|
833
|
+
|
|
834
|
+
`edits/<harness-id>/curation.jsonl` — one JSON line per index fact, **one writer per file,
|
|
835
|
+
ever** (§3.3), zero conflicts by construction:
|
|
836
|
+
|
|
837
|
+
```json
|
|
838
|
+
{"type":"topic-alias","from":"auth","to":"authentication","author":"plugin",
|
|
839
|
+
"reason":"co-occurrence in 14 chapters","session":"…","at":"…"}
|
|
840
|
+
{"type":"group","chapters":["…a","…b"],"label":"jwt-refresh-work","author":"user","at":"…"}
|
|
841
|
+
{"type":"weight","topic":"auth","weight":2,"author":"user","at":"…"}
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
Fact types: `topic-alias` (merge/rename), `topic-pin`, `group` (sibling chapters that
|
|
845
|
+
belong together — the semantic grouping that composition cannot do alone), `weight`,
|
|
846
|
+
`note`, and the two rule facts (§7.2, §7.4): `rule-status` — per-machine approve/revoke,
|
|
847
|
+
the 2026-09-20 amendment's whole mechanism — and `rule-pin`, the always-rendered class,
|
|
848
|
+
which rides a fact for the same reason status does: a rule file is write-once and the
|
|
849
|
+
append-only rule has no exceptions. Authors are `plugin` or `user` (user entries via commands, P3). This is the
|
|
850
|
+
"improve over time" layer: curation accumulates as facts, and the derived index reflects
|
|
851
|
+
them — performance improves without anyone hand-editing, and every fact is inspectable and
|
|
852
|
+
revertable in git.
|
|
853
|
+
|
|
854
|
+
### 10.2 Layer 2 — compressed, derived, incremental
|
|
855
|
+
|
|
856
|
+
`index/<topic-shard>.md` — one shard per canonical topic (alphabetical segmentation:
|
|
857
|
+
bounded files, and writes from different chapters usually touch different shards). Shard
|
|
858
|
+
content: a header line (topic, count, last-updated) then one line per chapter:
|
|
859
|
+
`date | title | path | (kind) | recency-hint`.
|
|
860
|
+
|
|
861
|
+
**Build is incremental** (user requirement: generation time must not scale with the corpus):
|
|
862
|
+
`manifest.json` records, per input file, the content hash last processed. A build touches
|
|
863
|
+
only (a) new chapters/artifacts/collections and (b) new curation entries since the last
|
|
864
|
+
build — O(delta). Because inputs are append-only, "changed" ≈ "new" in practice.
|
|
865
|
+
|
|
866
|
+
**Full rebuild stays possible** (deterministic from the tree) as the *recovery* path — a
|
|
867
|
+
machine with a lost manifest regenerates, and any machine can repair a divergent derived
|
|
868
|
+
file by rebuild-and-commit rather than merge-resolving (§3.3). Incremental is the fast path;
|
|
869
|
+
rebuild is the repair path.
|
|
870
|
+
|
|
871
|
+
## 11. Trust and security
|
|
872
|
+
|
|
873
|
+
### 11.1 Credentials
|
|
874
|
+
|
|
875
|
+
Scoped, revocable, per-repo, per-harness (§2.1). No SSH. A compromised harness exposes at
|
|
876
|
+
most one repo token, revocable in one click.
|
|
877
|
+
|
|
878
|
+
### 11.2 Data exposure
|
|
879
|
+
|
|
880
|
+
The corpus is plaintext memory (chapters are verbatim by design). Mitigations are the
|
|
881
|
+
boundaries, not illusion: per-project repos, private, user-created; redaction carve-out
|
|
882
|
+
(§9); per-harness namespacing so one machine's sessions are always attributable.
|
|
883
|
+
|
|
884
|
+
### 11.3 The blast-radius rule
|
|
885
|
+
|
|
886
|
+
Everything in the corpus that a *model* can influence and that other machines will *follow*
|
|
887
|
+
needs a human gate:
|
|
888
|
+
|
|
889
|
+
- **Chapters:** low blast radius — evidence, provenance-tagged, attributable. No gate.
|
|
890
|
+
- **Topics/vocabulary:** medium — affects search, not behavior. Plugin-autonomous with
|
|
891
|
+
git-visible curation entries (§6.3).
|
|
892
|
+
- **Rules:** high — they are prompts by another name, shared across machines. **Human
|
|
893
|
+
`approve` is mandatory** (§7.2). A proposed rule that is never approved affects nothing.
|
|
894
|
+
- **Session rules** (§15 amendment 2026-10-20) are the deliberate exception, and the exception is
|
|
895
|
+
*narrowed by scope, not by gate*: machine-local, never staged to the pool (§7.5), permanently
|
|
896
|
+
active for one session tree, and visible/reversible with one command (`active`, `revoke`).
|
|
897
|
+
Blast radius is that tree. What keeps it honest is structural, not evidentiary (§7.5, amended
|
|
898
|
+
2026-10-21): machine-local scope and never staged, a per-chapter cap of 3 plus per-tier display
|
|
899
|
+
caps, the warrant rendered beside every rule with its chapter, the agent naming which rules it
|
|
900
|
+
applied, one-command `active`/`revoke` review, `rulesAutoActivate` for anyone who wants
|
|
901
|
+
review-before-trust, and a session rule never overriding a project rule (§7.7). What is *not*
|
|
902
|
+
permitted is a session rule migrating into the shared tier on its own — promotion is a human act,
|
|
903
|
+
exactly as approval is.
|
|
904
|
+
|
|
905
|
+
Git history + provenance makes every tier auditable and every mistake revertible.
|
|
906
|
+
|
|
907
|
+
## 12. Configuration surface
|
|
908
|
+
|
|
909
|
+
No hardcoded tunables (existing hard rule). New config, all with defaults documented in
|
|
910
|
+
`dev/` templates:
|
|
911
|
+
|
|
912
|
+
| Key | Meaning | Default |
|
|
913
|
+
|---|---|---|
|
|
914
|
+
| `knowledgeRemote` | the project's repo URL (or `/chapters-link`) | none (local-only) |
|
|
915
|
+
| `projectKeyOverride` | wins over §2.3 derivation | none |
|
|
916
|
+
| `mergeThreshold` (τ) | collection-merge overlap, §4.2 | tuned in P1 |
|
|
917
|
+
| `chapterLimit` | max chapter size (tokens, heuristic) before forced split | 8000 (today's `chapterTokenTarget`) |
|
|
918
|
+
| `coreRulesBudgetTokens` | cap on the notice's core-rules section | refused-with-numbers on overflow |
|
|
919
|
+
| `syncDebounceMs` | coalesce window for pushes | 30000 |
|
|
920
|
+
| `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 |
|
|
921
|
+
| `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 |
|
|
922
|
+
| `enrichment.enabled` / `.model` | per-harness enrichment toggle + model pin (built flat: `enrichmentEnabled`, `enrichmentModel`) | enabled, conversation model |
|
|
923
|
+
| `enrichmentMaxTokens` | One auxiliary completion budget (annotation+mining, conflicts). Must cover **reasoning** tokens, not just the visible answer — measured mining usage 1.1K–3.2K per chapter. The route measured here ignores `max_tokens`, so this is headroom for strict providers, not a live limit. No call site keeps a literal | 4000 |
|
|
924
|
+
| `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 |
|
|
925
|
+
| `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 |
|
|
926
|
+
| `coreRulesBudgetTokens` | P3 §7.3 cap on the notice's core-rules block; overflow refuses with per-rule numbers | 1200 |
|
|
927
|
+
| `sessionRulesEnabled` | §7.5: the deterministic `ADD RULE:` capture path (zero model tokens) | true |
|
|
928
|
+
| `rulesMarkerPrefix` | §7.5 capture marker; matched at line start of **human-authored** messages only | `ADD RULE:` |
|
|
929
|
+
| `rulesEnrichment` | §7.5 model-derived capture: rides the §6.2 idle batch, never per-turn | true |
|
|
930
|
+
| `rulesAutoActivate` | §7.5 (amended 2026-10-21): may a mined rule govern on arrival? It **may**, by default — inferring what nobody said is the tier's purpose. Off ⇒ mined rules are `reviewOnly` and inert until `approve` | true |
|
|
931
|
+
| `rulesModel` | §7.5: `'provider/model'` for a dedicated mining call — one extra bounded call per chapter, replacing the annotation pass's answer. `''` ⇒ mine inside the enrichment call, at no extra inference cost. Runtime override `/chapters-rule model` (settings key `rules.model`) | `''` |
|
|
932
|
+
| `rulesMinedPerChapter` | §7.5 cap on mined rules per chapter — the cap counts rules **written**, not candidates examined, so a duplicate cannot eat a chapter's budget | 3 |
|
|
933
|
+
| `projectRulesDisplayCap` / `sessionRulesDisplayCap` | §7.4 per-tier count caps on rules **rendered** into a rebuilt head; anything dropped is announced by count, never silently | 12 / 12 |
|
|
934
|
+
| `rulesRelevance` | §7.4 relevance selection on; off ⇒ deterministic category→id order (the order tapes were distilled against) | true |
|
|
935
|
+
| — | §7.7 `/chapters-rule conflicts` has **no key of its own**: one bounded stream call on the conversation route the session already has (so it rides that route's cached prefix), report-only. No route yet ⇒ the verb refuses honestly. A gate of its own was rejected: a human types the verb, so `elicitedPlot`'s reason for a switch does not apply | — |
|
|
936
|
+
| `rulesCoreBonus` | P3 §8 query-time rank bonus for rules effective-core on this machine (never baked into shared shards) | 0.15 |
|
|
937
|
+
| `redaction.patterns` | additive to the built-in list (§9) | built-ins |
|
|
938
|
+
| `search.defaultMaxTokens` | default pack size for `chapters_search` | small, config |
|
|
939
|
+
| `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 |
|
|
940
|
+
| `treedxFetchTimeoutMs` / `treedxWorkspaceTtlSeconds` / `treedxLeaseRetries` / `treedxLeaseRetryDelayMs` | TreeDX transport tunables (provider.md); lease retries bound the one-writable-lease-per-branch contention window | 15000 / 900 / 3 / 1000 |
|
|
941
|
+
|
|
942
|
+
## 13. Phasing (each phase independently useful)
|
|
943
|
+
|
|
944
|
+
**P1 — Transport + deterministic corpus (zero new inference).**
|
|
945
|
+
Clone/sync loop (§3, §5); `projectKey` (§2.3); per-turn signatures at `turn/end` (§4.1);
|
|
946
|
+
topic-sequential composition replacing size-only splitting (§4.2–4.4) for the fork path
|
|
947
|
+
first (compaction spans use the same composer); derived sharded index + incremental manifest
|
|
948
|
+
(§10.2); `chapters_search` (§8); redaction at the archive chokepoint (§9); notice v2 —
|
|
949
|
+
`Project:` line, message counts, search instructions (§7.3).
|
|
950
|
+
*Exit criterion: two machines, one repo; a fork on machine A sees machine B's chapters via
|
|
951
|
+
search; sync failure degrades to local-only without breaking the fork.*
|
|
952
|
+
|
|
953
|
+
**P2 — Model enrichment + emergent vocabulary.**
|
|
954
|
+
Idle-batched enrichment pass (§6.1–6.2) with validation/fallback; provenance frontmatter
|
|
955
|
+
final; self-bootstrapping vocabulary + curation entries (§6.3); Layer 1 edit types finalized.
|
|
956
|
+
*Exit criterion: enriching a corpus is idempotent and re-runnable; vocabulary merges are
|
|
957
|
+
git-visible and revertible; disabling enrichment leaves a fully working corpus.*
|
|
958
|
+
|
|
959
|
+
**P2 STATUS (2026-09-20): BUILT.** The ladder (slice → validate → retry-once → keep
|
|
960
|
+
deterministic; `src/enrich.ts`), queue with the pluggable trigger seam (`both`/60s defaults,
|
|
961
|
+
realm owns auto, host is manual + `/chapters-enrich run|model|report`), provenance chains +
|
|
962
|
+
the body-hash-guarded frontmatter write as the ONLY sanctioned chapter mutation
|
|
963
|
+
(`enrich-store.ts`), the vocabulary canonicalizer shadow-by-default with applied merges as
|
|
964
|
+
git-visible `topic-alias` entries + derived `topics/vocabulary.json` (`vocabulary.ts` in the
|
|
965
|
+
sync pass), and index-level fragment stitching (pure read-view `stitchFragments` — adjacent
|
|
966
|
+
legacy fragments cohere into one search entry citing all members, nothing merged on disk).
|
|
967
|
+
Idempotent re-run / kill-switch parity / never-blank failure: unit + integration; the `enrich`
|
|
968
|
+
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.
|
|
969
|
+
|
|
970
|
+
**P3 — Rules.**
|
|
971
|
+
Rules-as-chapters (`kind: rule`); the in-session command family `/chapters-rule
|
|
972
|
+
list|add|approve|revoke` (§7.2 — command output verified context-free); core-rules section +
|
|
973
|
+
category index in the notice (§7.3); agent-side rule proposals (async, always `proposed`);
|
|
974
|
+
`/chapters-topic` escape hatch; topic-matched selection (§7.4 — deferred as P3b in this draft,
|
|
975
|
+
built later under P3c, not by design but by sequencing).
|
|
976
|
+
*Exit criterion: a rule proposed by a machine affects no other machine until a human types
|
|
977
|
+
`approve`; the core set overflows by refusing with numbers, never clipping.*
|
|
978
|
+
|
|
979
|
+
**P3 STATUS (2026-09-20): BUILT.** `src/rules.ts` (write-once proposed files, per-machine
|
|
980
|
+
status resolution, budgeted section with refuse-with-numbers) + `rules-commands.ts`
|
|
981
|
+
(`/chapters-rule add|list|approve|revoke`, host plane) + `chapters_rule_propose` tool +
|
|
982
|
+
notice seam (`assembleNotice` rulesSection — byte-identical ABSENT, golden-tested so the
|
|
983
|
+
tape corpus is untouched by construction) + transport (rules tree publishes; commit is
|
|
984
|
+
attempted even at copied===0 so fact-only passes travel — the change that made approvals
|
|
985
|
+
shareable) + scorer (category 1.5, rule bodies, per-machine core bonus at query time).
|
|
986
|
+
Exit-criterion tests: unit ('machine B sees nothing core'), transport-grade (three machines
|
|
987
|
+
over a real pool), e2e (`rules` project: UI add → approve → fork → the child's own log
|
|
988
|
+
carries the rule verbatim). §7.4 selection was left unbuilt in that round by design — the corpus
|
|
989
|
+
of active rules did not yet grow on its own.
|
|
990
|
+
|
|
991
|
+
**P3c — Two-tier rules (issue #50): BUILT, both capture speeds (2026-10-20).** §7.1's `scope`/`session`/
|
|
992
|
+
`derivedFrom`/`pinned` frontmatter (absent `scope` ⇒ `project`, so every pre-existing rule record
|
|
993
|
+
keeps working byte-identically); `ADD RULE:` capture at the existing `turn/end` listener;
|
|
994
|
+
`rulesEnrichment` capture (§7.5's second speed; governed on arrival by default per
|
|
995
|
+
Amendment 2026-10-21, the quote demoted to a rendered warrant); session-scope exclusion in
|
|
996
|
+
`planStoreToRepo`; the three render classes and per-tier caps in `buildRulesSection`; the third
|
|
997
|
+
checkpoint lead section; `intent` filtering at the four door sites; `chapters_rules`; the
|
|
998
|
+
`active`/`pin`/`conflicts` verbs; and §7.8's loud-identity lines.
|
|
999
|
+
*Exit criteria (each must be a red row before it is a fact):* an in-place compaction renders active
|
|
1000
|
+
rules verbatim into the checkpoint; `chapters_continue` carries project **+ session** while
|
|
1001
|
+
`chapters_fork` carries project only; `ADD RULE:` yields an active rule at **zero** model tokens;
|
|
1002
|
+
no session rule file appears in the pool over **either** provider; an unlinked or `path-derived`
|
|
1003
|
+
checkout says it rendered zero rules; the empty set still renders byte-identical ABSENT; selection
|
|
1004
|
+
is byte-stable across two machines with the same inputs.
|
|
1005
|
+
Build status, honestly split (each ✅ is a red-then-green row, not an intention):
|
|
1006
|
+
✅ `scope`/`session`/`derivedFrom`/`pinned` frontmatter with absent-means-`project` byte-compat;
|
|
1007
|
+
✅ tier-aware `buildRulesSection` with pinned→relevance→category/id selection, per-tier caps and the
|
|
1008
|
+
announced withheld count; ✅ `ADD RULE:` capture at the existing `turn/end` listener (zero model
|
|
1009
|
+
tokens); ✅ the one `planStoreToRepo` exclusion over **both** providers; ✅ `rulesCarry` as the third
|
|
1010
|
+
checkpoint lead section on both paths; ✅ `intent` filtering at the doors through one
|
|
1011
|
+
`renderRulesBlock`, with a source-shape row forbidding a door-local picker; ✅ `chapters_rules`;
|
|
1012
|
+
✅ `active`/`pin`/`unpin`/`--scope`/`conflicts` and revoke-on-session; ✅ §7.8 identity lines at
|
|
1013
|
+
`/chapters-status` and `/chapters-link`. ❌ the §6.1 `rules: []` enrichment field and its quote gate.
|
|
1014
|
+
❌ the browser plane: `npm run test:e2e:replay` **refuses by name** on the pinned host (it predates
|
|
1015
|
+
preset declaration rows — `tests/e2e/boot.ts:130`), so the `rules` journey and the R-rows over a
|
|
1016
|
+
live model are unverified, and the new tool schema is a standing re-record owed the moment
|
|
1017
|
+
`dev/dsh-host-lock` moves to 0.2.x. Sequencing then stays as planned: **one** whole-project
|
|
1018
|
+
re-record, never a series of per-spec ones (the 2026-09-23 lesson: a tape records the context it was
|
|
1019
|
+
distilled in).
|
|
1020
|
+
|
|
1021
|
+
## 14. Open risks (honest register)
|
|
1022
|
+
|
|
1023
|
+
| Risk | State |
|
|
1024
|
+
|---|---|
|
|
1025
|
+
| Pure-JS git SDK performance at repo scale; SSH-only remotes | accepted (user-directed); CLI fallback config-gated |
|
|
1026
|
+
| Deterministic topic quality on pure-prose (non-code) sessions | degrades to today's time/size behavior; enrichment improves recall later |
|
|
1027
|
+
| Pattern redaction is a speed bump, not a guarantee | documented in-repo; patterns are config-extensible |
|
|
1028
|
+
| Single-slot model server contention | enrichment is idle-batched and per-harness disableable; signatures are model-free |
|
|
1029
|
+
| Cross-model topic vocabulary drift | canonicalizer + provenance + re-run (§6.3) — mitigated by design, observed in practice at P2 |
|
|
1030
|
+
| `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 |
|
|
1031
|
+
| 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 |
|
|
1032
|
+
| 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 |
|
|
1033
|
+
| P3c: a model-captured rule encodes a **mis-generalization** permanently for the session tree ("never run two GPU suites at once" → "never run tests concurrently") | narrowed by the verbatim-quote activation gate (§7.5), by scope (local, never the shared tier), and by one-command visibility (`active`, `revoke`); the residual risk is a wrong rule you did not look at — which is why the count is capped and the withheld line is mandatory |
|
|
1034
|
+
| P3c: relevance selection **ranks out the load-bearing rule**, reintroducing "lost in the noise" inside the fix for it | the pinned class is never subject to selection (§7.4); the withheld count renders even when nothing is selected |
|
|
1035
|
+
| P3c: a new tool/param changes every replay prefix | one whole-project re-record, sequenced deliberately; notice prose stays free (measured 2026-10-11) |
|
|
1036
|
+
| P3c: per-checkout identity divergence silently forks a project's rule namespace (§7.8) | stated, not assumed: `projectKeySource` reported in the zero-rules line, `active`, and `/chapters-status` |
|
|
1037
|
+
|
|
1038
|
+
## 15. Amendments
|
|
1039
|
+
|
|
1040
|
+
This record governs implementation. An amendment is a **human decision** — discussed,
|
|
1041
|
+
accepted, and edited in with a commit message stating the reason — because the record's value
|
|
1042
|
+
is that every architectural fact in it was chosen, not generated. Machine-derived knowledge
|
|
1043
|
+
lives in the *corpus* (chapters, curation entries, vocabulary with provenance); machine
|
|
1044
|
+
opinions about *this document* do not.
|
|
1045
|
+
|
|
1046
|
+
### Amendment 2026-09-21 — transport is pluggable; git is one provider
|
|
1047
|
+
|
|
1048
|
+
Accepted with the human-approved provider-plugin plan. §3.1/§3.2 stand as the git
|
|
1049
|
+
provider's rules; they are now one implementation behind a seam
|
|
1050
|
+
(`src/provider.ts`, contract in [provider.md](provider.md)). The six transport verbs
|
|
1051
|
+
`runSync` already injected (`GitDriver`) define the provider interface; a second
|
|
1052
|
+
implementation, `kind: 'treedx'`, transports the same mirror layout over TreeDX's
|
|
1053
|
+
no-clone HTTP API (`src/treedx/`), selected by the `treedx+<url>/<repo>` remote
|
|
1054
|
+
scheme (`/chapters-link mode 4`; §12 keys `knowledgeProvider`, `treedx*`). What the
|
|
1055
|
+
amendment changes in the record, precisely:
|
|
1056
|
+
|
|
1057
|
+
- §3.1 holds unchanged: the store is truth, the mirror is transport — in TreeDX
|
|
1058
|
+
mode the mirror is a provider-materialized plain directory instead of a clone.
|
|
1059
|
+
Search, rules, index build, notice assembly, and the `read` path see no
|
|
1060
|
+
difference.
|
|
1061
|
+
- §5.2's divergence rule generalizes: any head movement detected at pull or commit
|
|
1062
|
+
routes through the same rebuild-the-mirror-from-the-store path. Never a merge.
|
|
1063
|
+
- §2.3's identity rule extends: TreeDX keys derive from the canonical
|
|
1064
|
+
`host[:port]/repo` string under a `treedx/` namespace prefix, so a service at a
|
|
1065
|
+
git-colliding URL is never the same pool.
|
|
1066
|
+
- §11.3's blast-radius gate is provider-independent: per-machine rule approval is
|
|
1067
|
+
a local curation fact — no server, federated or otherwise, can pre-approve.
|
|
1068
|
+
- §5.3's degradation semantics are identical, plus one auth class (a rejected
|
|
1069
|
+
token surfaces a re-link instruction; it never enters the conversation path).
|
|
1070
|
+
|
|
1071
|
+
The TreeDX backend's API-level facts (and what remains [LIVE-GATED] pending a real
|
|
1072
|
+
container) are pinned in [spikes/treedx/FINDINGS.md](../spikes/treedx/FINDINGS.md);
|
|
1073
|
+
transport-grade proof runs over the stub service (`treedx-{provider,sync,link}.test.ts`)
|
|
1074
|
+
and the live exit criterion over `treedx-live.test.ts` when `scripts/treedx-local.sh`
|
|
1075
|
+
brings the service up.
|
|
1076
|
+
|
|
1077
|
+
### Amendment 2026-09-21 — host-injected context is not conversation
|
|
1078
|
+
Accepted with the same plan (the human directive: the knowledge layer must not
|
|
1079
|
+
depend on any project's instruction files — every target project ships its own,
|
|
1080
|
+
unpredictably). The archive guarantee restates: **every byte of the CONVERSATION
|
|
1081
|
+
remains retrievable** in chapters or artifacts; host-injected context — project
|
|
1082
|
+
instruction files (`agent-instructions` events and the `instructions from:`
|
|
1083
|
+
reminder form), the runtime-context snapshot, the skill catalog, and this plugin's
|
|
1084
|
+
own TOC notice — is re-derivable project state and is EXCLUDED at the render
|
|
1085
|
+
chokepoint (`src/injections.ts`), each omission leaving a countable marker.
|
|
1086
|
+
`stats.messages` counts conversation only, so TOC `(N msgs)` stays honest. The
|
|
1087
|
+
durable session log is untouched by this (invariant: append-only) — originals live
|
|
1088
|
+
in the parent session; the screen is archive-time bytes, applied before redaction
|
|
1089
|
+
(§9) at the same chokepoint. The signature layer has applied the same predicate
|
|
1090
|
+
since r28; the two now share one implementation so they can never drift. The
|
|
1091
|
+
model-tape masking rule (AGENTS-BLIND, 2026-09-20) is the test-side half of the
|
|
1092
|
+
same position: development artifacts, never product facts.
|
|
1093
|
+
|
|
1094
|
+
### Amendment 2026-09-20 — rule approval is a per-machine curation fact
|
|
1095
|
+
|
|
1096
|
+
Accepted as the P3 plan's single decision point (human-approved with the plan). §7.2's
|
|
1097
|
+
file-status rewrite replaced by `rule-status` facts in each machine's own edits file
|
|
1098
|
+
(see §7.2 note for the reason). Everything else in §7 stands.
|
|
1099
|
+
|
|
1100
|
+
### Amendment 2026-10-10 — plans are documents of a third kind
|
|
1101
|
+
|
|
1102
|
+
Human decision, recorded here because it widens the corpus: besides transcript chapters and curated
|
|
1103
|
+
rules, a workspace's shared knowledge now also carries **plan documents** — one file per
|
|
1104
|
+
`exit_plan_mode` submission, `kind: plan`, written into `chapters/` so the §4 transport, the §5
|
|
1105
|
+
mirror and the §6 index carry and rank them with no special casing.
|
|
1106
|
+
|
|
1107
|
+
Rules of the amendment, all enforced by tests (verify.md P12–P14):
|
|
1108
|
+
|
|
1109
|
+
- **A plan document's body is the plan, byte for byte.** It is never a summary of one, and the
|
|
1110
|
+
enrichment ladder (§6.4) refuses to annotate it: a directive is approved text, and paraphrasing
|
|
1111
|
+
an order is the drift the whole store exists to prevent. The guard reads the file's own
|
|
1112
|
+
`kind: plan` frontmatter, so renaming cannot disarm it.
|
|
1113
|
+
- **Superseding never overwrites.** A revised plan is a new document; the earlier one keeps its
|
|
1114
|
+
bytes and its status changes only in the sense that the newest submission is operative. Within
|
|
1115
|
+
their classes the append-only rules stand; the one sanctioned deletion is operator removal of bad
|
|
1116
|
+
chapters (#255), which takes a removed chapter's plan documents and orphaned artifacts with it.
|
|
1117
|
+
- **Numbering is its own** (`nextPlanNumber`, registry-allocated under a distinct attempt key,
|
|
1118
|
+
never a directory scan), so a plan never consumes a chapter number, never reorders the TOC, and
|
|
1119
|
+
never collides with a concurrent sibling. Files are `plan-<NNN>-<slug>.md` precisely so a listing
|
|
1120
|
+
cannot read as two chapters claiming the same number.
|
|
1121
|
+
- **Redaction is unchanged and applies identically** (§9): the chokepoint runs over plan text on the
|
|
1122
|
+
way to both the blob and the document, so a credential pasted into a plan never reaches the pool.
|
|
1123
|
+
The one asymmetry a reader should know: a plan is *deliberately* a human-facing directive, so it
|
|
1124
|
+
keeps its own `sha256` (the blob's hash, identical bytes) plus `bodySha256` (the §2.2 anchor
|
|
1125
|
+
convention) — two handles, one purpose each, provably the same content.
|
|
1126
|
+
- **Host-injected context is still not conversation** — but with the one named exception recorded in
|
|
1127
|
+
`docs/plans-carriage.md` § 13: the harness's own `plugin: plan-mode / form: notice` mode-flip
|
|
1128
|
+
sentence is session state worth keeping, so it archives verbatim. Instruction files, runtime
|
|
1129
|
+
snapshots and skill catalogs screen exactly as before, and the topic-signature layer (§4.1)
|
|
1130
|
+
keeps the strict predicate so injected text cannot move a chapter boundary.
|
|
1131
|
+
|
|
1132
|
+
Design detail and measurements: `docs/plans-carriage.md` §§ 9–13.
|
|
1133
|
+
|
|
1134
|
+
### Amendment 2026-10-11 — the index answers at home, and the child is told the verbs
|
|
1135
|
+
|
|
1136
|
+
Human directive: *"make sure everything is indexed correctly so that a new session can find the
|
|
1137
|
+
plans, rules, and other important details when it is asked."* Auditing that claim found the index was
|
|
1138
|
+
sound and the **access path was not**, in two specific ways — both now closed and pinned:
|
|
1139
|
+
|
|
1140
|
+
1. **`chapters_search` required a pool mirror.** With nothing linked it returned an empty list and a
|
|
1141
|
+
suggestion to go link a remote — while every chapter, every `kind: plan` document and every rule sat
|
|
1142
|
+
in the workspace store, indexable. Search now has two doors over **one shared scorer**
|
|
1143
|
+
(`rankEntries`): the materialized mirror when a project is linked, and `buildLocalCorpus` over the
|
|
1144
|
+
store when it is not. Deliberate details:
|
|
1145
|
+
- **artifacts are not indexed** (they answer to `chapters_artifact`; a blob indexed twice is a second
|
|
1146
|
+
index, which §6 forbids);
|
|
1147
|
+
- **the door is named in the answer** — "searched this machine's store (N indexed document(s); no pool
|
|
1148
|
+
mirror …)" — because a session must never read *this machine only* as *the whole project*;
|
|
1149
|
+
- **"nothing matched" and "nothing here" are different results** (`empty` + `documents`), since they
|
|
1150
|
+
imply different next actions;
|
|
1151
|
+
- if the pool yields nothing, the local store is consulted before answering empty.
|
|
1152
|
+
2. **The continuation notice taught only `read`.** A child was told chapters exist and how to open one;
|
|
1153
|
+
it was never told `chapters_search` or `chapters_artifact` exist. An installed tool that is never
|
|
1154
|
+
mentioned is a tool a small model will not reach for, so the notice gains a three-line
|
|
1155
|
+
`## Finding things` block naming all three verbs, plus the one fact a child cannot infer — that plans
|
|
1156
|
+
are searchable **as plans** (`kind: plan`) and rules arrive from other machines. `retrievalHints`
|
|
1157
|
+
(default on) is the switch; off, the notice is byte-identical to the previous shape.
|
|
1158
|
+
|
|
1159
|
+
Measured, not assumed: no committed tape contains a seeded notice anywhere in its prefixes (checked
|
|
1160
|
+
entry-by-entry across the corpus), so the notice change costs no re-record — and the eight-project chain
|
|
1161
|
+
replays green with it. That is luck of coverage rather than design: no current journey makes a
|
|
1162
|
+
*continuation child* speak, which is why the child-facing prose is now covered by golden rows here plus
|
|
1163
|
+
the live-model leg, and why a child that answers a question in a browser is on the open list.
|
|
1164
|
+
|
|
1165
|
+
Also landed: **the kernel-drift gate** (`tests/unit/host-seed-contract.test.ts`) — the seeded
|
|
1166
|
+
`plan/mode` continuation depends on the host knowing that type and treating it as log-only, read from the
|
|
1167
|
+
kernel's own exported predicates (`KNOWN_SESSION_EVENT_TYPES`, `isSurfaceEligibleType`,
|
|
1168
|
+
`isAppendSurfaceEvent`) rather than reimplemented, so a kernel change surfaces as a red row instead of a
|
|
1169
|
+
child that silently stops being gated.
|
|
1170
|
+
|
|
1171
|
+
### Amendment 2026-10-20 — rules become two tiers: project (shared, approved) and session (local, derived)
|
|
1172
|
+
|
|
1173
|
+
Human directive, in the round that planned it: *"I end up giving the AI agent a bunch of rules
|
|
1174
|
+
embedded in my requests… these rules just get lost in the noise."* Auditing where they actually get
|
|
1175
|
+
lost found one concrete hole, not a vague one: the in-place checkpoint composes
|
|
1176
|
+
`leadSections = [planSection, plotSection]` (`src/engine-core.ts`). **A plan survives a compaction
|
|
1177
|
+
and a plot survives a compaction; a rule does not.** That, plus a rule set that only ever reaches a
|
|
1178
|
+
child through the continuation notice with no relevance selection, is the whole motivation.
|
|
1179
|
+
|
|
1180
|
+
Six decisions, each narrowing a temptation the discussion produced:
|
|
1181
|
+
|
|
1182
|
+
1. **Two tiers, one store** (§7.1). Session rules are `kind: rule` files in the same tree with
|
|
1183
|
+
`scope: session` — same numbering, same write-once immutability, same `list`, same index. Absent
|
|
1184
|
+
`scope` means `project`, so every rule record on disk before this amendment keeps working with
|
|
1185
|
+
unchanged bytes. There is no second rules subsystem to drift.
|
|
1186
|
+
2. **The global tier was not built, because it already existed.** The request was "carry across my
|
|
1187
|
+
checkouts of this repository", and `projectKey` is UUIDv5 over the *canonicalized remote* (§2.3)
|
|
1188
|
+
with `harnessId` defaulting to the hostname — so several checkouts of one repo already resolve to
|
|
1189
|
+
one rule namespace on one machine. The work became making the four ways that identity silently
|
|
1190
|
+
diverges say so (§7.8) instead of inventing a third namespace with a much larger blast radius.
|
|
1191
|
+
3. **Session activation is derived, never a curation fact** (§7.5). Active iff `scope: session` ∧
|
|
1192
|
+
its session is on the current ancestor path ∧ not revoked. `edits/<harness>/curation.jsonl` is
|
|
1193
|
+
reserved for human status decisions *and it travels in the pool* — machine-local activation volume
|
|
1194
|
+
does not belong there. The 2026-09-20 amendment (approval is a per-machine fact, never a file
|
|
1195
|
+
rewrite) is **not** relaxed: it still governs every byte of the shared tier.
|
|
1196
|
+
4. **Model-captured rules may auto-activate, and only on a verbatim quote** (§7.5, behind
|
|
1197
|
+
`rulesEnrichment`, default off — *decided and specified; the P3c build shipped the marker path
|
|
1198
|
+
first, so this half is still ahead of the code*). The rejected framing was "the model writes its own governing
|
|
1199
|
+
rules with no human gate"; the accepted one is that the machine-local, one-command-reversible,
|
|
1200
|
+
never-staged scope makes the blast radius one session tree — so the gate moves from *who clicks*
|
|
1201
|
+
to *what the rule is derived from*. An inferred rule with no human sentence behind it waits in
|
|
1202
|
+
`list --proposed`. And capture stays off the critical path entirely: the `ADD RULE:` marker is
|
|
1203
|
+
deterministic and costs zero model tokens (invariant 1), while the model pass rides §6.2's idle
|
|
1204
|
+
batch, because the local server is single-slot and per-turn work queues against the user's own
|
|
1205
|
+
turns.
|
|
1206
|
+
5. **Rules are carried at the moments the head is rebuilt, never injected per request** (§7.6).
|
|
1207
|
+
Two measured reasons, not taste: invariant 3 (the system prompt is process-global and
|
|
1208
|
+
`systemPrompt` is excluded from our injections on purpose), and the llama.cpp number from
|
|
1209
|
+
E3/r23-r28 — a head-position replacement reuses **nothing** (`cacheReuse 0`), so a ruleset that
|
|
1210
|
+
churns mid-session is a cache-wipe machine on exactly the target hardware. Between rebuilds the
|
|
1211
|
+
human's own bytes are still in the surface; they die only when the surface is replaced, which is
|
|
1212
|
+
where they are now re-stated. `chapters_continue` carries both tiers, `chapters_fork` carries
|
|
1213
|
+
project only — the *same* `intent` axis that already decides plan bytes (`docs/plans-carriage.md`
|
|
1214
|
+
§§ 9–13), not a new distinction.
|
|
1215
|
+
6. **§7.4 selection is raised from "P3b later" to required, with a floor.** Automatic capture means
|
|
1216
|
+
the active set now grows without a human noticing, so always-render-everything stops being
|
|
1217
|
+
affordable: pinned (human-set, always) → relevance-selected (the existing `search.rankEntries`,
|
|
1218
|
+
never a second ranker) → index-only. Per-tier count caps are **announced**, because selection
|
|
1219
|
+
that can silently drop the load-bearing rule would rebuild the original bug inside the fix —
|
|
1220
|
+
and a cap that *truncates* would violate §1.2, so the token ceiling still refuses with numbers
|
|
1221
|
+
and the count ceiling selects with a visible count. Selection must be a pure function of stored
|
|
1222
|
+
inputs (no clock, no readdir order) or two machines disagree about what the agent was told and
|
|
1223
|
+
the tape corpus cannot be stable.
|
|
1224
|
+
|
|
1225
|
+
Also recorded as accepted consequences: session-scope exclusion happens at the one store→repo mapper
|
|
1226
|
+
(`planStoreToRepo`) and is proven over **both** providers; a session rule never overrides a project
|
|
1227
|
+
rule, and conflicts are reported (`/chapters-rule conflicts`, one bounded stream call in the
|
|
1228
|
+
`elicitedPlot` family) rather than resolved (§7.7); losing a checkout loses its session rules, which
|
|
1229
|
+
is *re-derivation*, not corruption (§7.5, and the storage warning in `AGENTS.md`); and the agent-
|
|
1230
|
+
facing surface stays minimal by decision — capture and carriage are invisible to the model, and the
|
|
1231
|
+
one thing it is told to do is **look the rules up before starting a task** (§7.9), because a tool an
|
|
1232
|
+
instruction never names does not exist to a small model.
|
|
1233
|
+
|
|
1234
|
+
Specification: §7.1 and §7.3–§7.9. Build status and exit criteria: §13 P3c (specified, not yet
|
|
1235
|
+
built at the time of this amendment — this entry is the decision record, not a claim of behavior).
|
|
1236
|
+
|
|
1237
|
+
### Amendment 2026-10-21 (b) — rules reach agents, and agents reach rules (#52)
|
|
1238
|
+
|
|
1239
|
+
Two user decisions in the same review round, both about the gap between "the rule exists" and
|
|
1240
|
+
"the agent in front of the work is told about it".
|
|
1241
|
+
|
|
1242
|
+
**1. The preset persona is the instruction channel.** Nothing told a brand-new session, or a delegated
|
|
1243
|
+
subagent, to use `chapters_rules` / `chapters_search`: a plugin must not contribute a system-prompt
|
|
1244
|
+
section (§1.3 / invariant 3), and the continuation notice only exists for continuation children. The
|
|
1245
|
+
sanctioned per-composition channel is the preset's OWN persona row — `@deepseek-ai/dsh-persona`,
|
|
1246
|
+
prefix/suffix — the mechanism already carrying the `PLOT:` discipline. The shipped suffix now adds a
|
|
1247
|
+
short "Chapters memory and rules" section: run `chapters_rules` with the task description before a
|
|
1248
|
+
task and NAME the rules you followed (or say none); for earlier work, decisions, or bug history run
|
|
1249
|
+
`chapters_search` FIRST instead of re-reading files or asking the human; `chapters_artifact` holds the
|
|
1250
|
+
oversized bytes; and a delegated child is covered by the same rules and the same archive. It is
|
|
1251
|
+
per-preset, so a session on `standard` sees none of it. Two couplings are pinned by behaviour, not
|
|
1252
|
+
comment: the suffix must remain matched by the plot template-echo guard (else every session inherits
|
|
1253
|
+
its own instruction as a "plot"), and no new prompt variable may appear (`{{model}}` only — an
|
|
1254
|
+
unregistered variable throws at assembly and kills every turn of every chapters session).
|
|
1255
|
+
|
|
1256
|
+
**2. Rule ancestry crosses the delegation edge.** The registry is the sole authority on continuation
|
|
1257
|
+
lineage and deliberately ignores the kernel header's `parentSession` — correct for numbering and the
|
|
1258
|
+
TOC, and it left a whole session shape with ZERO rules: a delegated subagent was an unrelated root by
|
|
1259
|
+
that view. So `session-rules.ruleAncestorSet` walks BOTH edges (registry `parentSession` ∪ header
|
|
1260
|
+
`parentSession`) transitively, READ-ONLY, writing nothing, and still only UPWARD — a child's
|
|
1261
|
+
inferences never govern the session that delegated them. Bounded (64 expansions) and loud when the
|
|
1262
|
+
cap bites (`lineage walk hit its cap…` inside the block, `LINEAGE TRUNCATED:` in `/chapters-rule
|
|
1263
|
+
active`); an unreadable header contributes no edge rather than breaking a turn. One walk, one builder:
|
|
1264
|
+
every door, the checkpoint and the surface call `ruleAncestorSet`, and an anti-drift row fails if a
|
|
1265
|
+
plane computes ancestry by hand again — hand-built ancestry is precisely how the gap appeared.
|
|
1266
|
+
|
|
1267
|
+
Consequence, recorded before anyone is surprised by it: this changes a PRODUCT PROMPT (the persona
|
|
1268
|
+
suffix) and adds a tool, so the browser-plane tapes owe one whole-project re-distill when the host
|
|
1269
|
+
lock moves to 0.2.x — one re-record pays both debts. Until that runs, no claim about browser-plane
|
|
1270
|
+
rule carriage is verified.
|
|
1271
|
+
|
|
1272
|
+
### Amendment 2026-10-21 — a mined rule is governed by POLICY, not by proof that a human said it
|
|
1273
|
+
|
|
1274
|
+
Human directive, one day after the previous entry and against the feature as built: *"I don't like
|
|
1275
|
+
your approach of making mined rules governing if it appears verbatim in my own words… we need to
|
|
1276
|
+
assume that the human may not know, which is why they may be using AI in the first place."* The
|
|
1277
|
+
verbatim-quote gate built under §7.5 is **removed as a condition on activation**.
|
|
1278
|
+
|
|
1279
|
+
What was wrong was a category error, and it is worth stating plainly because it is the kind of
|
|
1280
|
+
error this project keeps making: the gate confused **where a rule came from** with **whether it is
|
|
1281
|
+
allowed to govern**. Requiring a human sentence behind every mined rule meant the model could only
|
|
1282
|
+
ever re-state what had already been said — which is exactly what the deterministic `ADD RULE:`
|
|
1283
|
+
marker does better, because there you know precisely what you got. A tier that can only confirm the
|
|
1284
|
+
known is not inference; it is a paraphrase of the marker with extra latency. And the honest failure
|
|
1285
|
+
mode it was built to prevent — the model inventing a rule the human never sanctioned — is the same
|
|
1286
|
+
failure mode it was built to *cause*: the useful case is the standing order nobody has articulated
|
|
1287
|
+
yet, and the value of asking a model to look is precisely that it may see one.
|
|
1288
|
+
|
|
1289
|
+
**What replaces it.** Four things, all structural — and the reason all four are needed is that
|
|
1290
|
+
evidence is no longer one of them:
|
|
1291
|
+
|
|
1292
|
+
1. `rulesAutoActivate` (default **on**) is the single switch. Off ⇒ every mined rule is written
|
|
1293
|
+
`reviewOnly: true` and stays inert until this machine approves it. Review-before-trust is a
|
|
1294
|
+
posture a team can choose; it is no longer the default posture.
|
|
1295
|
+
2. The quote check survives, **demoted to a label**: `stated` (a human sentence backs it) vs
|
|
1296
|
+
`inferred` (the model judged it, nobody said it). It renders beside the rule text *with the
|
|
1297
|
+
chapter it came from*, in the head, so no inferred rule can govern anonymously. Assistant prose
|
|
1298
|
+
stays inadmissible even as a label — a "quote" found only in the model's restatement is
|
|
1299
|
+
`inferred`, not `stated`.
|
|
1300
|
+
3. The agent must **name by id the session rules that shaped its reply** (§7.3's speak-up line),
|
|
1301
|
+
or say none did. This is the mechanism the human asked for instead of a gate, and it is better:
|
|
1302
|
+
a wrong inference becomes visible at the moment it acts, to the only party who can revoke it,
|
|
1303
|
+
rather than being prevented in advance by a test the model can satisfy by quoting badly.
|
|
1304
|
+
4. The caps stay: 3 mined rules per chapter (counting rules **written**), per-tier display caps
|
|
1305
|
+
with the withheld count announced, machine-local scope, never staged to the pool, `revoke` one
|
|
1306
|
+
command away.
|
|
1307
|
+
|
|
1308
|
+
Two things deliberately **not** relaxed: a session rule never overrides a project rule (§7.7), and
|
|
1309
|
+
a session rule never becomes a project rule without a human (`add`) — promotion is still a human
|
|
1310
|
+
act. The tier's warrant for governing itself got broader; its warrant for governing *anyone else*
|
|
1311
|
+
did not change at all.
|
|
1312
|
+
|
|
1313
|
+
**Honest limits, recorded at the time of writing.** No live model has mined a real chapter yet (the
|
|
1314
|
+
dev box's GPU driver was unreachable), so how often a 4B produces a *correct* inference — versus a
|
|
1315
|
+
confident wrong one that now governs by default — is unmeasured. The default is a bet on the
|
|
1316
|
+
visibility mechanisms above, and `docs/verify.md` R5 marks the quality half unverified. Anyone
|
|
1317
|
+
revisiting this should look at the ratio of `stated` to `inferred` on real archives first; if
|
|
1318
|
+
inferences are wrong more often than a human catches them in replies, the fix is
|
|
1319
|
+
`rulesAutoActivate: false`, not a new gate.
|
|
1320
|
+
|
|
1321
|
+
Implementation: `src/rules.ts` (`basis`/`chapter`/`reviewOnly`, `sessionRuleActive`),
|
|
1322
|
+
`src/session-rules.ts` (`captureMinedRules` policy + classification), `src/enrich-wire.ts`
|
|
1323
|
+
(`rulesAutoActivate`, `rulesModel`, dedicated mining call), `src/rules-commands.ts` (`model` verb,
|
|
1324
|
+
`active` posture). Reversal pinned by a test row, not just described: *"THE REVERSAL, PINNED: an
|
|
1325
|
+
inferred rule governs on arrival — no human said it."*
|
|
1326
|
+
|
|
1327
|
+
---
|
|
1328
|
+
|
|
1329
|
+
*Cross-references: invariants and the four invariants — `AGENTS.md`; budget math —
|
|
1330
|
+
`docs/contract.md` (continuation budget) and `docs/architecture.md` (storage); the engine
|
|
1331
|
+
seam the composer hooks into — `docs/host-compaction-seam.md`; measured claims cited as
|
|
1332
|
+
`rNN` — `spikes/probe/FINDINGS.md`.*
|