@treeseed/dsh-chapters 0.1.1 → 0.1.3
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/README.md +196 -609
- package/docs/verify.md +1 -0
- package/lib/engine-core.d.ts +0 -6
- package/lib/engine-core.js +33 -9
- package/lib/engine-core.js.map +1 -1
- package/lib/engine.js +51 -18
- package/lib/engine.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,644 +1,231 @@
|
|
|
1
1
|
# dsh-chapters
|
|
2
2
|
|
|
3
|
-
A DeepSeek Harness plugin for
|
|
3
|
+
**A [DeepSeek Harness](https://www.deepseek.com) plugin for lossless context relief and a shared
|
|
4
|
+
project knowledge layer.** Published on npm as [`@treeseed/dsh-chapters`](https://www.npmjs.com/package/@treeseed/dsh-chapters);
|
|
5
|
+
source lives at [treeseed-ai/dsh-chapters](https://github.com/treeseed-ai/dsh-chapters).
|
|
4
6
|
|
|
5
|
-
When a long conversation gets expensive,
|
|
6
|
-
store and opens a **new session whose entire content is a Table of Contents** of that
|
|
7
|
-
model reloads any chapter on demand with the `read` tool. The original
|
|
8
|
-
|
|
7
|
+
When a long conversation gets expensive, dsh-chapters archives it as **verbatim Markdown chapters** in
|
|
8
|
+
a project store and opens a **new session whose entire content is a Table of Contents** of that
|
|
9
|
+
archive. The model reloads any chapter on demand with the ordinary `read` tool. The original
|
|
10
|
+
conversation is never rewritten, its prompt cache stays intact, and the index costs **zero model
|
|
11
|
+
tokens** — chapters are written from the session log (a file copy), not summarized by an LLM.
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
> **`dsh-chapters`**, which names the artifact and claims nothing false. Tools are `chapters_segment` and
|
|
14
|
-
> `chapters_continue`. The full reasoning, including the two candidates that lost, is in
|
|
15
|
-
> [docs/architecture.md](docs/architecture.md#naming). The one thing not yet renamed is the repository
|
|
16
|
-
> folder itself, which is cosmetic — see that section.
|
|
13
|
+
Archived knowledge is not stranded per-machine: link the store to a shared upstream — a **git pool**
|
|
14
|
+
or a **TreeDX** service — and every linked workspace gets a two-layer derived index, topic
|
|
15
|
+
vocabulary, cross-machine search (`chapters_search`), and a per-machine rules ledger.
|
|
17
16
|
|
|
18
17
|
---
|
|
19
18
|
|
|
20
|
-
##
|
|
19
|
+
## Purpose — what it fixes, and what it does not claim
|
|
21
20
|
|
|
22
|
-
The
|
|
23
|
-
The two problems that actually hurt, on local hardware with slow prefill:
|
|
21
|
+
The two things that actually hurt on long sessions, especially with local models and slow prefill:
|
|
24
22
|
|
|
25
|
-
1. **
|
|
26
|
-
|
|
27
|
-
2. **The
|
|
28
|
-
|
|
23
|
+
1. **Lossy memory.** A compaction summary drops the exact flag, the line number, the error text —
|
|
24
|
+
and leaves the model no handle to go get them back.
|
|
25
|
+
2. **The summarization tax.** Producing a summary prefills the whole history into one auxiliary
|
|
26
|
+
call — minutes of wall-clock on local hardware, *again every time the window fills*.
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
| 100K | **55 min** | 17 min | 3.5 min |
|
|
28
|
+
dsh-chapters removes the summarization step from the critical path entirely. Measured on a
|
|
29
|
+
32K-capped model: ~102 tokens of index where the equivalent transcript ran ~2,476, and zero
|
|
30
|
+
inference spent producing it.
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
**Be clear about what this does not buy you:** it is not cheaper caching. The continuation session
|
|
33
|
+
pays one cold prefill (system prompt + TOC + handoff note), the same cost class a post-compaction
|
|
34
|
+
request pays. What you gain instead is *verbatim* archive (a reload returns real text, not a
|
|
35
|
+
paraphrase), an *untouched* parent session that keeps its cache and stays usable, reversibility
|
|
36
|
+
(abandon the child, keep working in the parent), and branchable history.
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
equivalent transcript ran ~2,476.
|
|
41
|
-
|
|
42
|
-
> **The point is to make small models more capable, not merely longer-lived:** easily retrievable memory,
|
|
43
|
-
> no summarization tax, and a conversation that can keep going and going and going until the objective is
|
|
44
|
-
> actually finished.
|
|
45
|
-
|
|
46
|
-
Secondary, and still worth having: in-place compaction's other historical cost was breaking the prompt
|
|
47
|
-
cache —
|
|
48
|
-
|
|
49
|
-
1. **It breaks prompt caching from the cut point onward.** Modifying the prefix invalidates the
|
|
50
|
-
provider-side cache behind it; DSH's design minimizes this by keeping the header intact and
|
|
51
|
-
replacing only a span — but everything after the replacement refills. On hardware with slow prefill,
|
|
52
|
-
that refill is the cost.
|
|
53
|
-
2. **The model loses access to what was dropped.** DSH's compaction *shadows* surface nodes rather than
|
|
54
|
-
deleting the durable log, so the raw text is still recorded — but in any default composition the
|
|
55
|
-
agent can no longer see it and has no in-context handle to get it back. (Upstream ships an opt-in
|
|
56
|
-
package that gives a model raw event JSON for shadowed history; it costs five tool schemas in every
|
|
57
|
-
request, and JSON events are not a readable archive either way.) What survives in context is a
|
|
58
|
-
paraphrase it must trust.
|
|
59
|
-
|
|
60
|
-
This plugin never touches the current session. It archives the conversation verbatim to disk, then
|
|
61
|
-
starts a new session that carries only an index. Nothing is destroyed, and everything stays retrievable.
|
|
62
|
-
|
|
63
|
-
### Be clear about what this does not buy you
|
|
64
|
-
|
|
65
|
-
The new session still pays one cold prefill — its system prompt plus the TOC plus the handoff note —
|
|
66
|
-
which is at least the cost class a post-compaction request pays (a compaction keeps the header cached
|
|
67
|
-
and refills from the cut point; a continuation refills everything). **This is not marketed as cheaper
|
|
68
|
-
than compaction.** What it buys is different, and for the right workload better:
|
|
69
|
-
|
|
70
|
-
- the archive is **verbatim**, so reloading a chapter returns real text, not a summary of it
|
|
71
|
-
- the original session is **untouched**, keeping its cache and remaining usable as an ancestor
|
|
72
|
-
- the move is **reversible** — abandon the new session and keep working in the old one
|
|
73
|
-
- history can be **branched**, not just truncated
|
|
74
|
-
|
|
75
|
-
Built for people running local models in small windows (32K–128K), who depend on high cache hit rates
|
|
76
|
-
because prefill is slow, and who want relief that is inspectable rather than destructive.
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
## How It Works
|
|
81
|
-
|
|
82
|
-
### `chapters_segment`
|
|
83
|
-
|
|
84
|
-
The agent calls `chapters_segment` on the current conversation. It proposes topic chapters as
|
|
85
|
-
**event-sequence ranges** plus a title and one-line summary each, along with the *archive ceiling* — the
|
|
86
|
-
last completed turn. The agent reviews and edits the proposal.
|
|
87
|
-
|
|
88
|
-
The model supplies only boundaries and labels, never archived text. That keeps the archive verbatim
|
|
89
|
-
rather than reconstructed from recall, and keeps one tool call within output-token limits no matter how
|
|
90
|
-
long the conversation is.
|
|
91
|
-
|
|
92
|
-
### `chapters_continue`
|
|
93
|
-
|
|
94
|
-
The agent — or the user, via a button once the UI trigger lands — calls `chapters_continue` with the
|
|
95
|
-
approved ranges and a handoff note. The tool:
|
|
96
|
-
|
|
97
|
-
1. **Reserves** chapter numbers and slugs in the chain registry, so a crash or retry cannot collide them
|
|
98
|
-
2. **Renders chapter bodies from the session log** at the given ranges, writing Markdown and recording a
|
|
99
|
-
content hash per file
|
|
100
|
-
3. **Defers oversized tool results** out of the chapter into content-addressed artifact files
|
|
101
|
-
4. **Builds a flat, chronological TOC** by walking the ancestor path — the plugin walks, the model reads
|
|
102
|
-
a list
|
|
103
|
-
5. **Preflights the context budget** and refuses with concrete numbers if the new session would start too
|
|
104
|
-
large
|
|
105
|
-
6. **Creates a new session** seeded with exactly one event: the TOC
|
|
106
|
-
7. **Returns** the new session id, store directory, archive ceiling, written paths, and hashes
|
|
107
|
-
|
|
108
|
-
The TOC is the new session's first message:
|
|
109
|
-
|
|
110
|
-
```markdown
|
|
111
|
-
## Conversation TOC
|
|
112
|
-
|
|
113
|
-
This session continues an earlier conversation. The chapters below hold its
|
|
114
|
-
history as Markdown. Use the `read` tool on a chapter path to reload one —
|
|
115
|
-
reloading costs context too, so read the bullet first and only open what you need.
|
|
116
|
-
|
|
117
|
-
### In flight
|
|
118
|
-
Refactoring the migration runner. `applyPending` is split; `resolveOrder` is not
|
|
119
|
-
touched yet. The failing test is `migrations.e2e` case 3.
|
|
120
|
-
Parent: 01HTZ6Q8K3 · Root: 01HTAB12CD7
|
|
121
|
-
|
|
122
|
-
### Chapters
|
|
123
|
-
1. [Project Setup](.dsh-chapters/01HTAB…/chapters/001-project-setup.md) — Repo scaffolding and dependency choices.
|
|
124
|
-
2. [Auth Debugging](.dsh-chapters/01HTAB…/chapters/002-auth-debugging.md) — Resolved CORS and token refresh issues.
|
|
125
|
-
3. [Database Schema](.dsh-chapters/01HTAB…/chapters/003-database-schema.md) — Designed the user table and migration.
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
"Chapters" is a flat numbered list in causal order, never a graph to explore — see
|
|
129
|
-
[Ancestry without traversal](#ancestry-without-traversal).
|
|
130
|
-
|
|
131
|
-
The "In flight" section exists because the turn that triggers a continuation is by construction not yet
|
|
132
|
-
archived: the ceiling is the last completed turn, so without a handoff note the most relevant context
|
|
133
|
-
would be the part that gets dropped.
|
|
134
|
-
|
|
135
|
-
### Chapters stay small: the model decides which tool results to carry
|
|
136
|
-
|
|
137
|
-
Long dumps — a `read` on a big file, a `bash` traceback — are most of a real conversation's tokens and
|
|
138
|
-
usually the least valuable thing to re-read. But "which ones matter" is a *semantic* judgement, not a size
|
|
139
|
-
question: a four-line compiler error is load-bearing and a four-hundred-line config `read` can be noise.
|
|
140
|
-
So the agent that lived the conversation decides, and the plugin does the arithmetic.
|
|
141
|
-
|
|
142
|
-
- The plugin enumerates the candidates — every tool result's `seq`, tool, and real size — so the model
|
|
143
|
-
never guesses at byte counts.
|
|
144
|
-
- The default defers large results and keeps trivial ones inline; **the model submits only exceptions**
|
|
145
|
-
(`toolResultOverrides: [{seq, inline}]`), so the judgement costs almost nothing in output.
|
|
146
|
-
- Invocations and arguments always stay inline — that is the "what was attempted" half of the record.
|
|
147
|
-
- The artifact file is written **either way**, so a wrong call is a context miss, never data loss. That
|
|
148
|
-
asymmetry is what makes it safe to hand this decision to a model.
|
|
149
|
-
|
|
150
|
-
```
|
|
151
|
-
**Assistant:** Let me check the migration order.
|
|
152
|
-
↳ tool: bash `npm run migrate -- --dry-run`
|
|
153
|
-
result: 41.2 KB, sha256 8f3a1c… → .dsh-chapters/01HTAB…/artifacts/8f/8f3a1c9d….txt
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
Artifacts are **content-addressed**, so the same 200-line file read ten times across a whole subtree is
|
|
157
|
-
stored once. Images and attachments take the same path, since Markdown cannot carry them.
|
|
158
|
-
|
|
159
|
-
The chapter files themselves are **Markdown with YAML frontmatter**, deliberately not XML: the consumer
|
|
160
|
-
is a model reading text, markup tokens are context tokens in a plugin whose purpose is bounding context,
|
|
161
|
-
and the session log plus the registry already hold the machine-readable structure. Frontmatter carries
|
|
162
|
-
`seqRange`, `session`, `root`, `sha256`, and `tokens`, so files stay queryable without a heavier format.
|
|
163
|
-
The reasoning, and the single condition that would reopen the question, are in
|
|
164
|
-
[docs/architecture.md](docs/architecture.md#file-format-markdown-with-frontmatter-not-xml).
|
|
165
|
-
|
|
166
|
-
### The impossible band: too big never means "try anyway"
|
|
167
|
-
|
|
168
|
-
A tool result at or above `toolResultArtifactTokens` (default 8000) is stored as a
|
|
169
|
-
content-addressed **artifact the moment it arrives** — before the next model request is
|
|
170
|
-
composed — and the context carries a reference stub instead: size, sha, path, and the
|
|
171
|
-
query instruction. The blob is prefilled zero times; the retention paradox (a newest node
|
|
172
|
-
too big to file) becomes unreachable by construction. The model works the artifact with
|
|
173
|
-
`chapters_artifact`: `toc` (heading map with line numbers — a 300-page book arrives with
|
|
174
|
-
its own contents), `search` (term hits as small blocks), `read` (exact line ranges). The
|
|
175
|
-
archive-time judgment above is unchanged for everything that genuinely fits.
|
|
176
|
-
|
|
177
|
-
### What "lossless" means here — precisely
|
|
178
|
-
|
|
179
|
-
Because oversized results are deferred, chapters are no longer self-contained, and the claim has to move
|
|
180
|
-
with the design:
|
|
181
|
-
|
|
182
|
-
> **Every byte of the conversation remains retrievable** — inline in its chapter, or by an exact path
|
|
183
|
-
> printed in it. Nothing is paraphrased, summarized, or dropped.
|
|
184
|
-
|
|
185
|
-
That is a weaker-sounding and *more true* statement than "the chapter contains everything." Note also
|
|
186
|
-
that tool **effects** (files written, commands run) survive only as their logged output. The parent
|
|
187
|
-
session remains the only complete record, which is why the TOC prints its id: consulting an ancestor
|
|
188
|
-
directly is the ultimate fallback.
|
|
189
|
-
|
|
190
|
-
---
|
|
191
|
-
|
|
192
|
-
## How The New Session Is Created
|
|
193
|
-
|
|
194
|
-
Worth reading before contributing, because it is the least obvious part of the design and the reason
|
|
195
|
-
there is a Phase 0.
|
|
196
|
-
|
|
197
|
-
DSH's fork operation seeds a child with the parent's history — `packages/api/session-controller/src/commands.ts:260`:
|
|
198
|
-
|
|
199
|
-
```ts
|
|
200
|
-
seed: source.events.slice(0, cut),
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
Using that would give the new session *all* of the parent's context plus the TOC. Context would grow at
|
|
204
|
-
every link instead of shrinking, and the plugin would compact nothing.
|
|
205
|
-
|
|
206
|
-
`seed` is optional (`packages/core/agent/src/index.ts:95`), so the plugin creates a session seeded with
|
|
207
|
-
one synthetic event and attaches it to the parent's workspace. That is what makes a bounded head real.
|
|
208
|
-
|
|
209
|
-
Two honest consequences:
|
|
210
|
-
|
|
211
|
-
- **Lineage lives in this plugin's registry, not in the kernel.** A synthetic-seed child is not a
|
|
212
|
-
fork-inherited session, so the web UI's branch graph shows it as an unrelated root. The chain and
|
|
213
|
-
manifest view are this plugin's responsibility.
|
|
214
|
-
- **It leans on kernel internals.** Workspace attachment and agent-preset composition are replicated
|
|
215
|
-
from the api-proxy fork handler, following the `dsh-session-fork` precedent, and must be re-verified
|
|
216
|
-
against each DSH upgrade.
|
|
217
|
-
|
|
218
|
-
---
|
|
219
|
-
|
|
220
|
-
## Ancestry Without Traversal
|
|
221
|
-
|
|
222
|
-
Continuing an older session twice makes history a tree. Asking an agent to navigate a tree is how agents
|
|
223
|
-
get lost, so **the model never does it**:
|
|
224
|
-
|
|
225
|
-
- **The plugin walks the ancestor path and emits a flat, numbered, chronological list.** Reading order
|
|
226
|
-
*is* causal order. At depth 10 or mid-branch, the agent sees an index and `read` paths — no graph, no
|
|
227
|
-
inference, no traversal.
|
|
228
|
-
- **The TOC is the only index.** It already lists every chapter on the path with its summary, and the
|
|
229
|
-
registry holds the machine-readable form. An earlier draft added a per-root `MANIFEST.md`; it was
|
|
230
|
-
dropped as a redundant, drift-prone duplicate whose size made reading it its own context cost.
|
|
231
|
-
- **Back-links are printed** — root session and parent session — so the trace is visible without being
|
|
232
|
-
reconstructed.
|
|
233
|
-
|
|
234
|
-
Chapters are keyed by **creating session** rather than a linear chain id, so sibling branches get
|
|
235
|
-
different correct TOCs and cannot collide on numbering.
|
|
236
|
-
|
|
237
|
-
---
|
|
238
|
-
|
|
239
|
-
## Enrichment (P2)
|
|
240
|
-
|
|
241
|
-
Archived chapters are annotated asynchronously by a model — titles, summaries, semantic
|
|
242
|
-
topics — with per-field provenance chains (`generated:` in the frontmatter, newest-first).
|
|
243
|
-
The verbatim body is untouchable: the one sanctioned write path re-verifies the body hash
|
|
244
|
-
and refuses loudly on disagreement. The queue triggers are a config seam
|
|
245
|
-
(`enrichmentTrigger: afterPush | idle | both | manual`, idle default 60 s);
|
|
246
|
-
`/chapters-enrich run|model|report` is the manual surface, and an `enrichmentEnabled: false`
|
|
247
|
-
kill switch leaves a fully working, signatures-only corpus. Topic vocabulary merges
|
|
248
|
-
themselves from corpus statistics (shadow-by-default; `vocabApply: true` writes git-visible
|
|
249
|
-
curation entries), and adjacent legacy fragments cohere into one stitched search entry.
|
|
250
|
-
Detail: `docs/knowledge-repo.md` §6, §12.
|
|
251
|
-
|
|
252
|
-
## Rules (P3)
|
|
253
|
-
|
|
254
|
-
Rules are chapters of a different kind: `kind: rule` files with a category, proposed by
|
|
255
|
-
a human (`/chapters-rule add`) or by an agent (`chapters_rule_propose`), and rendered into
|
|
256
|
-
the continuation notice **verbatim only after a human approves them on that machine** —
|
|
257
|
-
approval is a per-machine curation fact, so a rule from another machine is searchable
|
|
258
|
-
everywhere but influential nowhere until each keyboard says so. The core block is budgeted
|
|
259
|
-
(`coreRulesBudgetTokens`): overflow refuses with per-rule numbers, never clips.
|
|
260
|
-
Detail: `docs/knowledge-repo.md` §7 (incl. the 2026-09-20 §15 amendment).
|
|
261
|
-
|
|
262
|
-
## Querying the knowledge base from the CLI
|
|
263
|
-
|
|
264
|
-
`dsh chapters` is a thin, **read-only** shell window over the same stores the model sees — for scripts,
|
|
265
|
-
and for a human checking the corpus with their own eyes. It runs in-process against the plugin's own
|
|
266
|
-
modules (no daemon, no second implementation), and lives in the DSH CLI:
|
|
267
|
-
|
|
268
|
-
```
|
|
269
|
-
dsh chapters status # what exists, and which store each layer came from
|
|
270
|
-
dsh chapters search "budget preflight" # ranked chapters/rules; [RULE approved] hits included
|
|
271
|
-
dsh chapters show 004-auth-debugging # a chapter's frontmatter, topics, and body (or --excerpt)
|
|
272
|
-
dsh chapters vocab # effective topic vocabulary + corpus coverage
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
Two properties are load-bearing, and tested as contract, not style:
|
|
276
|
-
|
|
277
|
-
- **It reads the same files, resolved the same way.** `--workspace` defaults to `$PWD` and each layer
|
|
278
|
-
falls back through the same chain the model uses (workspace store → knowledge mirror → profile store,
|
|
279
|
-
including the legacy `.dsh-knowledge.json` shape) — so the CLI cannot disagree with the plugin just
|
|
280
|
-
because it guessed a path. `--json` on every command (the `dsh` CLI's own `-j` flag) is for agents;
|
|
281
|
-
the default output is for humans.
|
|
282
|
-
- **It cannot write.** Zero mutations by construction: the modules it calls are read paths only, and a
|
|
283
|
-
test asserts every JSON key the commands emit (plus their nesting) against an allowlist of read-only
|
|
284
|
-
APIs. The plugin's public read surface is **frozen**: adding a write-capable call to it fails that
|
|
285
|
-
test, so the CLI stays an observer as the layer grows. What it prints is labelled by provenance —
|
|
286
|
-
`enriched by model` / `deterministic` — because a topic from the enrichment ladder is a different
|
|
287
|
-
kind of fact than a registered range.
|
|
288
|
-
|
|
289
|
-
## Querying the knowledge base from the CLI
|
|
290
|
-
|
|
291
|
-
`dsh chapters` is a thin, **read-only** shell window over the same stores the model sees — for scripts,
|
|
292
|
-
and for a human checking the corpus with their own eyes. It runs in-process against the plugin's own
|
|
293
|
-
modules (no daemon, no second implementation), and lives in the DSH CLI:
|
|
294
|
-
|
|
295
|
-
```
|
|
296
|
-
dsh chapters status # what exists, and which store each layer came from
|
|
297
|
-
dsh chapters search "budget preflight" # ranked chapters/rules; [RULE approved] hits included
|
|
298
|
-
dsh chapters show 004-auth-debugging # a chapter's frontmatter, topics, and body (or --excerpt)
|
|
299
|
-
dsh chapters vocab # effective topic vocabulary + corpus coverage
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
Two properties are load-bearing, and tested as contract, not style:
|
|
303
|
-
|
|
304
|
-
- **It reads the same files, resolved the same way.** `--workspace` defaults to `$PWD` and each layer
|
|
305
|
-
falls back through the same chain the model uses (workspace store → knowledge mirror → profile store,
|
|
306
|
-
including the legacy `.dsh-knowledge.json` shape) — so the CLI cannot disagree with the plugin just
|
|
307
|
-
because it guessed a path. `--json` on every command (the `dsh` CLI's own `-j` flag) is for agents;
|
|
308
|
-
the default output is for humans.
|
|
309
|
-
- **It cannot write.** Zero mutations by construction: the modules it calls are read paths only, and a
|
|
310
|
-
test asserts every JSON key the commands emit (plus their nesting) against an allowlist of read-only
|
|
311
|
-
APIs. The plugin's public read surface is **frozen**: adding a write-capable call to it fails that
|
|
312
|
-
test, so the CLI stays an observer as the layer grows. What it prints is labelled by provenance —
|
|
313
|
-
`enriched by model` / `deterministic` — because a topic from the enrichment ladder is a different
|
|
314
|
-
kind of fact than a registered range.
|
|
315
|
-
|
|
316
|
-
## Acceptance Testing
|
|
317
|
-
|
|
318
|
-
`npm test` covers unit + integration. The browser acceptance suite (7 specs, seven boot-pinned
|
|
319
|
-
projects) runs **off the model tape** by default: `npm run test:e2e:replay` replays recorded LLM
|
|
320
|
-
exchanges from `var/model-tape/` — seconds, deterministic, GPU-free (recorded usage fields keep
|
|
321
|
-
compaction thresholds firing at identical steps). `npm run test:e2e:record` distills the tapes
|
|
322
|
-
from the real Local model (once per scenario; also `npm run test:e2e` to run fully live), and
|
|
323
|
-
misses in replay mode fail LOUD with a journal, never silently. Details: docs/development.md.
|
|
38
|
+
**This is not a fork.** DSH's native fork copies the parent's log into the child; this plugin does
|
|
39
|
+
the opposite — it creates an *unseeded* session that cites an archive. The whole design discipline
|
|
40
|
+
traces back to that distinction.
|
|
324
41
|
|
|
325
42
|
## Architecture
|
|
326
43
|
|
|
327
44
|
```
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
│
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
│
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
├── cordis.patch.yml # mounts the plugin into a profile's bundle list
|
|
342
|
-
├── package.json
|
|
343
|
-
└── tsconfig.json
|
|
45
|
+
agent turn ──► chapters_segment ──► chapter ranges (the model picks ranges; never writes bodies)
|
|
46
|
+
│
|
|
47
|
+
chapters_continue(title, handoffNote, chapters[])
|
|
48
|
+
│
|
|
49
|
+
┌─────────────────────────────────┼──────────────────────────────┐
|
|
50
|
+
▼ ▼ ▼
|
|
51
|
+
render bodies from log defer oversized tool results preflight the budget
|
|
52
|
+
(verbatim, per range) to content-addressed TOC + note vs remainder
|
|
53
|
+
artifacts/ files │ │
|
|
54
|
+
└─────────────► chapter .md files ◄───────────────────────────────┘
|
|
55
|
+
│ refuse loudly if over budget
|
|
56
|
+
▼
|
|
57
|
+
new session seeded with ONE message: the cumulative Table of Contents
|
|
344
58
|
```
|
|
345
59
|
|
|
346
|
-
The
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
### Design constraint: measure, then refuse
|
|
388
|
-
|
|
389
|
-
The new session's *added* context is capped: the TOC plus the handoff note must fit a configured share
|
|
390
|
-
(default 25%) of the window **remaining after the system prompt and tool schemas**. This is checked
|
|
391
|
-
**before** creating the session, and exceeding it is **an error with numbers, not a truncation**. There is
|
|
392
|
-
deliberately no length cap on the handoff note — an over-long note fails this check, and clipping one
|
|
393
|
-
would reintroduce exactly the silent loss the plugin exists to avoid. Basing the cap on the remaining
|
|
394
|
-
space matters: a 32K model's header alone can occupy a quarter of the window, and a whole-window rule would
|
|
395
|
-
make every continuation impossible on precisely the hardware this plugin is for.
|
|
396
|
-
|
|
397
|
-
### Trigger model
|
|
398
|
-
|
|
399
|
-
**Manual is the MVP**: ask the agent to segment and continue. The user pays the continuation cost only
|
|
400
|
-
when they choose to.
|
|
401
|
-
|
|
402
|
-
**Pressure detection arrives in Phase 2, and acts in Phase 3.** Detection at `turn/end` reuses the same
|
|
403
|
-
budget machinery the preflight needs, so it is built early rather than last — but the automatic path only
|
|
404
|
-
*suggests* until Phase 3. It fires **only at turn boundaries, never mid-step**; `agent/pre-step` would
|
|
405
|
-
break the user's in-flight request. And since the archive ceiling is a completed `turn/end`, mid-turn cut
|
|
406
|
-
points are not merely unsafe — they are not representable.
|
|
407
|
-
|
|
408
|
-
---
|
|
409
|
-
|
|
410
|
-
## What Is Still Open
|
|
411
|
-
|
|
412
|
-
Each of these is a decision or an unmeasured claim, not an oversight. The Phase 0/0b gates that used to
|
|
413
|
-
live here were **answered by probe rounds 1-25** — unseeded-but-styled creation is durable, listed, and
|
|
414
|
-
resumable; creation schedules no turn (children sit until steered); the compaction seam runs a plugin
|
|
415
|
-
subclass end to end with zero summarization tokens; and E3 measured that a compaction halves the uncached
|
|
416
|
-
refill rather than cold-restarting the prompt.
|
|
417
|
-
|
|
418
|
-
- **Coexistence with `dsh-session-fork`.** Name-spaces are disjoint by inspection (domains
|
|
419
|
-
`dsh_chapters`/`dsh_session_fork`, tools `chapters_*`/`branch_*`, presets), but the boot-pair test came
|
|
420
|
-
back MOOT: the shipped example fails to boot against the installed host **on its own** (`webServer`
|
|
421
|
-
inject drift, rc.2-era code vs rc.1 host — attributed by booting it alone; r26). Re-run the pair-boot
|
|
422
|
-
on a version-matched host before making it a product claim; until then coexistence is assumed-neutral,
|
|
423
|
-
not proven.
|
|
424
|
-
- **Automatic continuation (Phase 3).** The pressure *suggestion* at `turn/end` and any autonomous
|
|
425
|
-
continue decision are unbuilt by design; the engine's own automatic compaction is the host's shipped
|
|
426
|
-
timing and needs nothing from us.
|
|
427
|
-
- **The index is bounded nowhere yet.** Chapter size is bounded; TOC length grows with depth, and
|
|
428
|
-
reloading chapters can re-trigger pressure in a loop. Bullet folding, a reload budget, and retention
|
|
429
|
-
for abandoned chains are Phase 4.
|
|
430
|
-
- **Integrity is detection, not proof.** Chapter files are writable Markdown replayed as the model's own
|
|
431
|
-
memory, so bodies are hashed and mismatches surfaced — but a writer that can also edit the registry
|
|
432
|
-
defeats it. Accidental drift and single-shot injection are caught; a determined tamperer is not.
|
|
433
|
-
|
|
434
|
-
## Shared Knowledge (P1, shipped)
|
|
435
|
-
|
|
436
|
-
Chapters are not just escape hatches — they are a corpus. Per project, a private git repository
|
|
437
|
-
(`.dsh-knowledge/` mirror, real transport via pure-JS isomorphic-git) carries the workspace's
|
|
438
|
-
knowledge between every harness pointed at the same remote:
|
|
439
|
-
|
|
440
|
-
- **Turn signatures** (paths/commands/terms — deterministic, zero tokens) are collected at every
|
|
441
|
-
`turn/end`; the fork/continue archive merges adjacent same-topic turns into one chapter and
|
|
442
|
-
splits on topic changes or the size cap (record §4).
|
|
443
|
-
- **Derived sharded index** + per-machine curation facts (`index/`, `edits/`) — rebuilt
|
|
444
|
-
deterministically from the corpus, never hand-edited state.
|
|
445
|
-
- **`chapters_search`** — the model (or you) query the whole project's chapter history; results
|
|
446
|
-
are paths readable with `read`.
|
|
447
|
-
- **Redaction** at the render chokepoint: credentials become stable `⟦redacted…⟧` markers before
|
|
448
|
-
anything is archived (pattern-based speed bump, documented as such).
|
|
449
|
-
- **Sync loop**: pull at new-session/fork, debounced push after archives, a file writer-lock, and
|
|
450
|
-
§5.3 degradation: unreachable remote ⇒ LOCAL-ONLY mode where publish/index/search keep working
|
|
451
|
-
and `/chapters-status` says so with the numbers. Divergence rebuilds the mirror (it is
|
|
452
|
-
transport — the workspace store is the truth).
|
|
453
|
-
|
|
454
|
-
Configure the upstream with `/chapters-link`:
|
|
60
|
+
The four rules the design never breaks:
|
|
61
|
+
|
|
62
|
+
- **Never rewrite the durable session log.** Append-only; nothing is edited or deleted, ever.
|
|
63
|
+
- **Never seed a continuation from the parent's events.** The child starts from one synthetic TOC
|
|
64
|
+
notice; history grows by citation, never by copying.
|
|
65
|
+
- **Never contribute to the shared system prompt.** Per-continuation notices only — enabling the
|
|
66
|
+
plugin changes what *your sessions* can do, not what every other session in the profile sees.
|
|
67
|
+
- **Never let the model author chapter bodies.** The model chooses ranges; the plugin renders the
|
|
68
|
+
text from the log. Model-typed "lossless" is the failure mode this design exists to avoid.
|
|
69
|
+
|
|
70
|
+
Supporting machinery, each with a chapter in [docs/architecture.md](docs/architecture.md):
|
|
71
|
+
|
|
72
|
+
- **Oversized tool results** (file reads, command output) are deferred to a content-addressed
|
|
73
|
+
`artifacts/` store and referenced from the chapter — the model can still pull them back via the
|
|
74
|
+
`chapters_artifact` tool (toc / search / read modes). Hence the honest guarantee: **every byte
|
|
75
|
+
remains retrievable**, not "every byte is in the chapter".
|
|
76
|
+
- **Continuation budget, not a handoff cap:** TOC + note must fit a share of the window *remaining
|
|
77
|
+
after the header*; over budget the plugin **refuses with the numbers** — it never clips a handoff
|
|
78
|
+
note.
|
|
79
|
+
- **Ancestry is a DAG, flattened for the model:** the child's notice lists every chapter on the
|
|
80
|
+
ancestor path chronologically, with explicit back-links. Reading order is causal order; the model
|
|
81
|
+
never traverses a graph.
|
|
82
|
+
- **The compaction seam:** when the `chapters` agent preset is mounted, in-place compaction (manual
|
|
83
|
+
`/compact` and automatic pressure) runs through the plugin's deterministic engine — the same
|
|
84
|
+
log-only, zero-summarization discipline inside a session rather than across sessions.
|
|
85
|
+
- **Enrichment (P2)**: after pushes, an idle/deferred ladder annotates archived chapters (topics,
|
|
86
|
+
summaries) — with validation before commit and a body-hash guard making enrichment the *only*
|
|
87
|
+
sanctioned mutation of a chapter file, and even that provenance-chained.
|
|
88
|
+
- **Rules (P3)**: a write-once ledger of proposed project rules; approval is a **per-machine
|
|
89
|
+
curation fact** (never a shared-file rewrite), and the active core rules ride verbatim into every
|
|
90
|
+
continuation notice within a budget that refuses rather than clips.
|
|
91
|
+
|
|
92
|
+
Storage map: chapter bodies and artifacts live **in the workspace** under `.dsh-chapters/<root>/…`
|
|
93
|
+
(so the `read` tool reaches them); ancestry, numbering and hashes live in the harness's durable
|
|
94
|
+
storage domain (`dsh_chapters`); your upstream credentials live under the **DSH home** with 0600
|
|
95
|
+
permissions — never inside the project directory, never committed, never pushed.
|
|
96
|
+
|
|
97
|
+
## Installation (users)
|
|
98
|
+
|
|
99
|
+
Node ≥ 24 and a DSH host you install plugins into. From npm:
|
|
455
100
|
|
|
456
101
|
```
|
|
457
|
-
|
|
458
|
-
/chapters-link ~/pools/proj-kb.git # local-path pool — no HTTP, no credentials
|
|
459
|
-
/chapters-link https://… <token> # network pool — token required (loopback exempt),
|
|
460
|
-
# stored 0600 under the DSH HOME, never here
|
|
102
|
+
dsh plugin add @treeseed/dsh-chapters
|
|
461
103
|
```
|
|
462
104
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
|
105
|
+
(or pin the version: `@treeseed/dsh-chapters@0.1.1`). Restart your `dsh web` afterwards so the
|
|
106
|
+
bundle, presets and client plane reload. What activating the plugin does:
|
|
107
|
+
|
|
108
|
+
- **Registers the model tools** (`chapters_segment`, `chapters_continue`, `chapters_fork`,
|
|
109
|
+
`chapters_search`, `chapters_artifact`, `chapters_rule_propose`) — available to agents.
|
|
110
|
+
- **Registers the composer commands** listed below — available to you.
|
|
111
|
+
- **Adds a "Fork with chapters" action** to every assistant message in the web UI.
|
|
112
|
+
- **Installs the `chapters` agent preset** into your DSH home at boot (`.agent-presets/`;
|
|
113
|
+
write-once — your edits are never clobbered). Pick it in the agent-preset menu to get the
|
|
114
|
+
chapter-form compaction engine; the plugin's own `fallbackPreset` covers children it creates.
|
|
115
|
+
|
|
116
|
+
Configure it from your profile's plugin entry. The **complete key reference with defaults is the
|
|
117
|
+
comment header of the shipped `cordis.patch.yml`** (`node_modules/@treeseed/dsh-chapters/cordis.patch.yml`)
|
|
118
|
+
— it mirrors the live config schema, which is enforced. The knobs users reach for most:
|
|
119
|
+
|
|
120
|
+
| Key | What it governs |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `harnessId` | this machine's identity in the shared knowledge (author of edits, rule approvals) |
|
|
123
|
+
| `knowledgeRemote` | pre-bind an upstream at config time (else `/chapters-link`) |
|
|
124
|
+
| `syncDebounceMs` | how quickly archives push upstream (default 30000) |
|
|
125
|
+
| `enrichmentEnabled` / `enrichmentModel` | the P2 annotator (off = pure no-op) |
|
|
126
|
+
| `vocabApply` | shadow (default) vs git-visible topic-alias curation |
|
|
127
|
+
| `coreRulesBudgetTokens` / `rulesCoreBonus` | the rules block in notices and its search bonus |
|
|
128
|
+
| `toolResultDeferFloorTokens` | the archive-time inline/defer floor |
|
|
129
|
+
| `continuationBudgetRatio` | how much of the remaining window a continuation may add |
|
|
130
|
+
|
|
131
|
+
## Supported commands
|
|
132
|
+
|
|
133
|
+
Typed in the composer **inside a session** (a brand-new draft screen sends text as a message, not a
|
|
134
|
+
command — start the session first). Every command answers with honest numbers; nothing degrades
|
|
135
|
+
silently.
|
|
136
|
+
|
|
137
|
+
| Command | Meaning |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `/chapters-link` | show the current upstream and mirror state |
|
|
140
|
+
| `/chapters-link <local-path>` | bind a shared local git repo (bare or worktree) — needs no credentials |
|
|
141
|
+
| `/chapters-link <https-url> <token>` | bind a networked git pool over smart-HTTP; token stored 0600 under the DSH home |
|
|
142
|
+
| `/chapters-link treedx+<url>/<repo> <token>` | bind a TreeDX service (token required — it is a bearer API) |
|
|
143
|
+
| `/chapters-status` | last sync with its steps: cloned / published n new / index / pushed, or the local-only reason |
|
|
144
|
+
| `/chapters-enrich run` \| `model <provider/model \| clear>` \| `report` | drive the enrichment ladder and choose its model (default: the conversation route) |
|
|
145
|
+
| `/chapters-rule add <category> <text>` \| `list [--all \| --proposed \| --category <c>]` \| `approve <id>` \| `revoke <id>` | the rules ledger; approve/revoke are per-machine facts |
|
|
146
|
+
| `/compact` (host command) | with the `chapters` preset mounted, this routes through the deterministic chapter engine |
|
|
147
|
+
|
|
148
|
+
The agent-side tools you'll see in transcripts: **`chapters_segment`** (propose archive ranges),
|
|
149
|
+
**`chapters_continue`** (open the TOC-seeded continuation), **`chapters_fork`** (same machinery,
|
|
150
|
+
mid-conversation branch), **`chapters_search`** (query the shared index), **`chapters_artifact`**
|
|
151
|
+
(retrieve deferred tool results), **`chapters_rule_propose`** (submit a rule candidate). The
|
|
152
|
+
**Fork with chapters** button on an assistant message performs the archive-and-continue in one
|
|
153
|
+
click and switches the UI to the new session.
|
|
154
|
+
|
|
155
|
+
## Backend providers
|
|
156
|
+
|
|
157
|
+
Transport is a **provider seam** (design record: [docs/knowledge-repo.md](docs/knowledge-repo.md) §15;
|
|
158
|
+
contract: [docs/provider.md](docs/provider.md)): six verbs — clone/pull/push/commit/list/fetch — and
|
|
159
|
+
two shipped backends. Search, rules and the index all read the materialized **mirror**; they are
|
|
160
|
+
identical under either provider.
|
|
161
|
+
|
|
162
|
+
| | **`git` (default)** | **`treedx`** |
|
|
479
163
|
|---|---|---|
|
|
480
|
-
|
|
|
481
|
-
|
|
|
482
|
-
|
|
|
483
|
-
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
**Do not port from** its `squash*` files or `vendor/compact.ts` — those rewrite spans, the operation this
|
|
487
|
-
plugin forbids — and do not imitate `src/prompt.ts`, per the header constraint above.
|
|
488
|
-
|
|
489
|
-
---
|
|
490
|
-
|
|
491
|
-
## Planning
|
|
492
|
-
|
|
493
|
-
Scoped so the cache-critical path is proven before anything is built on it, with the budget and pressure
|
|
494
|
-
machinery moved early because automatic continuation depends on it.
|
|
495
|
-
|
|
496
|
-
**Phase 0 — Prove the premise.** Package skeleton and one trivial tool. Then the load-bearing question:
|
|
497
|
-
can a plugin create a session seeded with a single synthetic event that is durable, sidebar-listed,
|
|
498
|
-
resumable after restart, and materially *smaller* in tokens than its parent? Settle the name, the test
|
|
499
|
-
harness, and the `dsh-session-fork` coexistence question here.
|
|
500
|
-
*If the child is not durable and listed, the design does not work and later phases should not be funded.*
|
|
501
|
-
|
|
502
|
-
**Phase 1 — Render, cite, and bound.** `chapters_continue` with validated event ranges; bodies rendered
|
|
503
|
-
from the log; artifact deferral with content-addressed dedup; registry with ancestry, numbering, and
|
|
504
|
-
hashes; flat ancestor-path TOC with handoff note and back-links; the budget preflight with
|
|
505
|
-
concrete refusal numbers; atomic ordering and idempotent retry. Manual trigger only.
|
|
164
|
+
| Address form | `https://…/pool.git` (+ token) or a local path | `treedx+<url>/<repo>` (+ bearer token) |
|
|
165
|
+
| Transport | git smart-HTTP via isomorphic-git; plain repositories, browsable with any git client | TreeDX service API: workspace → overlay writes → commit, lease-managed |
|
|
166
|
+
| Credentials | per-project token in the DSH home (0600); local paths need none | bearer token, same home treatment |
|
|
167
|
+
| Contention | push rejected → ff-pull retry → divergence routes through **rebuild-from-the-store** | lease contention maps onto the same rejected→pull→diverged ladder |
|
|
168
|
+
| Mirror memory | cloned mirror directory under the workspace store | `.treedx-state.json` remembers the served head |
|
|
169
|
+
| Layout on the wire | identical: `chapters/`, `artifacts/`, `rules/`, derived `index/`, `collections/`, `topics/vocabulary.json` | identical (the service stores the same tree) |
|
|
506
170
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
171
|
+
Shared honesty rules both backends implement (measured, pinned by tests): every sync-path failure
|
|
172
|
+
**degrades to `local-only` with the reason**, never a silent loss — the mirror stays current and the
|
|
173
|
+
push lands when the remote returns; an unreachable-vs-auth-rejected distinction survives into the
|
|
174
|
+
status; divergence resolves by rebuilding from the store (the store is the truth, transport is
|
|
175
|
+
expendable); corpus listings page to the end. `kind` defaults to `git` for every record written
|
|
176
|
+
before providers existed — upgrading is zero migration.
|
|
510
177
|
|
|
511
|
-
|
|
512
|
-
preflight. Never mid-step.
|
|
178
|
+
## Development process
|
|
513
179
|
|
|
514
|
-
|
|
515
|
-
a reload budget in the TOC preamble; artifact refcounting and a retention/prune command.
|
|
180
|
+
Everything runs from a clone with plain **npm** (this is a single package, not a monorepo):
|
|
516
181
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
---
|
|
525
|
-
|
|
526
|
-
## Development
|
|
527
|
-
|
|
528
|
-
### Prerequisites
|
|
529
|
-
|
|
530
|
-
- A working DeepSeek Harness installation
|
|
531
|
-
- The four reference repositories cloned into `examples/`
|
|
532
|
-
|
|
533
|
-
### Installing for development
|
|
182
|
+
```
|
|
183
|
+
npm ci
|
|
184
|
+
npm run typecheck && npm run build
|
|
185
|
+
npm test # unit + integration
|
|
186
|
+
npm run coverage:ci # c8 gate: ≥85% statements/branches/functions/lines
|
|
187
|
+
```
|
|
534
188
|
|
|
535
|
-
The
|
|
536
|
-
|
|
537
|
-
`docs/development.md` — this repo's development runs inside a harness that must not be restarted):
|
|
189
|
+
The browser acceptance suite runs against **model tapes** — recorded LLM exchanges replayed by a
|
|
190
|
+
local proxy, so it needs no model, no key and no GPU, and passes or fails *loudly* on a miss:
|
|
538
191
|
|
|
539
|
-
```bash
|
|
540
|
-
npm run build # emits lib/ from src/
|
|
541
|
-
scripts/dsh-scratch.sh --home .dshdev2 plugin --profile web add "link:$(pwd)"
|
|
542
|
-
scripts/dsh-scratch.sh --home .dshdev2 web --port 0 --no-open # disposable instance, its own port/token
|
|
543
192
|
```
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
`scripts/bootstrap-dev-profile.sh` then boot `.dshdev-local`. Restarts inside one scratch home
|
|
549
|
-
are free; anything that could touch `~/.dsh` is the bug the wrapper exists to make impossible.
|
|
550
|
-
|
|
551
|
-
> **Manual-testing home: `.dshdev-local` (the Local-Qwen @64K target), never `.dshdev`.** The `.dshdev` profile carries
|
|
552
|
-
> `dsh-chapters-probe`, whose rounds are one-shot boot experiments that end in `process.exit` by design
|
|
553
|
-
> — leaving it mounted kills an interactive server seconds after the URL prints (a live boot that
|
|
554
|
-
> answered HTTP 401-with-token-page after 20s once the probe was removed; `scripts/dsh-scratch.sh
|
|
555
|
-
> --home .dshdev2 plugin --profile web remove dsh-chapters-probe` is the fix if it reappears).
|
|
556
|
-
|
|
557
|
-
> A bundle `add` alone does not activate the plugin: bundle layers compose at boot, so a restart is
|
|
558
|
-
> required after any change. A restart also recomposes every session's header and resets cache state
|
|
559
|
-
> profile-wide, so cache measurements only mean something within one process generation.
|
|
560
|
-
|
|
561
|
-
### Trying it out
|
|
562
|
-
|
|
563
|
-
1. In a **Chapters-preset** session, hold several exchanges across a couple of topics, including one
|
|
564
|
-
tool call with a long result.
|
|
565
|
-
2. Ask the agent to call `chapters_segment`. Review the returned ceiling, the real tool-result sizes,
|
|
566
|
-
and the chapters already in the registry; ask for ranges and titles you want.
|
|
567
|
-
3. Ask it to call `chapters_continue` with the approved ranges and a handoff note. An over-budget notice
|
|
568
|
-
must come back as a refusal WITH numbers, not a clipped note.
|
|
569
|
-
4. Switch to the new session (titled, listed). Its first message is the flat TOC, and it should feel
|
|
570
|
-
empty apart from that.
|
|
571
|
-
5. Ask it to `read` a chapter path, then follow one artifact reference, and confirm both return real
|
|
572
|
-
text — this is the reachability check; existence proves nothing.
|
|
573
|
-
6. Separately: in a long enough Chapters session let pre-step pressure compact, confirm a chapter file
|
|
574
|
-
lands beside the durable `compaction/summary` (provider `dsh-chapters`, no `usage`), and `read` the
|
|
575
|
-
chapter from INSIDE the compacted session — that is the engine path earning its name.
|
|
576
|
-
|
|
577
|
-
### Verifying a build
|
|
578
|
-
|
|
579
|
-
`docs/verify.md` has the full 20-step procedure with what to record. The three that decide whether the
|
|
580
|
-
design works at all:
|
|
581
|
-
|
|
582
|
-
1. **The new session's token count is materially smaller than the parent's.** Check this first — every
|
|
583
|
-
other assertion can pass while this one fails silently, and a failure here means the child was seeded
|
|
584
|
-
from the log
|
|
585
|
-
2. **The parent's cache hit rate is unaffected** — continue the parent and confirm
|
|
586
|
-
`usage.cacheReadTokens` on the next step, within one process generation
|
|
587
|
-
3. **The new session builds a stable cache on its second turn** — `usage.cacheReadTokens` is high
|
|
588
|
-
|
|
589
|
-
Plus the correctness set: install and tool listing; TOC as the child's first message; the child listed in
|
|
590
|
-
the sidebar and resumable after restart; `read` reachability of chapters (not mere existence); artifact
|
|
591
|
-
deferral resolves; a second continuation still lists the first's chapters; branching one ancestor twice
|
|
592
|
-
yields two correct non-colliding TOCs; kill mid-continuation and retry leaves no orphan citation and no
|
|
593
|
-
duplicate number; overlapping or gapped ranges are refused; an over-budget handoff note is refused with
|
|
594
|
-
numbers rather than clipped; a hand-edited chapter is flagged from its hash; missing `ctx.fs` degrades to
|
|
595
|
-
a clean refusal.
|
|
596
|
-
|
|
597
|
-
### Removing the plugin
|
|
598
|
-
|
|
599
|
-
```bash
|
|
600
|
-
scripts/dsh-scratch.sh --home .dshdev2 plugin --profile web remove dsh-chapters
|
|
601
|
-
rm -rf .dshdev2/.agent-presets/chapters # the installed preset; user-editable, left on purpose otherwise
|
|
193
|
+
npm ci --prefix dev/dsh-host-lock # the pinned, hermetic DSH host for e2e boots
|
|
194
|
+
export PATH="$PWD/dev/dsh-host-lock/node_modules/.bin:$PATH"
|
|
195
|
+
npm run test:e2e:replay # all seven segments, ~6 min
|
|
196
|
+
bash scripts/bootstrap-dev-profile.sh --home .dshdev-local # scratch dev home, built via npm
|
|
602
197
|
```
|
|
603
198
|
|
|
604
|
-
|
|
199
|
+
Tapes live in `tests/fixtures/model-tape/<project>/`; `E2E_MODEL=record` distills them against a
|
|
200
|
+
local llama.cpp endpoint (dev-only — CI never touches the model). When **product prompts change**
|
|
201
|
+
(persona, tool descriptions, notice text, thresholds) re-record the affected projects; documentation
|
|
202
|
+
edits are free. Agent-facing rules (the four invariants, the tape discipline, the coverage ledgers)
|
|
203
|
+
live in [AGENTS.md](AGENTS.md); the workflow loop in [docs/development.md](docs/development.md);
|
|
204
|
+
verification checklists in [docs/verify.md](docs/verify.md). Hosted CI
|
|
205
|
+
(`.github/workflows/ci.yml`) runs the gate plus the full tape-replay chain on every push;
|
|
206
|
+
releases publish automatically from a bare-semver tag via **npm trusted publishing (OIDC)** with
|
|
207
|
+
provenance attestations — no deploy secrets.
|
|
605
208
|
|
|
606
209
|
## Contributing
|
|
607
210
|
|
|
608
|
-
|
|
609
|
-
`
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
- **
|
|
615
|
-
-
|
|
616
|
-
|
|
617
|
-
- **
|
|
618
|
-
|
|
619
|
-
- **Never
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
- **Call `next()` in waterfall listeners** unless deliberately short-circuiting, and document why.
|
|
630
|
-
- **Put tunable parameters in config.** If `cordis.yml` can change it, it does not belong in code.
|
|
631
|
-
|
|
632
|
-
### Reporting a bug
|
|
633
|
-
|
|
634
|
-
Include parent, child, and root session ids, the store directory, the archive ceiling, the chapter paths
|
|
635
|
-
and any artifact references, and `usage.cacheReadTokens` for the parent's steps immediately before and
|
|
636
|
-
after plus the child's first two steps — noting **which process generation** each was taken in. Above all,
|
|
637
|
-
include the **first-request token counts of both sessions**: a silently re-seeded child is the most
|
|
638
|
-
damaging regression, and that number is what catches it.
|
|
639
|
-
|
|
640
|
-
---
|
|
641
|
-
|
|
642
|
-
## License
|
|
643
|
-
|
|
644
|
-
Apache 2.0
|
|
211
|
+
- **Start with an issue** for anything that changes behavior; the design record in
|
|
212
|
+
`docs/knowledge-repo.md` explains *why* things are shaped as they are, and amendments are human
|
|
213
|
+
decisions recorded in git.
|
|
214
|
+
- **Keep the four invariants inviolable** (never rewrite the log; never seed from the parent's
|
|
215
|
+
events; never touch the shared system prompt; never let the model author bodies). A PR that
|
|
216
|
+
trades one of these for convenience will be closed, not merged.
|
|
217
|
+
- **CI defines "done"**: deterministic suite green, coverage gate green, and — if your change moves
|
|
218
|
+
a model-visible prompt — the tape-replay chain re-recorded and green. Refuse-with-numbers beats
|
|
219
|
+
silent fallback everywhere in this codebase; follow that doctrine in new code too.
|
|
220
|
+
- **Test what you touch, and say how**: measured claims (this README's numbers are measured),
|
|
221
|
+
cited failure modes, no speculative generality.
|
|
222
|
+
- **Never commit credentials** or machine paths into fixtures; tokens belong to the DSH home only.
|
|
223
|
+
- Releases are maintainer-driven: bump `version`, land to `main`, push the tag — CI publishes.
|
|
224
|
+
|
|
225
|
+
License: see [LICENSE](LICENSE) (Copyright 2026 Fractal Synapse Labs).
|
|
226
|
+
|
|
227
|
+
> **On the name.** The package ships as `@treeseed/dsh-chapters` (npm scope `treeseed`; the GitHub
|
|
228
|
+
> org is `treeseed-ai`). Earlier candidates — `dsh-chapter-fork`, anything with "compact" — were
|
|
229
|
+
> rejected because they claimed something false: in DSH, *fork* means copying the parent's history,
|
|
230
|
+
> the one operation this plugin must never perform. The name names the artifact instead. Reasoning:
|
|
231
|
+
> [docs/architecture.md § Naming](docs/architecture.md#naming).
|