@treeseed/dsh-chapters 0.1.24 → 0.2.1
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 +2 -1
- package/cordis.patch.yml +49 -1
- package/docs/architecture.md +1 -496
- package/docs/backlog.md +234 -4
- package/docs/contract.md +127 -1
- package/docs/development.md +248 -12
- package/docs/host-compaction-seam.md +108 -0
- package/docs/knowledge-repo.md +532 -16
- package/docs/project-state.md +323 -0
- package/docs/verify.md +266 -0
- package/lib/archive.d.ts +49 -1
- package/lib/archive.js +56 -2
- package/lib/archive.js.map +1 -1
- package/lib/arrival.js +24 -10
- package/lib/arrival.js.map +1 -1
- package/lib/attach.d.ts +46 -0
- package/lib/attach.js +120 -0
- package/lib/attach.js.map +1 -0
- package/lib/auto-save.d.ts +74 -0
- package/lib/auto-save.js +175 -26
- package/lib/auto-save.js.map +1 -1
- package/lib/client.js +413 -11
- package/lib/client.js.map +1 -1
- package/lib/commands.d.ts +8 -1
- package/lib/commands.js +135 -7
- package/lib/commands.js.map +1 -1
- package/lib/compose.js +25 -6
- package/lib/compose.js.map +1 -1
- package/lib/continue-core.d.ts +24 -1
- package/lib/continue-core.js +41 -9
- package/lib/continue-core.js.map +1 -1
- package/lib/diag.d.ts +55 -0
- package/lib/diag.js +186 -0
- package/lib/diag.js.map +1 -0
- package/lib/engine-core.d.ts +53 -1
- package/lib/engine-core.js +64 -7
- package/lib/engine-core.js.map +1 -1
- package/lib/engine.d.ts +99 -0
- package/lib/engine.js +488 -36
- package/lib/engine.js.map +1 -1
- package/lib/enrich-queue.js +28 -2
- package/lib/enrich-queue.js.map +1 -1
- package/lib/enrich-wire.d.ts +92 -2
- package/lib/enrich-wire.js +135 -17
- package/lib/enrich-wire.js.map +1 -1
- package/lib/enrich.d.ts +27 -2
- package/lib/enrich.js +114 -18
- package/lib/enrich.js.map +1 -1
- package/lib/index.d.ts +52 -0
- package/lib/index.js +182 -2
- package/lib/index.js.map +1 -1
- package/lib/indexing.d.ts +7 -0
- package/lib/indexing.js.map +1 -1
- package/lib/injections.d.ts +1 -0
- package/lib/injections.js +38 -0
- package/lib/injections.js.map +1 -1
- package/lib/last-resort.d.ts +24 -0
- package/lib/last-resort.js +27 -0
- package/lib/last-resort.js.map +1 -0
- package/lib/prompt.d.ts +130 -0
- package/lib/prompt.js +255 -0
- package/lib/prompt.js.map +1 -0
- package/lib/registry.d.ts +39 -1
- package/lib/registry.js +76 -9
- package/lib/registry.js.map +1 -1
- package/lib/render.d.ts +31 -3
- package/lib/render.js +75 -3
- package/lib/render.js.map +1 -1
- package/lib/repair-run.d.ts +49 -0
- package/lib/repair-run.js +423 -0
- package/lib/repair-run.js.map +1 -0
- package/lib/repair.d.ts +200 -0
- package/lib/repair.js +354 -0
- package/lib/repair.js.map +1 -0
- package/lib/rules-commands.d.ts +22 -0
- package/lib/rules-commands.js +177 -20
- package/lib/rules-commands.js.map +1 -1
- package/lib/rules.d.ts +184 -7
- package/lib/rules.js +312 -27
- package/lib/rules.js.map +1 -1
- package/lib/save-core.d.ts +31 -1
- package/lib/save-core.js +153 -17
- package/lib/save-core.js.map +1 -1
- package/lib/session-rules.d.ts +222 -0
- package/lib/session-rules.js +454 -0
- package/lib/session-rules.js.map +1 -0
- package/lib/store.d.ts +22 -1
- package/lib/store.js +94 -17
- package/lib/store.js.map +1 -1
- package/lib/sync.js +13 -1
- package/lib/sync.js.map +1 -1
- package/lib/tools.d.ts +14 -0
- package/lib/tools.js +321 -32
- package/lib/tools.js.map +1 -1
- package/lib/types.d.ts +12 -1
- package/lib/types.js.map +1 -1
- package/lib/version.d.ts +9 -0
- package/lib/version.js +10 -0
- package/lib/version.js.map +1 -0
- package/lib/warnings.d.ts +10 -0
- package/lib/warnings.js +18 -0
- package/lib/warnings.js.map +1 -0
- package/package.json +9 -3
package/docs/knowledge-repo.md
CHANGED
|
@@ -286,6 +286,29 @@ Honest quality bar: strong for coding sessions (files and commands leave loud si
|
|
|
286
286
|
degrading to time/size splitting for pure-prose conversations — never worse than today's
|
|
287
287
|
behavior, never blocking.
|
|
288
288
|
|
|
289
|
+
**Amendment 2026-10-22 (#61, item 6) — a delegate's report to its parent IS conversation.** The
|
|
290
|
+
injection screen (H1) drops host-injected `user/message` events, and it was right to drop the project's
|
|
291
|
+
instruction files. But the same rule was also dropping two shapes the harness produces *about this
|
|
292
|
+
session's own work*:
|
|
293
|
+
|
|
294
|
+
| source (measured on 0.2.x logs) | content | was | now |
|
|
295
|
+
|---|---|---|---|
|
|
296
|
+
| `{ kind: 'agent-message', form: 'relay' }` | "Agent <id> sent a message: Both Starlight documentation files have been written…" | screened | **archived** |
|
|
297
|
+
| `{ kind: 'subagent-settled', form: 'notice' }` | "Background subagent <id> finished and will do no further work…" | screened | **archived** |
|
|
298
|
+
| `{ kind: 'agent-instructions', form: 'instructions' }` | a project's workspace instruction file | screened | **still screened** |
|
|
299
|
+
|
|
300
|
+
Measured cost of the old rule in the first `chapters`-preset project: **44 markers, 248,150 chars ≈ 62K
|
|
301
|
+
est tokens** of delegated results thrown away, including an 18,253-char spike report — so the work that
|
|
302
|
+
crossed the delegation edge was lost through **both** doors built to capture it (the parent's archive
|
|
303
|
+
dropped the report; the child's settlement had already archived its own bytes, so the parent's chapter
|
|
304
|
+
recorded a delegation with no outcome).
|
|
305
|
+
|
|
306
|
+
Still named by **producer AND form**, exactly like the `plan-mode` precedent it joins — the list cannot
|
|
307
|
+
widen by accident into the general plugin-source exemption, and an instruction file arriving with some
|
|
308
|
+
other form still screens. `isHumanAuthored` stays strict on purpose: the topic-signature layer keeps
|
|
309
|
+
ignoring these, because the r28 lesson (injected text moves chapter boundaries) is unchanged, and a
|
|
310
|
+
2 KB report is exactly the kind of text that would drag a signature toward `docs/…` filenames.
|
|
311
|
+
|
|
289
312
|
### 4.3 The completeness guard
|
|
290
313
|
|
|
291
314
|
Chapters include **only collections with a `turn/end`** inside the span. If a compaction
|
|
@@ -294,6 +317,33 @@ cannot produce one, §4.1), the composition clamps to the last completed collect
|
|
|
294
317
|
TOC notes the clamp. The unfinished collection stays in the live context, per the user
|
|
295
318
|
requirement, and composes at the next archive.
|
|
296
319
|
|
|
320
|
+
**Amendment 2026-10-22 (#59, measured in the first real `chapters`-preset project).** This guard is
|
|
321
|
+
one-directional: it keeps unfinished work OUT, and says nothing about what may silently fail to come
|
|
322
|
+
IN. Composing from collections alone means a completed turn that never got a signature vanishes — not
|
|
323
|
+
truncated, not deferred, **absent from every chapter and from every index**, with no warning. That is
|
|
324
|
+
what happened: `session-2edf9694` had 32 `turn/end` events and 31 collections, and the 203-event span
|
|
325
|
+
between chapters 27 and 28 — a complete turn including the maintainer's "I consider it foundational to
|
|
326
|
+
the project" hosting brief and its implementation — exists in no chapter in any tree, and the three
|
|
327
|
+
verbatim phrases from it match zero files in the store.
|
|
328
|
+
|
|
329
|
+
The guard is therefore **bidirectional** now:
|
|
330
|
+
|
|
331
|
+
1. Nothing unfinished is archived (the rule as written above).
|
|
332
|
+
2. **Nothing finished is dropped.** The composed ranges must cover `[spanStart..spanEnd]`
|
|
333
|
+
contiguously; an unsigned but *completed* span is absorbed into its neighbouring chapter (a
|
|
334
|
+
wider range, never a re-titled one), and every door asserts the coverage before writing,
|
|
335
|
+
refusing with the exact seqs rather than proceeding. The claim a chapter makes is
|
|
336
|
+
"everything in this range is here", so a hole is a false statement, not a small one.
|
|
337
|
+
3. A door may not treat "no collection covered it" as "incomplete turn": completeness is a
|
|
338
|
+
`turn/end` fact, read from the log, not inferred from the presence of a signature — a lost
|
|
339
|
+
signature is a registry defect (see docs/architecture.md § The row is shared), and the note the
|
|
340
|
+
composer used to emit claimed the opposite while dropping completed work.
|
|
341
|
+
|
|
342
|
+
`coverage()` (`src/archive.ts`) existed for exactly this and was called in one door, for a warning
|
|
343
|
+
that got counted and thrown away. The same rule now covers model-submitted ranges:
|
|
344
|
+
`validateRanges` refuses a **gap**, not only an overlap — AGENTS.md already said "a gap silently
|
|
345
|
+
drops it"; the check did not exist.
|
|
346
|
+
|
|
297
347
|
### 4.4 Consequence: composition is archive-time-only
|
|
298
348
|
|
|
299
349
|
Because chapters are append-only (invariant 3), the merge threshold must be right *when the
|
|
@@ -415,7 +465,33 @@ search, and sync. Frontmatter: `kind: rule`, plus `category`, `status`, and prov
|
|
|
415
465
|
plugin's modularity is what makes this nearly free — the chapter code paths are parameterized
|
|
416
466
|
by `kind`, not duplicated.
|
|
417
467
|
|
|
418
|
-
|
|
468
|
+
**Two scopes, one store, one format** (Amendment 2026-10-20, §15). Session rules are *not* a
|
|
469
|
+
second subsystem: they are `kind: rule` files in the same `rules/<harness>/` tree, with the same
|
|
470
|
+
write-once immutability, the same `list` surface, the same search index. What differs is **how
|
|
471
|
+
they get authored and how they become active**:
|
|
472
|
+
|
|
473
|
+
| Tier | Authored by | Activation | Transport | Carried by |
|
|
474
|
+
|---|---|---|---|---|
|
|
475
|
+
| **project** | `/chapters-rule add`, `chapters_rule_propose` (agent) | human `approve` → per-machine curation fact (§7.2) | synced to the pool | continue **and** fork |
|
|
476
|
+
| **session** | `ADD RULE:` marker (deterministic), enrichment (optional) | **derived** (§7.5) — no fact, no approval | local only; never staged (§7.5) | continue only — dropped by fork (§7.6) |
|
|
477
|
+
|
|
478
|
+
Extra frontmatter on every rule file: `scope: project | session` (absent ⇒ `project`, so every
|
|
479
|
+
pre-existing record keeps working byte-identically), `session: <sessionId>` (session tier only),
|
|
480
|
+
`derivedFrom: human | marker | model`, `pinned: true` (§7.4). Same `category`, `status`,
|
|
481
|
+
`sha256`, provenance chain as before.
|
|
482
|
+
|
|
483
|
+
**Why the split is safe.** A rule that only ever governs one machine's session tree is not shared
|
|
484
|
+
state, so §11.3's blast-radius argument does not apply to it — and the per-machine approval
|
|
485
|
+
boundary of §7.2 is left exactly as the 2026-09-20 amendment set it. Cross-checkout carry of
|
|
486
|
+
*project* rules needed no new mechanism: `projectKey` is UUIDv5 over the canonicalized remote
|
|
487
|
+
(§2.3) and `harnessId` defaults to the hostname, so several checkouts of one repository already
|
|
488
|
+
resolve to one rule namespace on one machine. §7.8 is about making the cases where that identity
|
|
489
|
+
*diverges* loud, not about building propagation.
|
|
490
|
+
|
|
491
|
+
### 7.2 Lifecycle: proposed → core, approved in-session *(the project tier)*
|
|
492
|
+
|
|
493
|
+
This section governs `scope: project` — the shared, team-visible tier. The session tier's
|
|
494
|
+
activation is derived and needs no approval (§7.5).
|
|
419
495
|
|
|
420
496
|
- **Proposal:** a user command (`/chapters-rule add <category> <text>`) or an agent's async
|
|
421
497
|
derivation from a completed session (P3, always `status: proposed`, provenance-tagged with
|
|
@@ -439,14 +515,35 @@ by `kind`, not duplicated.
|
|
|
439
515
|
in the loop is the one who types `approve`; the git diff of the rule file is the audit
|
|
440
516
|
trail. This is the trust boundary for shared state (§11.3).
|
|
441
517
|
|
|
442
|
-
### 7.3 The rules
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
518
|
+
### 7.3 The rules block: what a rebuilt head shows
|
|
519
|
+
|
|
520
|
+
One builder renders the rules block (`buildRulesSection`), and **both** places a head is rebuilt
|
|
521
|
+
use it — the continuation notice and the in-place compaction checkpoint (§7.6). The block is O(1)
|
|
522
|
+
in corpus size (§1 invariant 6) and, on an empty or unlinked corpus, renders **zero bytes**
|
|
523
|
+
(`kind: 'none'` → nothing at the call site): load-bearing for the tape corpus, which contains no
|
|
524
|
+
rules block anywhere (§13 P3).
|
|
525
|
+
|
|
526
|
+
Contents (all O(1), §1 invariant 6):
|
|
527
|
+
|
|
528
|
+
- **Active rules, verbatim,** selected by the three classes of §7.4 — pinned, then
|
|
529
|
+
relevance-selected, then index-only. Project tier and session tier render under their own
|
|
530
|
+
headers so the model can tell whose order it is reading.
|
|
531
|
+
- **A withheld line — mandatory.** When the caps drop anything, the block says so with numbers:
|
|
532
|
+
`9 project rules active, 4 shown — /chapters-rule list`. Selection by relevance is legitimate
|
|
533
|
+
(§7.4); *silent* selection is the exact failure this feature exists to remove, because a rule
|
|
534
|
+
the human believes is active and is not, is worse than a rule that was never captured.
|
|
535
|
+
- **The token ceiling stays refuse-don't-truncate:** if the selected set exceeds
|
|
536
|
+
`coreRulesBudgetTokens`, the builder returns `refusal` with per-rule token numbers, the caller
|
|
537
|
+
omits the block rather than clipping (§1.2), and the refusal is reported — never swallowed.
|
|
538
|
+
Omission stays non-blocking: rules are additive, and a broken mirror or an overflowing ruleset
|
|
539
|
+
must not stop a continuation or a compaction.
|
|
540
|
+
- **Precedence line** (§7.7): session rules refine and add; they never override a project rule.
|
|
541
|
+
- **Per-rule warrant** (§7.5, amended 2026-10-21): a session rule renders with how it was
|
|
542
|
+
established — `you said this` (marker), `stated by you, mined from ch.N`, or `inferred from ch.N
|
|
543
|
+
— nobody said it`. Inference is allowed to govern, but never anonymously.
|
|
544
|
+
- **The speak-up line**, present only when a block carries session rules: the agent names by id
|
|
545
|
+
which of them shaped its reply, or says none did. A project-only block keeps its exact pre-#50
|
|
546
|
+
bytes (verify.md R0 pins that), so nothing is spent where nothing is inferred.
|
|
450
547
|
- **Rules category index:** category names + one-line essence + paths, with the instruction
|
|
451
548
|
*load a rule category when this session's topics overlap a category* — i.e., the agent
|
|
452
549
|
reads the full category from the repo when (and only when) relevant.
|
|
@@ -458,11 +555,197 @@ The notice carries (all O(1), §1 invariant 6):
|
|
|
458
555
|
**message count** — not token count, since tokenization is not comparable across models —
|
|
459
556
|
and parent/fork links) unchanged.
|
|
460
557
|
|
|
461
|
-
### 7.4
|
|
558
|
+
### 7.4 Topic-matched selection (raised from the old P3b sketch)
|
|
559
|
+
|
|
560
|
+
The record deferred "topic-matched auto-inclusion" because always-render-everything does not scale
|
|
561
|
+
against an O(1) head. With automatic capture the corpus of *active* rules now grows on its own, so
|
|
562
|
+
selection moves from garnish to requirement, and it is built here on machinery that already existed
|
|
563
|
+
purely for pull-based search.
|
|
564
|
+
|
|
565
|
+
Three render classes, in this order, under `projectRulesDisplayCap` / `sessionRulesDisplayCap`
|
|
566
|
+
(count) and `coreRulesBudgetTokens` (tokens):
|
|
567
|
+
|
|
568
|
+
1. **Pinned** (`pinned: true`, human-set via `/chapters-rule pin`) — always rendered, first.
|
|
569
|
+
Ground rules are *old* by definition, so any scheme that ranks by recency or topicality evicts
|
|
570
|
+
them first; the pinned class is the floor that keeps that from being a surprise.
|
|
571
|
+
2. **Relevance-selected** — `rules.selectRules`, the ONE selector for what a head shows:
|
|
572
|
+
pinned first, then topicality against a term bag, then the pre-#50 category→id order. The term
|
|
573
|
+
bag is the archived span's stored turn signatures for a rebuilt head and the task query for
|
|
574
|
+
the `chapters_rules` door — both paths share the selector, so the head and the door can never
|
|
575
|
+
disagree about what applies. `search.rankEntries` stays the *corpus* scorer (§8): ranking an
|
|
576
|
+
unbounded archive against a query and selecting a bounded set for one head are different jobs,
|
|
577
|
+
and this round kept one implementation of each rather than bending a rebuilt head to read index
|
|
578
|
+
shards. What is forbidden is a **third** rule-picker living in a door or a checkpoint — a
|
|
579
|
+
source-shape row fails if either stops calling the shared builder.
|
|
580
|
+
3. **Index-only** — everything else: category + one-line essence + path, loadable. The corpus
|
|
581
|
+
grows without bound; the head does not.
|
|
582
|
+
|
|
583
|
+
`rulesRelevance: false` falls back to the pre-existing deterministic order (category, then id),
|
|
584
|
+
which is what the tapes were distilled against.
|
|
585
|
+
|
|
586
|
+
**Determinism is a requirement, not a preference.** Selection must be a pure function of (rule
|
|
587
|
+
files, stored topic signatures, caps, query) — no wall-clock timestamps, no map-iteration or
|
|
588
|
+
filesystem-readdir order — or two machines disagree about what the agent was told *and* every
|
|
589
|
+
replay prefix becomes unstable. Ranked-by-recency was rejected partly for this reason: recency
|
|
590
|
+
reads the clock.
|
|
591
|
+
|
|
592
|
+
### 7.5 Session rules: capture and derived activation
|
|
593
|
+
|
|
594
|
+
Two capture paths, deliberately different speeds (this is §6.2's single-slot constraint applied to
|
|
595
|
+
rules, not a new policy):
|
|
596
|
+
|
|
597
|
+
- **Marker — deterministic, instant, zero model tokens (§1 invariant 1).** A line in a
|
|
598
|
+
*human-authored* message beginning `ADD RULE:` (§12 `rulesMarkerPrefix`) becomes an active
|
|
599
|
+
session rule at the existing `turn/end` listener — the same one the topic signatures collect on
|
|
600
|
+
(r35j). It is not a new archive door (§12 of `docs/architecture.md`: five moments, extended in
|
|
601
|
+
place, never copied). The rule body is the human's own words, verbatim.
|
|
602
|
+
- **Enrichment — idle-batched, `rulesEnrichment` default ON.** The §6.1 pass carries a
|
|
603
|
+
`rules: [{ text, category, quote }]` field through that pass's existing ladder: validation
|
|
604
|
+
before commit, one retry, deterministic fallback, provenance chain, and the body-hash guard
|
|
605
|
+
(mining writes its own rule files; it never touches chapter prose). `rules` is **optional in the
|
|
606
|
+
output contract**, so every pre-existing stub, fake and tape still validates, and a malformed
|
|
607
|
+
`rules` value cannot cost a chapter its annotation.
|
|
608
|
+
**What decides whether a mined rule governs is POLICY, not evidence** (Amendment 2026-10-21).
|
|
609
|
+
`rulesAutoActivate` defaults ON: a rule the model inferred from context applies to the session
|
|
610
|
+
tree the moment it is written. With it off, every mined rule is written `reviewOnly: true` and
|
|
611
|
+
stays inert until this machine's `approve` fact says core — activation still rides a curation
|
|
612
|
+
fact, so the write-once file is never rewritten (§1.3).
|
|
613
|
+
**The quote check survives, demoted to a label.** Matching the chapter's human bytes verbatim
|
|
614
|
+
classifies a rule `stated`; not matching classifies it `inferred`; the label is rendered *beside
|
|
615
|
+
the rule text, with the chapter it came from*, and it drives nothing. Assistant prose is not
|
|
616
|
+
admissible even as a label — a "quote" found only in the model's own restatement is `inferred`.
|
|
617
|
+
Why it stopped being a gate is the amendment's subject: requiring a human to have already said it
|
|
618
|
+
makes the tier redundant with the deterministic marker, and the premise of the tier is that the
|
|
619
|
+
human may not know the rule yet.
|
|
620
|
+
Measured before shipping, on three real chapters at the session model (§13 P3c, verify.md R5): 9
|
|
621
|
+
rules captured, 6 stated / 3 inferred, every inference confined to the chapter that had no human
|
|
622
|
+
text at all, and the model's self-declared `basis` less accurate than the check. It also exposed a
|
|
623
|
+
cost fact worth a decision: the hosted route counts reasoning tokens, so mining inside the
|
|
624
|
+
annotation call shares a budget sized for annotation (`ENRICH_MAX_TOKENS`) — `rulesModel` is the
|
|
625
|
+
clean bound, and an unexamined default is not.
|
|
626
|
+
|
|
627
|
+
Three properties worth stating: mining runs **before** the hash-guarded annotation write, so a
|
|
628
|
+
chapter whose enrichment is refused still contributes its standing orders; mining may use a
|
|
629
|
+
**dedicated model** (`rulesModel` / `/chapters-rule model`), which costs one extra bounded call
|
|
630
|
+
per chapter and, when set, replaces rather than supplements the annotation pass's answer; and the
|
|
631
|
+
agent must **name the session rules it applied** in its reply (§7.3) — which is where a wrong
|
|
632
|
+
inference becomes visible to the only party who can revoke it. That is the §1.4 division — model proposes, plugin commits — with the activation gate
|
|
633
|
+
moved to machine-local, where a wrong guess costs one session and not a team's corpus.
|
|
634
|
+
|
|
635
|
+
**Activation is derived, never a curation fact.** A session rule is active iff `scope: session` ∧
|
|
636
|
+
its `session` is on the current session's ancestor path ∧ it is not revoked. No
|
|
637
|
+
`rule-status` entry is written for it: `edits/<harness>/curation.jsonl` travels in the pool, and
|
|
638
|
+
machine-local activation volume does not belong in a file reserved for human status decisions —
|
|
639
|
+
nor should it, since another machine reads only its own harness directory and would see nothing.
|
|
640
|
+
`revoke` on a session rule writes the one exception: a local fact, because the store is write-once
|
|
641
|
+
(§1.3) and a delete would be a rewrite.
|
|
642
|
+
|
|
643
|
+
**Transport exclusion happens at one point.** `planStoreToRepo` is the single store→repo path
|
|
644
|
+
mapper (`rules/<harness>/*.md → rules/<projectKey>/<harness>/*.md`), so it is the single place that
|
|
645
|
+
skips `scope: session`. No provider verb, no sync-loop edit, no second filter — proven over the
|
|
646
|
+
git integration *and* the TreeDX stub, because §5's exit criterion is transport-grade or it is
|
|
647
|
+
nothing. Session bytes therefore never become another machine's `proposed` noise, which is what
|
|
648
|
+
keeps §7.2's "stop re-reviewing a growing list" goal intact for teammates.
|
|
649
|
+
|
|
650
|
+
**Loss is expected and re-derivable.** The store root is gitignored inside a re-clonable directory
|
|
651
|
+
(`docs/architecture.md § Storage`), so deleting a checkout destroys its session rules with
|
|
652
|
+
everything else in it. The marker half re-derives deterministically by replaying the parser over
|
|
653
|
+
the durable log (invariant 1 is what makes that possible); the enrichment half re-derives by
|
|
654
|
+
re-running the pass. The correct description of that event is "the ruleset was rebuilt", never
|
|
655
|
+
"sessions were corrupted".
|
|
656
|
+
|
|
657
|
+
### 7.6 Carriage: where rules reach the model
|
|
658
|
+
|
|
659
|
+
Rules are delivered at the moments the head is rebuilt, plus on demand — **not** per request:
|
|
660
|
+
|
|
661
|
+
| Door | What renders | Mechanism |
|
|
662
|
+
|---|---|---|
|
|
663
|
+
| Continuation notice | project + session | `rulesSection` at the four door call sites (tools) — already shipped for project |
|
|
664
|
+
| Fork / branch notice | project only | the same `intent` axis that already decides plan bytes (§9–13 of `docs/plans-carriage.md`): `branch` ⇒ zero session bytes |
|
|
665
|
+
| In-place checkpoint | project + session | `rulesCarry`, the third lead section in `planSummarize`, beside `planCarry` and `plot` — both the composed and legacy paths |
|
|
666
|
+
| On demand | assembled set | `chapters_rules` tool (§8): verbatim text within budget + the withheld line |
|
|
667
|
+
|
|
668
|
+
**Why not "bundled with every request".** Two measured reasons, not stylistic. (a) Invariant 3:
|
|
669
|
+
the system prompt is process-global and `systemPrompt` is excluded from our injection list on
|
|
670
|
+
purpose — a per-session block cannot ride it, and the append-as-section precedent (`dsh-session-fork`)
|
|
671
|
+
was reviewed and rejected here. (b) Cache: on llama.cpp, the target hardware, a head-position
|
|
672
|
+
replacement reuses **nothing** (E3, r23/r28: `cacheReuse 0`), so a ruleset that churns mid-session
|
|
673
|
+
is a cache-wipe machine. Between rebuilds the human's original bytes are still live in the surface
|
|
674
|
+
anyway — they only genuinely die when the surface is replaced, and that is where they are now
|
|
675
|
+
re-stated. Plans arrived at the same conclusion for the same reason.
|
|
676
|
+
|
|
677
|
+
Empty set ⇒ the checkpoint carries zero rule bytes, which is what protects every already-committed
|
|
678
|
+
plot-carrying checkpoint (`src/engine-core.ts` records exactly that hazard).
|
|
679
|
+
|
|
680
|
+
### 7.7 Precedence and conflicts
|
|
681
|
+
|
|
682
|
+
Two live tiers means two sources of "must", and a small model degrades badly on a contradiction,
|
|
683
|
+
so precedence is stated in the block rather than left to the reader:
|
|
684
|
+
|
|
685
|
+
- **Project rules are the team's order. Session rules refine and add; they never override.**
|
|
686
|
+
- A session rule that conflicts with an active project rule is *rendered and flagged*, not
|
|
687
|
+
resolved silently — the human's options are to promote a fix to the project rule or revoke the
|
|
688
|
+
session rule.
|
|
689
|
+
- **`/chapters-rule conflicts`** spends one bounded `ctx.llm` stream call over the assembled set,
|
|
690
|
+
reusing the conversation's own system prompt as the cache-friendly prefix (`docs/contract.md`),
|
|
691
|
+
and returns a **report** of contradictory pairs with rule ids. Like `elicitedPlot`, this is an
|
|
692
|
+
explicit, config-gated exception to zero-inference. It is never a mutation: rules are write-once
|
|
693
|
+
(§1.3) and the only sanctioned change is a curation fact. Enrichment may flag the same pairs in
|
|
694
|
+
its own pass; neither path edits a rule.
|
|
695
|
+
- Command output never enters the context window (§7.2 verified), so reviewing conflicts costs a
|
|
696
|
+
human no session tokens.
|
|
697
|
+
|
|
698
|
+
### 7.8 Identity must fail loudly
|
|
699
|
+
|
|
700
|
+
The design goal "project rules carry across my checkouts of this repo" holds because identity is
|
|
701
|
+
remote-derived (§2.3). Where it does *not* hold, silence is the bug — ungoverned must never look
|
|
702
|
+
like governed:
|
|
703
|
+
|
|
704
|
+
| Divergence | Consequence | What the plugin must say |
|
|
705
|
+
|---|---|---|
|
|
706
|
+
| No readable remote ⇒ `path-derived` key | that checkout has a **private** rule namespace | `0 project rules — this checkout resolves project <key> from path <cwd>; link it with /chapters-link` |
|
|
707
|
+
| No `origin`, another remote listed first | key derives from the fork ⇒ a forked pool | report the remote string actually used |
|
|
708
|
+
| Linked via TreeDX rather than git | key is the **pool location** (`treedx/<host>/<repo>`), not the workspace remote | report `projectKeySource` so git/treedx keyed checkouts are visibly different projects |
|
|
709
|
+
| Fresh checkout, unlinked or not yet synced | no project ⇒ no rules at all | same explicit zero line, plus `/chapters-status` sync state |
|
|
710
|
+
|
|
711
|
+
`projectKeySource` (`override | remote | path-derived`) rides **`/chapters-status`** as an
|
|
712
|
+
`Identity:` line — naming the source AND where it came from (remote string / override / literal
|
|
713
|
+
path) — and the divergence branch is its own warning: `⚠ THIS CHECKOUT RESOLVES A DIFFERENT
|
|
714
|
+
PROJECT KEY … rules and chapters approved elsewhere will NOT appear here`. `/chapters-link`'s view
|
|
715
|
+
reports the same fact. The continuation head carries the zero-rules statement **only when the
|
|
716
|
+
session tier is governing that head**: bolting an "unlinked" line onto every unlinked workspace's
|
|
717
|
+
notice would spend real bytes on a warning most sessions do not need, while a head that already
|
|
718
|
+
carries local rules is exactly where a silent zero gets misread as "the team approved nothing".
|
|
719
|
+
`/chapters-rule active` prints the tier counts on demand, and `chapters_rules` names a missing
|
|
720
|
+
project in its note. A session-scoped archive depends on none of this — which is why an unlinked
|
|
721
|
+
checkout still gets its own rules.
|
|
722
|
+
|
|
723
|
+
### 7.9 Command surface, and what the agent is told
|
|
724
|
+
|
|
725
|
+
**Principle: the machinery is invisible; the query is encouraged.** Capture, activation, budgeting
|
|
726
|
+
and carriage happen without the agent doing anything — no tool it must remember to call, no
|
|
727
|
+
command it must issue, no bookkeeping it can get wrong. What it *is* told, in every rebuilt head,
|
|
728
|
+
is to **look the rules up before starting a task**: `chapters_rules` with the task description,
|
|
729
|
+
named in the notice's `## Finding things` block (§8). Everything else about rules reaches it as
|
|
730
|
+
prose it simply reads.
|
|
731
|
+
|
|
732
|
+
| Surface | Tier | Notes |
|
|
733
|
+
|---|---|---|
|
|
734
|
+
| `/chapters-rule add <category> <text>` | project | unchanged — the only human door that writes shared rule bytes |
|
|
735
|
+
| `/chapters-rule list [--scope project\|session] [--proposed] [--category <c>]` | both | table: id, scope, status, category, one-line text, provenance, source seq |
|
|
736
|
+
| `/chapters-rule active` | both | **exactly what is or will be rendered**, with the withheld count and why — the command that makes §7.3's honesty checkable |
|
|
737
|
+
| `/chapters-rule approve / revoke <id>` | project (approve) · both (revoke) | per-machine curation facts (§7.2, §15 2026-09-20) |
|
|
738
|
+
| `/chapters-rule pin / unpin <id>` | both | the §7.4 always-shown class; **human-set only** — an auto-pinned rule is an ungoverned always-shown rule |
|
|
739
|
+
| `/chapters-rule conflicts [--model <pin>]` | both | report-only (§7.7) |
|
|
740
|
+
| `chapters_rule_propose` (tool) | project | agent-facing, stays `proposed` and machine-inert until a human approves — unchanged contract, do not widen |
|
|
741
|
+
| `chapters_rules` (tool) | both | the assembly door: verbatim selected set within budget + the withheld line |
|
|
742
|
+
|
|
743
|
+
Encouragement lives in the **notice**, deliberately: notice prose costs nothing against the tape
|
|
744
|
+
corpus (measured 2026-10-11 — no committed tape prefix contains a seeded notice), while a persona
|
|
745
|
+
or tool-description edit forces a re-record of every affected project (§13). A new tool or tool
|
|
746
|
+
parameter does change the header, so this feature's schema edits are sequenced into **one**
|
|
747
|
+
re-record rather than scattered across several.
|
|
462
748
|
|
|
463
|
-
When a continuation is created, the plugin can compute topic overlap between the archived
|
|
464
|
-
span's topics and rule categories and inline matching *category-level* rules within the same
|
|
465
|
-
budget. P3b; gated by the same cap-and-refuse rule.
|
|
466
749
|
|
|
467
750
|
## 8. Search: `chapters_search`
|
|
468
751
|
|
|
@@ -491,6 +774,24 @@ chapters_search { query: string, maxTokens?: number, topic?: string, crossProjec
|
|
|
491
774
|
opens (chapters in `.dsh-chapters/`, shards in `.dsh-knowledge/index/`). The search tool
|
|
492
775
|
adds a scorer and a formatter; it adds no new way to read.
|
|
493
776
|
|
|
777
|
+
**`chapters_rules` — the head's selector, assembled (§7.4, §7.9).** One door whose output is *the
|
|
778
|
+
text of the rules that apply to this task*, verbatim, within budget, plus the per-tier counts it
|
|
779
|
+
withheld. It calls `renderRulesBlock` — the renderer the continuation notice and the compaction
|
|
780
|
+
checkpoint also use — and nothing else; a rule-picker living in a door or a checkpoint is forbidden
|
|
781
|
+
by the same argument that made `exit_plan_mode` and `chapters_continue` share one builder. §7.4
|
|
782
|
+
keeps `rankEntries` (corpus ranking) and `selectRules` (head selection) as one implementation of
|
|
783
|
+
each genuinely different job.
|
|
784
|
+
|
|
785
|
+
- **It reads both corpora, which is where the trap is.** Project rules come from the pool mirror
|
|
786
|
+
under `rules/<projectKey>/<harness>/`; session rules live in the **workspace store** under
|
|
787
|
+
`rules/<harness>/` and are never staged (§7.5), so they carry no `projectKey` path prefix. The
|
|
788
|
+
`projectKey` prefix filter above must therefore not be the only scope for the session tier, or
|
|
789
|
+
session rules become invisible to the very door whose job is to find them — a silent zero, the
|
|
790
|
+
failure class §7.8 exists to eliminate. Scoping for session rules is by the ancestor-path
|
|
791
|
+
activation predicate instead, never by path prefix.
|
|
792
|
+
- **It is the encouraged step, not a required one.** Capture and carriage never depend on it
|
|
793
|
+
(§7.9); the notice names it in `## Finding things` as the thing to run before starting a task.
|
|
794
|
+
|
|
494
795
|
## 9. Redaction: minimal, deterministic, marked
|
|
495
796
|
|
|
496
797
|
User position, adopted: **credentials only, deterministic patterns only, everything else
|
|
@@ -527,7 +828,10 @@ ever** (§3.3), zero conflicts by construction:
|
|
|
527
828
|
|
|
528
829
|
Fact types: `topic-alias` (merge/rename), `topic-pin`, `group` (sibling chapters that
|
|
529
830
|
belong together — the semantic grouping that composition cannot do alone), `weight`,
|
|
530
|
-
`note
|
|
831
|
+
`note`, and the two rule facts (§7.2, §7.4): `rule-status` — per-machine approve/revoke,
|
|
832
|
+
the 2026-09-20 amendment's whole mechanism — and `rule-pin`, the always-rendered class,
|
|
833
|
+
which rides a fact for the same reason status does: a rule file is write-once and the
|
|
834
|
+
append-only rule has no exceptions. Authors are `plugin` or `user` (user entries via commands, P3). This is the
|
|
531
835
|
"improve over time" layer: curation accumulates as facts, and the derived index reflects
|
|
532
836
|
them — performance improves without anyone hand-editing, and every fact is inspectable and
|
|
533
837
|
revertable in git.
|
|
@@ -572,6 +876,16 @@ needs a human gate:
|
|
|
572
876
|
git-visible curation entries (§6.3).
|
|
573
877
|
- **Rules:** high — they are prompts by another name, shared across machines. **Human
|
|
574
878
|
`approve` is mandatory** (§7.2). A proposed rule that is never approved affects nothing.
|
|
879
|
+
- **Session rules** (§15 amendment 2026-10-20) are the deliberate exception, and the exception is
|
|
880
|
+
*narrowed by scope, not by gate*: machine-local, never staged to the pool (§7.5), permanently
|
|
881
|
+
active for one session tree, and visible/reversible with one command (`active`, `revoke`).
|
|
882
|
+
Blast radius is that tree. What keeps it honest is structural, not evidentiary (§7.5, amended
|
|
883
|
+
2026-10-21): machine-local scope and never staged, a per-chapter cap of 3 plus per-tier display
|
|
884
|
+
caps, the warrant rendered beside every rule with its chapter, the agent naming which rules it
|
|
885
|
+
applied, one-command `active`/`revoke` review, `rulesAutoActivate` for anyone who wants
|
|
886
|
+
review-before-trust, and a session rule never overriding a project rule (§7.7). What is *not*
|
|
887
|
+
permitted is a session rule migrating into the shared tier on its own — promotion is a human act,
|
|
888
|
+
exactly as approval is.
|
|
575
889
|
|
|
576
890
|
Git history + provenance makes every tier auditable and every mistake revertible.
|
|
577
891
|
|
|
@@ -591,9 +905,19 @@ No hardcoded tunables (existing hard rule). New config, all with defaults docume
|
|
|
591
905
|
| `toolResultArtifactTokens` | arrival-time artifacting floor (architecture.md amendment 2026-09-19): tool results at/above this many estimated tokens are stored as artifacts and reference-stubbed BEFORE the next request composes — the blob never enters any prompt | 8000 |
|
|
592
906
|
| `elicitedPlot` | when a compaction region holds no model-authored `PLOT:` note, spend ONE bounded stream call (≤8K chars in, ≤220 tokens out) to mint the working plot carried into the checkpoint; the approved, config-killed exception to zero-inference | true |
|
|
593
907
|
| `enrichment.enabled` / `.model` | per-harness enrichment toggle + model pin (built flat: `enrichmentEnabled`, `enrichmentModel`) | enabled, conversation model |
|
|
908
|
+
| `enrichmentMaxTokens` | One auxiliary completion budget (annotation+mining, conflicts). Must cover **reasoning** tokens, not just the visible answer — measured mining usage 1.1K–3.2K per chapter. The route measured here ignores `max_tokens`, so this is headroom for strict providers, not a live limit. No call site keeps a literal | 4000 |
|
|
594
909
|
| `enrichment.trigger` / `.idleMs` / `.batchCap` | P2 queue policy seam (built: `enrichmentTrigger/enrichmentIdleMs/enrichmentBatchCap`); realm owns auto, host plane is manual-only; `/chapters-enrich model` overrides durably | both / 60000 / 5 |
|
|
595
910
|
| `vocabulary.*` | built flat: `vocabApply` (false = **shadow**: candidates reported in the sync status line, nothing written), `vocabCoMin` (3), `vocabOverlapMin` (0.5); applied merges land as §10.1 `topic-alias` facts under `edits/<harness>/`; `topics/vocabulary.json` derived | shadow / 3 / 0.5 |
|
|
596
911
|
| `coreRulesBudgetTokens` | P3 §7.3 cap on the notice's core-rules block; overflow refuses with per-rule numbers | 1200 |
|
|
912
|
+
| `sessionRulesEnabled` | §7.5: the deterministic `ADD RULE:` capture path (zero model tokens) | true |
|
|
913
|
+
| `rulesMarkerPrefix` | §7.5 capture marker; matched at line start of **human-authored** messages only | `ADD RULE:` |
|
|
914
|
+
| `rulesEnrichment` | §7.5 model-derived capture: rides the §6.2 idle batch, never per-turn | true |
|
|
915
|
+
| `rulesAutoActivate` | §7.5 (amended 2026-10-21): may a mined rule govern on arrival? It **may**, by default — inferring what nobody said is the tier's purpose. Off ⇒ mined rules are `reviewOnly` and inert until `approve` | true |
|
|
916
|
+
| `rulesModel` | §7.5: `'provider/model'` for a dedicated mining call — one extra bounded call per chapter, replacing the annotation pass's answer. `''` ⇒ mine inside the enrichment call, at no extra inference cost. Runtime override `/chapters-rule model` (settings key `rules.model`) | `''` |
|
|
917
|
+
| `rulesMinedPerChapter` | §7.5 cap on mined rules per chapter — the cap counts rules **written**, not candidates examined, so a duplicate cannot eat a chapter's budget | 3 |
|
|
918
|
+
| `projectRulesDisplayCap` / `sessionRulesDisplayCap` | §7.4 per-tier count caps on rules **rendered** into a rebuilt head; anything dropped is announced by count, never silently | 12 / 12 |
|
|
919
|
+
| `rulesRelevance` | §7.4 relevance selection on; off ⇒ deterministic category→id order (the order tapes were distilled against) | true |
|
|
920
|
+
| — | §7.7 `/chapters-rule conflicts` has **no key of its own**: one bounded stream call on the conversation route the session already has (so it rides that route's cached prefix), report-only. No route yet ⇒ the verb refuses honestly. A gate of its own was rejected: a human types the verb, so `elicitedPlot`'s reason for a switch does not apply | — |
|
|
597
921
|
| `rulesCoreBonus` | P3 §8 query-time rank bonus for rules effective-core on this machine (never baked into shared shards) | 0.15 |
|
|
598
922
|
| `redaction.patterns` | additive to the built-in list (§9) | built-ins |
|
|
599
923
|
| `search.defaultMaxTokens` | default pack size for `chapters_search` | small, config |
|
|
@@ -632,7 +956,8 @@ e2e project replays the whole ladder through the model tape. CLOSED 2026-09-20:
|
|
|
632
956
|
Rules-as-chapters (`kind: rule`); the in-session command family `/chapters-rule
|
|
633
957
|
list|add|approve|revoke` (§7.2 — command output verified context-free); core-rules section +
|
|
634
958
|
category index in the notice (§7.3); agent-side rule proposals (async, always `proposed`);
|
|
635
|
-
`/chapters-topic` escape hatch;
|
|
959
|
+
`/chapters-topic` escape hatch; topic-matched selection (§7.4 — deferred as P3b in this draft,
|
|
960
|
+
built later under P3c, not by design but by sequencing).
|
|
636
961
|
*Exit criterion: a rule proposed by a machine affects no other machine until a human types
|
|
637
962
|
`approve`; the core set overflows by refusing with numbers, never clipping.*
|
|
638
963
|
|
|
@@ -645,7 +970,38 @@ attempted even at copied===0 so fact-only passes travel — the change that made
|
|
|
645
970
|
shareable) + scorer (category 1.5, rule bodies, per-machine core bonus at query time).
|
|
646
971
|
Exit-criterion tests: unit ('machine B sees nothing core'), transport-grade (three machines
|
|
647
972
|
over a real pool), e2e (`rules` project: UI add → approve → fork → the child's own log
|
|
648
|
-
carries the rule verbatim). §7.4
|
|
973
|
+
carries the rule verbatim). §7.4 selection was left unbuilt in that round by design — the corpus
|
|
974
|
+
of active rules did not yet grow on its own.
|
|
975
|
+
|
|
976
|
+
**P3c — Two-tier rules (issue #50): BUILT, both capture speeds (2026-10-20).** §7.1's `scope`/`session`/
|
|
977
|
+
`derivedFrom`/`pinned` frontmatter (absent `scope` ⇒ `project`, so every pre-existing rule record
|
|
978
|
+
keeps working byte-identically); `ADD RULE:` capture at the existing `turn/end` listener;
|
|
979
|
+
`rulesEnrichment` capture (§7.5's second speed; governed on arrival by default per
|
|
980
|
+
Amendment 2026-10-21, the quote demoted to a rendered warrant); session-scope exclusion in
|
|
981
|
+
`planStoreToRepo`; the three render classes and per-tier caps in `buildRulesSection`; the third
|
|
982
|
+
checkpoint lead section; `intent` filtering at the four door sites; `chapters_rules`; the
|
|
983
|
+
`active`/`pin`/`conflicts` verbs; and §7.8's loud-identity lines.
|
|
984
|
+
*Exit criteria (each must be a red row before it is a fact):* an in-place compaction renders active
|
|
985
|
+
rules verbatim into the checkpoint; `chapters_continue` carries project **+ session** while
|
|
986
|
+
`chapters_fork` carries project only; `ADD RULE:` yields an active rule at **zero** model tokens;
|
|
987
|
+
no session rule file appears in the pool over **either** provider; an unlinked or `path-derived`
|
|
988
|
+
checkout says it rendered zero rules; the empty set still renders byte-identical ABSENT; selection
|
|
989
|
+
is byte-stable across two machines with the same inputs.
|
|
990
|
+
Build status, honestly split (each ✅ is a red-then-green row, not an intention):
|
|
991
|
+
✅ `scope`/`session`/`derivedFrom`/`pinned` frontmatter with absent-means-`project` byte-compat;
|
|
992
|
+
✅ tier-aware `buildRulesSection` with pinned→relevance→category/id selection, per-tier caps and the
|
|
993
|
+
announced withheld count; ✅ `ADD RULE:` capture at the existing `turn/end` listener (zero model
|
|
994
|
+
tokens); ✅ the one `planStoreToRepo` exclusion over **both** providers; ✅ `rulesCarry` as the third
|
|
995
|
+
checkpoint lead section on both paths; ✅ `intent` filtering at the doors through one
|
|
996
|
+
`renderRulesBlock`, with a source-shape row forbidding a door-local picker; ✅ `chapters_rules`;
|
|
997
|
+
✅ `active`/`pin`/`unpin`/`--scope`/`conflicts` and revoke-on-session; ✅ §7.8 identity lines at
|
|
998
|
+
`/chapters-status` and `/chapters-link`. ❌ the §6.1 `rules: []` enrichment field and its quote gate.
|
|
999
|
+
❌ the browser plane: `npm run test:e2e:replay` **refuses by name** on the pinned host (it predates
|
|
1000
|
+
preset declaration rows — `tests/e2e/boot.ts:130`), so the `rules` journey and the R-rows over a
|
|
1001
|
+
live model are unverified, and the new tool schema is a standing re-record owed the moment
|
|
1002
|
+
`dev/dsh-host-lock` moves to 0.2.x. Sequencing then stays as planned: **one** whole-project
|
|
1003
|
+
re-record, never a series of per-spec ones (the 2026-09-23 lesson: a tape records the context it was
|
|
1004
|
+
distilled in).
|
|
649
1005
|
|
|
650
1006
|
## 14. Open risks (honest register)
|
|
651
1007
|
|
|
@@ -659,6 +1015,10 @@ carries the rule verbatim). §7.4 auto-inclusion remains P3b, unbuilt by design.
|
|
|
659
1015
|
| `mergeThreshold` mis-tune at archive time (composition is archive-time-only, §4.4) | signatures are stored with provenance; tune for future archives, inspect past ones |
|
|
660
1016
|
| TreeDX service down or token expired (Amendment 2026-09-21, transport) | same §5.3 degradation (offline mirror, deferred push, auth re-link surfaced with numbers); no corpus loss |
|
|
661
1017
|
| TreeDX corpus pull is per-file (`paths/list` + `files/read`): N+1 requests at large pools | acceptable at per-project corpus sizes (the record's own scaling premise); snapshot/`context` APIs are the Phase-F optimization |
|
|
1018
|
+
| P3c: a model-captured rule encodes a **mis-generalization** permanently for the session tree ("never run two GPU suites at once" → "never run tests concurrently") | narrowed by the verbatim-quote activation gate (§7.5), by scope (local, never the shared tier), and by one-command visibility (`active`, `revoke`); the residual risk is a wrong rule you did not look at — which is why the count is capped and the withheld line is mandatory |
|
|
1019
|
+
| P3c: relevance selection **ranks out the load-bearing rule**, reintroducing "lost in the noise" inside the fix for it | the pinned class is never subject to selection (§7.4); the withheld count renders even when nothing is selected |
|
|
1020
|
+
| P3c: a new tool/param changes every replay prefix | one whole-project re-record, sequenced deliberately; notice prose stays free (measured 2026-10-11) |
|
|
1021
|
+
| P3c: per-checkout identity divergence silently forks a project's rule namespace (§7.8) | stated, not assumed: `projectKeySource` reported in the zero-rules line, `active`, and `/chapters-status` |
|
|
662
1022
|
|
|
663
1023
|
## 15. Amendments
|
|
664
1024
|
|
|
@@ -792,6 +1152,162 @@ kernel's own exported predicates (`KNOWN_SESSION_EVENT_TYPES`, `isSurfaceEligibl
|
|
|
792
1152
|
`isAppendSurfaceEvent`) rather than reimplemented, so a kernel change surfaces as a red row instead of a
|
|
793
1153
|
child that silently stops being gated.
|
|
794
1154
|
|
|
1155
|
+
### Amendment 2026-10-20 — rules become two tiers: project (shared, approved) and session (local, derived)
|
|
1156
|
+
|
|
1157
|
+
Human directive, in the round that planned it: *"I end up giving the AI agent a bunch of rules
|
|
1158
|
+
embedded in my requests… these rules just get lost in the noise."* Auditing where they actually get
|
|
1159
|
+
lost found one concrete hole, not a vague one: the in-place checkpoint composes
|
|
1160
|
+
`leadSections = [planSection, plotSection]` (`src/engine-core.ts`). **A plan survives a compaction
|
|
1161
|
+
and a plot survives a compaction; a rule does not.** That, plus a rule set that only ever reaches a
|
|
1162
|
+
child through the continuation notice with no relevance selection, is the whole motivation.
|
|
1163
|
+
|
|
1164
|
+
Six decisions, each narrowing a temptation the discussion produced:
|
|
1165
|
+
|
|
1166
|
+
1. **Two tiers, one store** (§7.1). Session rules are `kind: rule` files in the same tree with
|
|
1167
|
+
`scope: session` — same numbering, same write-once immutability, same `list`, same index. Absent
|
|
1168
|
+
`scope` means `project`, so every rule record on disk before this amendment keeps working with
|
|
1169
|
+
unchanged bytes. There is no second rules subsystem to drift.
|
|
1170
|
+
2. **The global tier was not built, because it already existed.** The request was "carry across my
|
|
1171
|
+
checkouts of this repository", and `projectKey` is UUIDv5 over the *canonicalized remote* (§2.3)
|
|
1172
|
+
with `harnessId` defaulting to the hostname — so several checkouts of one repo already resolve to
|
|
1173
|
+
one rule namespace on one machine. The work became making the four ways that identity silently
|
|
1174
|
+
diverges say so (§7.8) instead of inventing a third namespace with a much larger blast radius.
|
|
1175
|
+
3. **Session activation is derived, never a curation fact** (§7.5). Active iff `scope: session` ∧
|
|
1176
|
+
its session is on the current ancestor path ∧ not revoked. `edits/<harness>/curation.jsonl` is
|
|
1177
|
+
reserved for human status decisions *and it travels in the pool* — machine-local activation volume
|
|
1178
|
+
does not belong there. The 2026-09-20 amendment (approval is a per-machine fact, never a file
|
|
1179
|
+
rewrite) is **not** relaxed: it still governs every byte of the shared tier.
|
|
1180
|
+
4. **Model-captured rules may auto-activate, and only on a verbatim quote** (§7.5, behind
|
|
1181
|
+
`rulesEnrichment`, default off — *decided and specified; the P3c build shipped the marker path
|
|
1182
|
+
first, so this half is still ahead of the code*). The rejected framing was "the model writes its own governing
|
|
1183
|
+
rules with no human gate"; the accepted one is that the machine-local, one-command-reversible,
|
|
1184
|
+
never-staged scope makes the blast radius one session tree — so the gate moves from *who clicks*
|
|
1185
|
+
to *what the rule is derived from*. An inferred rule with no human sentence behind it waits in
|
|
1186
|
+
`list --proposed`. And capture stays off the critical path entirely: the `ADD RULE:` marker is
|
|
1187
|
+
deterministic and costs zero model tokens (invariant 1), while the model pass rides §6.2's idle
|
|
1188
|
+
batch, because the local server is single-slot and per-turn work queues against the user's own
|
|
1189
|
+
turns.
|
|
1190
|
+
5. **Rules are carried at the moments the head is rebuilt, never injected per request** (§7.6).
|
|
1191
|
+
Two measured reasons, not taste: invariant 3 (the system prompt is process-global and
|
|
1192
|
+
`systemPrompt` is excluded from our injections on purpose), and the llama.cpp number from
|
|
1193
|
+
E3/r23-r28 — a head-position replacement reuses **nothing** (`cacheReuse 0`), so a ruleset that
|
|
1194
|
+
churns mid-session is a cache-wipe machine on exactly the target hardware. Between rebuilds the
|
|
1195
|
+
human's own bytes are still in the surface; they die only when the surface is replaced, which is
|
|
1196
|
+
where they are now re-stated. `chapters_continue` carries both tiers, `chapters_fork` carries
|
|
1197
|
+
project only — the *same* `intent` axis that already decides plan bytes (`docs/plans-carriage.md`
|
|
1198
|
+
§§ 9–13), not a new distinction.
|
|
1199
|
+
6. **§7.4 selection is raised from "P3b later" to required, with a floor.** Automatic capture means
|
|
1200
|
+
the active set now grows without a human noticing, so always-render-everything stops being
|
|
1201
|
+
affordable: pinned (human-set, always) → relevance-selected (the existing `search.rankEntries`,
|
|
1202
|
+
never a second ranker) → index-only. Per-tier count caps are **announced**, because selection
|
|
1203
|
+
that can silently drop the load-bearing rule would rebuild the original bug inside the fix —
|
|
1204
|
+
and a cap that *truncates* would violate §1.2, so the token ceiling still refuses with numbers
|
|
1205
|
+
and the count ceiling selects with a visible count. Selection must be a pure function of stored
|
|
1206
|
+
inputs (no clock, no readdir order) or two machines disagree about what the agent was told and
|
|
1207
|
+
the tape corpus cannot be stable.
|
|
1208
|
+
|
|
1209
|
+
Also recorded as accepted consequences: session-scope exclusion happens at the one store→repo mapper
|
|
1210
|
+
(`planStoreToRepo`) and is proven over **both** providers; a session rule never overrides a project
|
|
1211
|
+
rule, and conflicts are reported (`/chapters-rule conflicts`, one bounded stream call in the
|
|
1212
|
+
`elicitedPlot` family) rather than resolved (§7.7); losing a checkout loses its session rules, which
|
|
1213
|
+
is *re-derivation*, not corruption (§7.5, and the storage warning in `AGENTS.md`); and the agent-
|
|
1214
|
+
facing surface stays minimal by decision — capture and carriage are invisible to the model, and the
|
|
1215
|
+
one thing it is told to do is **look the rules up before starting a task** (§7.9), because a tool an
|
|
1216
|
+
instruction never names does not exist to a small model.
|
|
1217
|
+
|
|
1218
|
+
Specification: §7.1 and §7.3–§7.9. Build status and exit criteria: §13 P3c (specified, not yet
|
|
1219
|
+
built at the time of this amendment — this entry is the decision record, not a claim of behavior).
|
|
1220
|
+
|
|
1221
|
+
### Amendment 2026-10-21 (b) — rules reach agents, and agents reach rules (#52)
|
|
1222
|
+
|
|
1223
|
+
Two user decisions in the same review round, both about the gap between "the rule exists" and
|
|
1224
|
+
"the agent in front of the work is told about it".
|
|
1225
|
+
|
|
1226
|
+
**1. The preset persona is the instruction channel.** Nothing told a brand-new session, or a delegated
|
|
1227
|
+
subagent, to use `chapters_rules` / `chapters_search`: a plugin must not contribute a system-prompt
|
|
1228
|
+
section (§1.3 / invariant 3), and the continuation notice only exists for continuation children. The
|
|
1229
|
+
sanctioned per-composition channel is the preset's OWN persona row — `@deepseek-ai/dsh-persona`,
|
|
1230
|
+
prefix/suffix — the mechanism already carrying the `PLOT:` discipline. The shipped suffix now adds a
|
|
1231
|
+
short "Chapters memory and rules" section: run `chapters_rules` with the task description before a
|
|
1232
|
+
task and NAME the rules you followed (or say none); for earlier work, decisions, or bug history run
|
|
1233
|
+
`chapters_search` FIRST instead of re-reading files or asking the human; `chapters_artifact` holds the
|
|
1234
|
+
oversized bytes; and a delegated child is covered by the same rules and the same archive. It is
|
|
1235
|
+
per-preset, so a session on `standard` sees none of it. Two couplings are pinned by behaviour, not
|
|
1236
|
+
comment: the suffix must remain matched by the plot template-echo guard (else every session inherits
|
|
1237
|
+
its own instruction as a "plot"), and no new prompt variable may appear (`{{model}}` only — an
|
|
1238
|
+
unregistered variable throws at assembly and kills every turn of every chapters session).
|
|
1239
|
+
|
|
1240
|
+
**2. Rule ancestry crosses the delegation edge.** The registry is the sole authority on continuation
|
|
1241
|
+
lineage and deliberately ignores the kernel header's `parentSession` — correct for numbering and the
|
|
1242
|
+
TOC, and it left a whole session shape with ZERO rules: a delegated subagent was an unrelated root by
|
|
1243
|
+
that view. So `session-rules.ruleAncestorSet` walks BOTH edges (registry `parentSession` ∪ header
|
|
1244
|
+
`parentSession`) transitively, READ-ONLY, writing nothing, and still only UPWARD — a child's
|
|
1245
|
+
inferences never govern the session that delegated them. Bounded (64 expansions) and loud when the
|
|
1246
|
+
cap bites (`lineage walk hit its cap…` inside the block, `LINEAGE TRUNCATED:` in `/chapters-rule
|
|
1247
|
+
active`); an unreadable header contributes no edge rather than breaking a turn. One walk, one builder:
|
|
1248
|
+
every door, the checkpoint and the surface call `ruleAncestorSet`, and an anti-drift row fails if a
|
|
1249
|
+
plane computes ancestry by hand again — hand-built ancestry is precisely how the gap appeared.
|
|
1250
|
+
|
|
1251
|
+
Consequence, recorded before anyone is surprised by it: this changes a PRODUCT PROMPT (the persona
|
|
1252
|
+
suffix) and adds a tool, so the browser-plane tapes owe one whole-project re-distill when the host
|
|
1253
|
+
lock moves to 0.2.x — one re-record pays both debts. Until that runs, no claim about browser-plane
|
|
1254
|
+
rule carriage is verified.
|
|
1255
|
+
|
|
1256
|
+
### Amendment 2026-10-21 — a mined rule is governed by POLICY, not by proof that a human said it
|
|
1257
|
+
|
|
1258
|
+
Human directive, one day after the previous entry and against the feature as built: *"I don't like
|
|
1259
|
+
your approach of making mined rules governing if it appears verbatim in my own words… we need to
|
|
1260
|
+
assume that the human may not know, which is why they may be using AI in the first place."* The
|
|
1261
|
+
verbatim-quote gate built under §7.5 is **removed as a condition on activation**.
|
|
1262
|
+
|
|
1263
|
+
What was wrong was a category error, and it is worth stating plainly because it is the kind of
|
|
1264
|
+
error this project keeps making: the gate confused **where a rule came from** with **whether it is
|
|
1265
|
+
allowed to govern**. Requiring a human sentence behind every mined rule meant the model could only
|
|
1266
|
+
ever re-state what had already been said — which is exactly what the deterministic `ADD RULE:`
|
|
1267
|
+
marker does better, because there you know precisely what you got. A tier that can only confirm the
|
|
1268
|
+
known is not inference; it is a paraphrase of the marker with extra latency. And the honest failure
|
|
1269
|
+
mode it was built to prevent — the model inventing a rule the human never sanctioned — is the same
|
|
1270
|
+
failure mode it was built to *cause*: the useful case is the standing order nobody has articulated
|
|
1271
|
+
yet, and the value of asking a model to look is precisely that it may see one.
|
|
1272
|
+
|
|
1273
|
+
**What replaces it.** Four things, all structural — and the reason all four are needed is that
|
|
1274
|
+
evidence is no longer one of them:
|
|
1275
|
+
|
|
1276
|
+
1. `rulesAutoActivate` (default **on**) is the single switch. Off ⇒ every mined rule is written
|
|
1277
|
+
`reviewOnly: true` and stays inert until this machine approves it. Review-before-trust is a
|
|
1278
|
+
posture a team can choose; it is no longer the default posture.
|
|
1279
|
+
2. The quote check survives, **demoted to a label**: `stated` (a human sentence backs it) vs
|
|
1280
|
+
`inferred` (the model judged it, nobody said it). It renders beside the rule text *with the
|
|
1281
|
+
chapter it came from*, in the head, so no inferred rule can govern anonymously. Assistant prose
|
|
1282
|
+
stays inadmissible even as a label — a "quote" found only in the model's restatement is
|
|
1283
|
+
`inferred`, not `stated`.
|
|
1284
|
+
3. The agent must **name by id the session rules that shaped its reply** (§7.3's speak-up line),
|
|
1285
|
+
or say none did. This is the mechanism the human asked for instead of a gate, and it is better:
|
|
1286
|
+
a wrong inference becomes visible at the moment it acts, to the only party who can revoke it,
|
|
1287
|
+
rather than being prevented in advance by a test the model can satisfy by quoting badly.
|
|
1288
|
+
4. The caps stay: 3 mined rules per chapter (counting rules **written**), per-tier display caps
|
|
1289
|
+
with the withheld count announced, machine-local scope, never staged to the pool, `revoke` one
|
|
1290
|
+
command away.
|
|
1291
|
+
|
|
1292
|
+
Two things deliberately **not** relaxed: a session rule never overrides a project rule (§7.7), and
|
|
1293
|
+
a session rule never becomes a project rule without a human (`add`) — promotion is still a human
|
|
1294
|
+
act. The tier's warrant for governing itself got broader; its warrant for governing *anyone else*
|
|
1295
|
+
did not change at all.
|
|
1296
|
+
|
|
1297
|
+
**Honest limits, recorded at the time of writing.** No live model has mined a real chapter yet (the
|
|
1298
|
+
dev box's GPU driver was unreachable), so how often a 4B produces a *correct* inference — versus a
|
|
1299
|
+
confident wrong one that now governs by default — is unmeasured. The default is a bet on the
|
|
1300
|
+
visibility mechanisms above, and `docs/verify.md` R5 marks the quality half unverified. Anyone
|
|
1301
|
+
revisiting this should look at the ratio of `stated` to `inferred` on real archives first; if
|
|
1302
|
+
inferences are wrong more often than a human catches them in replies, the fix is
|
|
1303
|
+
`rulesAutoActivate: false`, not a new gate.
|
|
1304
|
+
|
|
1305
|
+
Implementation: `src/rules.ts` (`basis`/`chapter`/`reviewOnly`, `sessionRuleActive`),
|
|
1306
|
+
`src/session-rules.ts` (`captureMinedRules` policy + classification), `src/enrich-wire.ts`
|
|
1307
|
+
(`rulesAutoActivate`, `rulesModel`, dedicated mining call), `src/rules-commands.ts` (`model` verb,
|
|
1308
|
+
`active` posture). Reversal pinned by a test row, not just described: *"THE REVERSAL, PINNED: an
|
|
1309
|
+
inferred rule governs on arrival — no human said it."*
|
|
1310
|
+
|
|
795
1311
|
---
|
|
796
1312
|
|
|
797
1313
|
*Cross-references: invariants and the four invariants — `AGENTS.md`; budget math —
|