@treeseed/dsh-chapters 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +14 -0
- package/README.md +644 -0
- package/cordis.patch.yml +88 -0
- package/docs/architecture.md +460 -0
- package/docs/contract.md +375 -0
- package/docs/development.md +437 -0
- package/docs/host-compaction-seam.md +117 -0
- package/docs/knowledge-repo.md +730 -0
- package/docs/provider.md +98 -0
- package/docs/verify.md +200 -0
- package/lib/archive.d.ts +88 -0
- package/lib/archive.js +114 -0
- package/lib/archive.js.map +1 -0
- package/lib/arrival.d.ts +23 -0
- package/lib/arrival.js +86 -0
- package/lib/arrival.js.map +1 -0
- package/lib/artifact-query.d.ts +53 -0
- package/lib/artifact-query.js +130 -0
- package/lib/artifact-query.js.map +1 -0
- package/lib/client.js +234 -0
- package/lib/client.js.map +1 -0
- package/lib/commands.d.ts +28 -0
- package/lib/commands.js +305 -0
- package/lib/commands.js.map +1 -0
- package/lib/compose.d.ts +30 -0
- package/lib/compose.js +131 -0
- package/lib/compose.js.map +1 -0
- package/lib/continue-core.d.ts +153 -0
- package/lib/continue-core.js +252 -0
- package/lib/continue-core.js.map +1 -0
- package/lib/engine-core.d.ts +219 -0
- package/lib/engine-core.js +405 -0
- package/lib/engine-core.js.map +1 -0
- package/lib/engine.d.ts +211 -0
- package/lib/engine.js +571 -0
- package/lib/engine.js.map +1 -0
- package/lib/enrich-queue.d.ts +66 -0
- package/lib/enrich-queue.js +100 -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 +73 -0
- package/lib/enrich-wire.js +209 -0
- package/lib/enrich-wire.js.map +1 -0
- package/lib/enrich.d.ts +73 -0
- package/lib/enrich.js +150 -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 +81 -0
- package/lib/index.js +305 -0
- package/lib/index.js.map +1 -0
- package/lib/indexing.d.ts +85 -0
- package/lib/indexing.js +212 -0
- package/lib/indexing.js.map +1 -0
- package/lib/injections.d.ts +66 -0
- package/lib/injections.js +83 -0
- package/lib/injections.js.map +1 -0
- package/lib/notice.d.ts +60 -0
- package/lib/notice.js +84 -0
- package/lib/notice.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 +120 -0
- package/lib/registry.js +130 -0
- package/lib/registry.js.map +1 -0
- package/lib/render.d.ts +40 -0
- package/lib/render.js +280 -0
- package/lib/render.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 +24 -0
- package/lib/rules-commands.js +138 -0
- package/lib/rules-commands.js.map +1 -0
- package/lib/rules.d.ts +90 -0
- package/lib/rules.js +185 -0
- package/lib/rules.js.map +1 -0
- package/lib/search.d.ts +48 -0
- package/lib/search.js +152 -0
- package/lib/search.js.map +1 -0
- package/lib/signature.d.ts +28 -0
- package/lib/signature.js +149 -0
- package/lib/signature.js.map +1 -0
- package/lib/store.d.ts +130 -0
- package/lib/store.js +235 -0
- package/lib/store.js.map +1 -0
- package/lib/sync.d.ts +183 -0
- package/lib/sync.js +719 -0
- package/lib/sync.js.map +1 -0
- package/lib/tools.d.ts +84 -0
- package/lib/tools.js +581 -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 +100 -0
- package/lib/types.js +19 -0
- package/lib/types.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/package.json +132 -0
- package/presets/chapters/agent.cordis.yml +272 -0
- package/presets/chapters/preset.yml +3 -0
- package/presets/probe-roots/agent.cordis.yml +11 -0
- package/presets/probe-roots/preset.yml +3 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Copyright 2026 Fractal Synapse Labs
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
|
14
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
# dsh-chapters
|
|
2
|
+
|
|
3
|
+
A DeepSeek Harness plugin for **topic-chaptered conversation continuation with lossless archiving**.
|
|
4
|
+
|
|
5
|
+
When a long conversation gets expensive, this plugin archives it as Markdown chapters in a project
|
|
6
|
+
store and opens a **new session whose entire content is a Table of Contents** of that archive. The
|
|
7
|
+
model reloads any chapter on demand with the `read` tool. The original conversation is never rewritten
|
|
8
|
+
and its prompt cache is never disturbed.
|
|
9
|
+
|
|
10
|
+
> **On the name.** It used to be `dsh-chapter-fork`, and that was misleading in a way that mattered: in
|
|
11
|
+
> DSH, "fork" means *copy the parent's history into the child* — the one operation this plugin must never
|
|
12
|
+
> perform — while "compact" or "summarize" would imply the lossy in-place editing it replaces. It is now
|
|
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.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Why This Exists
|
|
21
|
+
|
|
22
|
+
The problem is **not** that long conversations hit context limits, and it is not really compaction either.
|
|
23
|
+
The two problems that actually hurt, on local hardware with slow prefill:
|
|
24
|
+
|
|
25
|
+
1. **Forgetting details that cannot easily be recovered.** A summary drops the specifics — the exact flag,
|
|
26
|
+
the line number, the error text — and the model has no handle to go get them back.
|
|
27
|
+
2. **The wall-clock cost of summarizing.** Producing a summary means prefilling the whole history into one
|
|
28
|
+
auxiliary call. At local prefill rates that is not slow, it is *stopping-you-from-working* slow:
|
|
29
|
+
|
|
30
|
+
| History | 30 tok/s | 100 tok/s | 300 tok/s |
|
|
31
|
+
|---|---|---|---|
|
|
32
|
+
| 32K | 18 min | 5 min | 2 min |
|
|
33
|
+
| 100K | **55 min** | 17 min | 3.5 min |
|
|
34
|
+
|
|
35
|
+
…and then it does it **again** the next time the window fills.
|
|
36
|
+
|
|
37
|
+
`dsh-chapters` removes the summarization step entirely. Chapters are **written from the session log** — a
|
|
38
|
+
file copy, not an inference — and the index that replaces the history is **generated deterministically**,
|
|
39
|
+
costing **zero model tokens**. On a 32K-capped model the archived index measured **~102 tokens** where the
|
|
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.
|
|
324
|
+
|
|
325
|
+
## Architecture
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
dsh-chapters/
|
|
329
|
+
├── src/
|
|
330
|
+
│ ├── index.ts # plugin entry (name, inject, Config, apply)
|
|
331
|
+
│ ├── chapter-store.ts # chapter rendering, artifact deferral, store I/O
|
|
332
|
+
│ ├── toc.ts # flat TOC assembly from an ancestry walk
|
|
333
|
+
│ ├── segmenter.ts # topic boundary proposal (ctx.llm.stream)
|
|
334
|
+
│ ├── budget.ts # preflight: measure, refuse, never truncate
|
|
335
|
+
│ └── continuation.ts # session creation + workspace attach
|
|
336
|
+
├── docs/
|
|
337
|
+
│ ├── contract.md # DSH API facts, schema systems, file:line references
|
|
338
|
+
│ ├── architecture.md # design decisions and their reasoning
|
|
339
|
+
│ └── verify.md # verification procedures and test strategy
|
|
340
|
+
├── examples/ # cloned reference repositories (gitignored)
|
|
341
|
+
├── cordis.patch.yml # mounts the plugin into a profile's bundle list
|
|
342
|
+
├── package.json
|
|
343
|
+
└── tsconfig.json
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
The `src/` tree is still to be written; the repository currently holds docs and the `examples/`
|
|
347
|
+
references. `AGENTS.md` is a short core for small-context agents; the detail is in `docs/`.
|
|
348
|
+
|
|
349
|
+
### Design constraint: never modify the prefix
|
|
350
|
+
|
|
351
|
+
The most important rule: **never modify the prefix of an existing session.** No `compactRegion`, no
|
|
352
|
+
`SurfaceManager.replaceGeneration`, no in-place rewriting. If you reach for a span-replacement API,
|
|
353
|
+
stop — that is the operation this plugin exists to avoid.
|
|
354
|
+
|
|
355
|
+
### Design constraint: never seed from the parent's events
|
|
356
|
+
|
|
357
|
+
Same reason, opposite failure: a continuation must not inherit its parent's log. One synthetic TOC
|
|
358
|
+
event at `seq = 0`, and nothing else.
|
|
359
|
+
|
|
360
|
+
### Design constraint: the header is global, so never contribute to it
|
|
361
|
+
|
|
362
|
+
System prompt and tool schemas are not stored per session; they are composed per request from the agent
|
|
363
|
+
preset plus plugin contributions. So:
|
|
364
|
+
|
|
365
|
+
- inheriting the parent's header is automatic — there is no per-session header to protect
|
|
366
|
+
- **contributing a system-prompt section would be a cache bug**, changing every session's prefix in the
|
|
367
|
+
profile. Teaching the model about chapters is the TOC notice's job. `dsh-session-fork` does contribute
|
|
368
|
+
a section in `src/prompt.ts`; copying that here would violate this rule
|
|
369
|
+
- **cache validity is per-process generation.** A restart or config edit recomposes the header and resets
|
|
370
|
+
cache state profile-wide, which is also why the dev loop's restarts limit what cache measurements prove
|
|
371
|
+
|
|
372
|
+
### Design constraint: append-only, with no exceptions
|
|
373
|
+
|
|
374
|
+
Chapters are never copied, rewritten, or renumbered. Splitting an oversized chapter writes *new* chapters
|
|
375
|
+
alongside the old, and only at continuation boundaries. Nothing derived is written back into the store —
|
|
376
|
+
which is part of why there is no separate manifest file: a regenerated index would be exactly the
|
|
377
|
+
mutable, drift-prone artifact this rule exists to prevent.
|
|
378
|
+
|
|
379
|
+
### Design constraint: two stores, for two reasons
|
|
380
|
+
|
|
381
|
+
Chapters and artifacts are plain files **in the workspace**, because the `read` tool must reach them —
|
|
382
|
+
and it runs under the session's file sandbox. A store under `$HOME` can be writable while being
|
|
383
|
+
completely unreachable to the model, silently dead-ending every TOC bullet. Chain bookkeeping — ancestry,
|
|
384
|
+
numbering, hashes, ranges — lives in a DSH **storage domain**, the sanctioned durable store. Trade-off:
|
|
385
|
+
a workspace-relative root scopes a chain to one working directory.
|
|
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`:
|
|
455
|
+
|
|
456
|
+
```
|
|
457
|
+
/chapters-link # show upstream + mirror state (git-status-like)
|
|
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
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
`knowledgeRemote` in the plugin's profile config supplies the same URL as a lazy default for
|
|
464
|
+
unlinked workspaces. Status any time: `/chapters-status`.
|
|
465
|
+
The design record: [docs/knowledge-repo.md](docs/knowledge-repo.md).
|
|
466
|
+
|
|
467
|
+
**Not yet built (P2/P3 of the record):** model-assisted enrichment of the vocabulary (idle-batched,
|
|
468
|
+
per-harness disableable), and the rules-as-chapters lifecycle (`/chapters-rule add|approve|revoke`,
|
|
469
|
+
core-section budgets). Both are specced and phased; the corpus layer works without them.
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## Reference Repositories
|
|
474
|
+
|
|
475
|
+
Cloned into `examples/` (gitignored). Read the **first** one that answers your question; do not read all
|
|
476
|
+
four. `docs/architecture.md` has the full table with line numbers.
|
|
477
|
+
|
|
478
|
+
| Repository | Role | When to read |
|
|
479
|
+
|---|---|---|
|
|
480
|
+
| **`dsh-session-fork`** | Primary precedent | Session creation, workspace attach, storage-domain registry, tool registration. |
|
|
481
|
+
| **`deepseek-harness`** | Official source | Ground truth for `agents.create`, `SessionHeader`, `defineTool`, `ctx.storageDomain`. |
|
|
482
|
+
| **`dsh-compact`** | Config and summarization | The `Schema<Config>` idiom and `ctx.llm.stream()` pattern only. |
|
|
483
|
+
| **`dsh-plugin-dev-kb`** | Minimal plugin shell | When you need a plugin skeleton that doesn't touch the agent loop. |
|
|
484
|
+
|
|
485
|
+
Start with `dsh-session-fork`: `src/vendor/fork.ts`, `src/branch.ts`, `src/store.ts`, `src/tools.ts`.
|
|
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.
|
|
506
|
+
|
|
507
|
+
**Phase 2 — Segmentation as a function, and pressure detection.** `chapters_segment` returning validated
|
|
508
|
+
ranges, built as a callable usable **with or without an agent turn**; a `turn/end` pressure check that
|
|
509
|
+
surfaces a *suggestion*. Both the UI button and Phase 3 depend on this seam, which is why it is not last.
|
|
510
|
+
|
|
511
|
+
**Phase 3 — Automatic continuation.** Fires on Phase 2's detector at `turn/end` only, through the same
|
|
512
|
+
preflight. Never mid-step.
|
|
513
|
+
|
|
514
|
+
**Phase 4 — Bound the index.** Split oversized chapters at boundaries; bullet folding past the TOC budget;
|
|
515
|
+
a reload budget in the TOC preamble; artifact refcounting and a retention/prune command.
|
|
516
|
+
|
|
517
|
+
**Phase 5 — UI trigger.** A button in the conversation title bar sharing Phase 2's segmentation.
|
|
518
|
+
Deliberately last: it needs a client bundle and the UI slot/locale/connection injects — the heaviest
|
|
519
|
+
packaging in the project, for ergonomics rather than correctness.
|
|
520
|
+
|
|
521
|
+
Permanently out of scope: in-place span compaction, prefix rewriting, seeding a continuation from parent
|
|
522
|
+
events, contributed system-prompt sections, and truncating anything to fit.
|
|
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
|
|
534
|
+
|
|
535
|
+
The entry point is compiled output, so build before installing — and **never run `dsh` without an
|
|
536
|
+
isolated `DSH_HOME`**: `scripts/dsh-scratch.sh` forces it and refuses the live profile (see
|
|
537
|
+
`docs/development.md` — this repo's development runs inside a harness that must not be restarted):
|
|
538
|
+
|
|
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
|
+
```
|
|
544
|
+
|
|
545
|
+
Then open the printed URL, **pick the "Chapters" preset** (the plugin installs it into the scratch
|
|
546
|
+
profile's `.agent-presets/` on first boot), and use the plugin there. For the real target
|
|
547
|
+
(Local Qwen, 64K window — the minimum we support for real coding/research work) build the profile first:
|
|
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
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
---
|
|
605
|
+
|
|
606
|
+
## Contributing
|
|
607
|
+
|
|
608
|
+
Read the constraints first — they are not stylistic preferences, they are the reason the plugin exists.
|
|
609
|
+
`AGENTS.md` is the short core; `docs/contract.md` has the API facts with citations.
|
|
610
|
+
|
|
611
|
+
### Hard rules
|
|
612
|
+
|
|
613
|
+
- **Never modify the prefix of an existing session.** No span replacement, no in-place rewriting.
|
|
614
|
+
- **Never seed a continuation from its parent's events.** One synthetic TOC event, at `seq = 0`.
|
|
615
|
+
- **Never let the model author chapter bodies.** It supplies ranges and labels; the plugin renders.
|
|
616
|
+
- **Never truncate to fit.** Measure and refuse with numbers.
|
|
617
|
+
- **Never contribute a system-prompt section.** The header is process-global, so contributing invalidates
|
|
618
|
+
every session's cache in the profile.
|
|
619
|
+
- **Never copy or rewrite an existing chapter.** The store is append-only across the subtree, with no
|
|
620
|
+
documented exceptions and nothing derived written back into it.
|
|
621
|
+
- **Never reorganize chapters mid-session.** Continuation boundaries only.
|
|
622
|
+
- **Never fire the automatic trigger mid-step.** Turn boundaries only.
|
|
623
|
+
- **Never trust agent-supplied ranges.** Validate ordering, overlap, and coverage against the archive
|
|
624
|
+
ceiling — an overlap duplicates archived text and a gap silently drops it.
|
|
625
|
+
- **Use schemastery for plugin configuration.** `import Schema from '@deepseek-ai/schemastery'` — a
|
|
626
|
+
**default** import — and `export const Config: Schema<Config> = Schema.object({ … })`. Plain objects are
|
|
627
|
+
rejected. Tool `parameters` are the opposite: plain declarative specs, not schemastery.
|
|
628
|
+
- **Always give a tool an `output` schema.** It is mandatory on every `ToolDefinition`.
|
|
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
|