@fractalsynapse/dsh-chapters 0.0.0-stage → 0.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +14 -0
- package/README.md +76 -2
- package/cordis.patch.yml +363 -0
- package/docs/architecture.md +692 -0
- package/docs/autorun-guide.md +248 -0
- package/docs/backlog.md +2022 -0
- package/docs/contract.md +938 -0
- package/docs/development.md +990 -0
- package/docs/host-compaction-seam.md +225 -0
- package/docs/host-protected-head-ask.md +80 -0
- package/docs/knowledge-repo.md +1332 -0
- package/docs/plans-carriage.md +571 -0
- package/docs/project-state.md +642 -0
- package/docs/provider.md +98 -0
- package/docs/superpowers/plans/2026-10-17-chapters-save.md +198 -0
- package/docs/superpowers/plans/2026-11-02-chapter-removal.md +151 -0
- package/docs/superpowers/specs/2026-11-02-chapter-removal-design.md +115 -0
- package/docs/verify.md +561 -0
- package/lib/archive.d.ts +161 -0
- package/lib/archive.js +229 -0
- package/lib/archive.js.map +1 -0
- package/lib/arrival.d.ts +23 -0
- package/lib/arrival.js +125 -0
- package/lib/arrival.js.map +1 -0
- package/lib/artifact-query.d.ts +62 -0
- package/lib/artifact-query.js +187 -0
- package/lib/artifact-query.js.map +1 -0
- package/lib/attach.d.ts +46 -0
- package/lib/attach.js +120 -0
- package/lib/attach.js.map +1 -0
- package/lib/auto-save.d.ts +145 -0
- package/lib/auto-save.js +334 -0
- package/lib/auto-save.js.map +1 -0
- package/lib/aux-message.d.ts +17 -0
- package/lib/aux-message.js +20 -0
- package/lib/aux-message.js.map +1 -0
- package/lib/client.js +1419 -0
- package/lib/client.js.map +1 -0
- package/lib/codegraph.d.ts +76 -0
- package/lib/codegraph.js +190 -0
- package/lib/codegraph.js.map +1 -0
- package/lib/commands.d.ts +52 -0
- package/lib/commands.js +946 -0
- package/lib/commands.js.map +1 -0
- package/lib/compose.d.ts +30 -0
- package/lib/compose.js +185 -0
- package/lib/compose.js.map +1 -0
- package/lib/continue-core.d.ts +245 -0
- package/lib/continue-core.js +405 -0
- package/lib/continue-core.js.map +1 -0
- package/lib/diag.d.ts +55 -0
- package/lib/diag.js +186 -0
- package/lib/diag.js.map +1 -0
- package/lib/engine-core.d.ts +301 -0
- package/lib/engine-core.js +531 -0
- package/lib/engine-core.js.map +1 -0
- package/lib/engine-note.d.ts +35 -0
- package/lib/engine-note.js +32 -0
- package/lib/engine-note.js.map +1 -0
- package/lib/engine.d.ts +329 -0
- package/lib/engine.js +1179 -0
- package/lib/engine.js.map +1 -0
- package/lib/enrich-queue.d.ts +66 -0
- package/lib/enrich-queue.js +126 -0
- package/lib/enrich-queue.js.map +1 -0
- package/lib/enrich-store.d.ts +31 -0
- package/lib/enrich-store.js +103 -0
- package/lib/enrich-store.js.map +1 -0
- package/lib/enrich-wire.d.ts +163 -0
- package/lib/enrich-wire.js +353 -0
- package/lib/enrich-wire.js.map +1 -0
- package/lib/enrich.d.ts +102 -0
- package/lib/enrich.js +267 -0
- package/lib/enrich.js.map +1 -0
- package/lib/gitops.d.ts +107 -0
- package/lib/gitops.js +539 -0
- package/lib/gitops.js.map +1 -0
- package/lib/index.d.ts +241 -0
- package/lib/index.js +1162 -0
- package/lib/index.js.map +1 -0
- package/lib/indexing.d.ts +98 -0
- package/lib/indexing.js +223 -0
- package/lib/indexing.js.map +1 -0
- package/lib/injections.d.ts +90 -0
- package/lib/injections.js +165 -0
- package/lib/injections.js.map +1 -0
- package/lib/last-resort.d.ts +24 -0
- package/lib/last-resort.js +27 -0
- package/lib/last-resort.js.map +1 -0
- package/lib/log-shape.d.ts +38 -0
- package/lib/log-shape.js +93 -0
- package/lib/log-shape.js.map +1 -0
- package/lib/models-command.d.ts +74 -0
- package/lib/models-command.js +100 -0
- package/lib/models-command.js.map +1 -0
- package/lib/notice.d.ts +74 -0
- package/lib/notice.js +92 -0
- package/lib/notice.js.map +1 -0
- package/lib/plans.d.ts +154 -0
- package/lib/plans.js +315 -0
- package/lib/plans.js.map +1 -0
- package/lib/preset.d.ts +186 -0
- package/lib/preset.js +212 -0
- package/lib/preset.js.map +1 -0
- package/lib/prompt-keys.d.ts +7 -0
- package/lib/prompt-keys.js +8 -0
- package/lib/prompt-keys.js.map +1 -0
- package/lib/prompt-loop.d.ts +73 -0
- package/lib/prompt-loop.js +147 -0
- package/lib/prompt-loop.js.map +1 -0
- package/lib/prompt-push.d.ts +47 -0
- package/lib/prompt-push.js +115 -0
- package/lib/prompt-push.js.map +1 -0
- package/lib/prompt-runner.d.ts +48 -0
- package/lib/prompt-runner.js +90 -0
- package/lib/prompt-runner.js.map +1 -0
- package/lib/prompt-state.d.ts +142 -0
- package/lib/prompt-state.js +101 -0
- package/lib/prompt-state.js.map +1 -0
- package/lib/prompt.d.ts +270 -0
- package/lib/prompt.js +408 -0
- package/lib/prompt.js.map +1 -0
- package/lib/provider.d.ts +44 -0
- package/lib/provider.js +66 -0
- package/lib/provider.js.map +1 -0
- package/lib/redact.d.ts +24 -0
- package/lib/redact.js +85 -0
- package/lib/redact.js.map +1 -0
- package/lib/registry.d.ts +187 -0
- package/lib/registry.js +235 -0
- package/lib/registry.js.map +1 -0
- package/lib/removal.d.ts +159 -0
- package/lib/removal.js +556 -0
- package/lib/removal.js.map +1 -0
- package/lib/render.d.ts +97 -0
- package/lib/render.js +530 -0
- package/lib/render.js.map +1 -0
- package/lib/repair-run.d.ts +76 -0
- package/lib/repair-run.js +506 -0
- package/lib/repair-run.js.map +1 -0
- package/lib/repair.d.ts +208 -0
- package/lib/repair.js +357 -0
- package/lib/repair.js.map +1 -0
- package/lib/repo.d.ts +78 -0
- package/lib/repo.js +184 -0
- package/lib/repo.js.map +1 -0
- package/lib/rules-commands.d.ts +46 -0
- package/lib/rules-commands.js +379 -0
- package/lib/rules-commands.js.map +1 -0
- package/lib/rules.d.ts +267 -0
- package/lib/rules.js +478 -0
- package/lib/rules.js.map +1 -0
- package/lib/save-core.d.ts +72 -0
- package/lib/save-core.js +220 -0
- package/lib/save-core.js.map +1 -0
- package/lib/search.d.ts +112 -0
- package/lib/search.js +282 -0
- package/lib/search.js.map +1 -0
- package/lib/session-rules.d.ts +222 -0
- package/lib/session-rules.js +454 -0
- package/lib/session-rules.js.map +1 -0
- package/lib/signature.d.ts +45 -0
- package/lib/signature.js +199 -0
- package/lib/signature.js.map +1 -0
- package/lib/store.d.ts +156 -0
- package/lib/store.js +368 -0
- package/lib/store.js.map +1 -0
- package/lib/sync.d.ts +220 -0
- package/lib/sync.js +810 -0
- package/lib/sync.js.map +1 -0
- package/lib/tools.d.ts +113 -0
- package/lib/tools.js +1090 -0
- package/lib/tools.js.map +1 -0
- package/lib/treedx/client.d.ts +57 -0
- package/lib/treedx/client.js +82 -0
- package/lib/treedx/client.js.map +1 -0
- package/lib/treedx/provider.d.ts +39 -0
- package/lib/treedx/provider.js +479 -0
- package/lib/treedx/provider.js.map +1 -0
- package/lib/treedx/state.d.ts +27 -0
- package/lib/treedx/state.js +65 -0
- package/lib/treedx/state.js.map +1 -0
- package/lib/types.d.ts +210 -0
- package/lib/types.js +31 -0
- package/lib/types.js.map +1 -0
- package/lib/version.d.ts +9 -0
- package/lib/version.js +10 -0
- package/lib/version.js.map +1 -0
- package/lib/vocabulary.d.ts +41 -0
- package/lib/vocabulary.js +118 -0
- package/lib/vocabulary.js.map +1 -0
- package/lib/warnings.d.ts +10 -0
- package/lib/warnings.js +18 -0
- package/lib/warnings.js.map +1 -0
- package/package.json +151 -3
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
# Plans in the Archive — design record
|
|
2
|
+
|
|
3
|
+
Written 2026-10-09. **Read before writing code.** Human decisions for this feature are recorded at
|
|
4
|
+
§ Decisions Taken and are binding; anything marked *deferred* is a human call, not ours.
|
|
5
|
+
|
|
6
|
+
DSh ships plan mode (`@deepseek-ai/dsh-plan-mode`) and our own preset mounts it
|
|
7
|
+
(the `plan-mode` row of the registered preset: `PLAN_MODE_SECTION` in `src/preset.ts`; before the 0.2.0 floor it lived in `presets/chapters/agent.cordis.yml`). Today a session plan survives an archive only as the
|
|
8
|
+
first 400 characters of a tool-call line. That is the worst possible outcome for the one artifact in a
|
|
9
|
+
conversation that is *deliberately* decision-complete — and the reason the plugin exists at all: a
|
|
10
|
+
continuation that cannot see the approved plan either re-plans from recall (lossy) or implements
|
|
11
|
+
something nobody approved (worse).
|
|
12
|
+
|
|
13
|
+
## Facts to re-verify on any DSH upgrade
|
|
14
|
+
|
|
15
|
+
| Fact | Where | Consequence for us |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| The plan is a **tool-call argument**, field `plan`, type string, required | `dsh-plan-mode/lib/index.js:230-236` | Detection is one predicate: `tool/call && name === 'exit_plan_mode'`. There is no plan document event. |
|
|
18
|
+
| The host validates `/^#\s+\S/` on the plan text | `index.js:256` | We can rely on a leading `#` H1 for the title. |
|
|
19
|
+
| Approval result is the canonical `{ approved: true }` + fixed render text | `index.js:237-251` | Verdict is derivable from the paired result, not from prose. |
|
|
20
|
+
| Rejection = failed call carrying review feedback; dismissal = failed call naming the takeover | `index.js` execute arm + package README § Model Experience | Three distinct non-approval shapes to classify, plus a fourth we must not invent. |
|
|
21
|
+
| State is one log-only whole-value event `plan/mode { active }`, appended at the next **accepted in-turn pre-step** | `index.js:374, 389` | An approved exit may not have its mode event yet at archive time → the *result* is the primary signal, the mode event secondary. |
|
|
22
|
+
| Mode state is recovered by **folding the log** (projection `apply`, `stateVersion: 3`) | `index.js:83-94` | Last logged value wins; we mirror that rule, we do not cache a verdict. |
|
|
23
|
+
| The mode-switch notice rides with `form: 'notice'` — v3 `source { kind: 'plugin', plugin: 'plan-mode' }`, v4 `{ kind: 'plan-mode' }` (measured dsh-plan-mode/lib/index.js:395-410 on 0.2.0-rc.2; issue #43) | `index.js:392-410` | The screen treats injected context as NOT conversation **except** this one sentence, which is archived verbatim (the named exception, `src/injections.ts:55-68`; both arms narrow by producer AND form). Amended 2026-10-10 — this row originally said "omits". |
|
|
24
|
+
| `/plan <message>` steers a real user message, `source.kind 'user'`; bare `/plan`, `/plan off` and their terminal results stay outside model history | `index.js:220` + README § Human Command | The instruction a human typed under plan guidance **is** conversation and is archived verbatim today. Nothing to change. |
|
|
25
|
+
| Freshly created agents begin plan mode **inactive**; `plan/mode` is per-session and not inheritable | README § Known Limitations | A `chapters_continue` / `chapters_fork` child cannot know it was mid-plan unless we carry that text. |
|
|
26
|
+
| `tool/call` data shape is `data.{callId, name, arguments}`, `arguments` an object **or** a JSON string | `src/types.ts:8`, precedent `src/render.ts:253` | The extractor must accept both, exactly as the renderer already does. |
|
|
27
|
+
|
|
28
|
+
## What the pipeline loses today
|
|
29
|
+
|
|
30
|
+
| Loss | Line | Fix below |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| Full plan bytes never reach the store (400-char head only) | `src/render.ts:250-257` | § 2 + § 3 |
|
|
33
|
+
| Artifacts are written for `tool/result` payloads only | `src/render.ts:258-277`, `:126-156` | § 3 |
|
|
34
|
+
| `plan/mode` events fall to `default:` → `unrenderedSeqs` (a disclosed hole, no prose) | `src/render.ts:284-285` | § 2 |
|
|
35
|
+
| Nothing in the TOC names a plan or its verdict | `src/continue-core.ts:111-141` | § 4 |
|
|
36
|
+
| `chapters_search` resolves chapters + rules; artifacts travel (`src/sync.ts:160-215`) but are not index entries | `src/search.ts:76, 104-105` | § 8 |
|
|
37
|
+
| A plan in an **open** turn is above the archive ceiling (completed turns only, `src/tools.ts:186-189`) and is never archived | `src/tools.ts:194-198` | § 5 `livePlans` |
|
|
38
|
+
|
|
39
|
+
## Decisions taken (human, 2026-10-09)
|
|
40
|
+
|
|
41
|
+
1. **Home for plan bytes: artifact pointer + chapter block.** The verbatim plan goes to the
|
|
42
|
+
content-addressed artifact store; the chapter carries status, outline, a bounded head, and the path.
|
|
43
|
+
*Not* a first-class `kind: plan` corpus document — that stays deferred (§ Deferred), because it
|
|
44
|
+
widens the corpus and needs a `docs/knowledge-repo.md` amendment.
|
|
45
|
+
2. **Mode carriage: notice-only.** The child is *told* about the active plan. We do not seed a synthetic
|
|
46
|
+
`plan/mode` event into the seed: the seed contract is pinned as one event at `seq 0`
|
|
47
|
+
(AGENTS.md invariant 2, `spikes/probe/FINDINGS.md`), and flipping the child's mode also changes the
|
|
48
|
+
composed `plan:policy` header section. Both belong to a measured spike, not to this feature.
|
|
49
|
+
3. **Record before code.** This file is the reviewable artifact; implementation starts after it lands.
|
|
50
|
+
|
|
51
|
+
## Design
|
|
52
|
+
|
|
53
|
+
### 1. `src/plans.ts` — pure detector (no cordis, no fs, L0-testable)
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
export type PlanStatus = 'approved' | 'rejected' | 'dismissed' | 'pending' | 'superseded' | 'failed'
|
|
57
|
+
export interface PlanDoc {
|
|
58
|
+
seq: number; resultSeq: number | null; callId: string
|
|
59
|
+
title: string; status: PlanStatus; text: string
|
|
60
|
+
sha256: string; bytes: number; estTokens: number
|
|
61
|
+
outline: string[] // H2/H3 headings, in order, verbatim
|
|
62
|
+
artifactPath: string // artifacts/<aa>/<sha>.txt
|
|
63
|
+
archived: boolean // false when seq > archiveCeiling
|
|
64
|
+
}
|
|
65
|
+
export function detectPlans(events, ceiling, opts): { plans: PlanDoc[]; modeActiveAt: (seq: number) => boolean }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
*Status-vocabulary amendment (2026-10-09, at implementation).* A fifth class appeared in the host's own
|
|
69
|
+
`execute` arm and is not a human verdict at all: the review could never open (no user-questions channel;
|
|
70
|
+
the plan-mode service reloaded mid-review; the call ran outside plan mode; the markdown had no `#`
|
|
71
|
+
heading). Labelling those `rejected` would credit the user with a decision they never made, so the union
|
|
72
|
+
carries `failed` and prints the host's own text as the verdict line. The four shapes the record named are
|
|
73
|
+
all still there.
|
|
74
|
+
|
|
75
|
+
Rules, each pinned by a unit row:
|
|
76
|
+
|
|
77
|
+
- **Pair by callId** using the existing `sameCall` logic (`src/render.ts:158-164`) — extract it, do not
|
|
78
|
+
duplicate it.
|
|
79
|
+
- **Verdict precedence**: paired `tool/result` first (`{approved:true}` → approved; `isError` +
|
|
80
|
+
"Keep planning" feedback → rejected; failed call naming takeover → dismissed); **no result event →
|
|
81
|
+
`pending`**, which must never be rendered as approval. The `plan/mode` fold is secondary evidence and
|
|
82
|
+
only breaks a tie.
|
|
83
|
+
- **`superseded`**: same H1 title, different sha256, later seq ⇒ newest is operative, earlier ones are
|
|
84
|
+
`superseded`. Deterministic; no model judgement.
|
|
85
|
+
- **Screen the body**: run `stripReminderSpans` over the plan text. A plan is model-authored bytes, but
|
|
86
|
+
an injected span riding inside it is host state (same reasoning as `src/injections.ts:62-70`).
|
|
87
|
+
- **Redaction rides for free**: build the doc through `artifactRef`, so the `render → redact → write`
|
|
88
|
+
chokepoint applies (`src/render.ts:179-182`). Never write an unredacted plan blob.
|
|
89
|
+
- Title = the first heading, H1 preferred; keep the 72-char cap that `deriveIdentity` already uses
|
|
90
|
+
(`src/engine-core.ts:251-262`).
|
|
91
|
+
|
|
92
|
+
### 2. Chapter body: a labeled section **after** the transcript
|
|
93
|
+
|
|
94
|
+
`renderChapter` appends one block, after the verbatim transcript and before the closing of the body —
|
|
95
|
+
never interleaved, so the transcript stays exactly what the log said:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
## Plans in this chapter
|
|
99
|
+
|
|
100
|
+
### Plan — "Carry session plans into chapters" — APPROVED (call seq 412, result seq 419)
|
|
101
|
+
- full text: `artifacts/8f/8f3a1c….txt` (18.4 KB ≈ 4.6K tok) — chapters_artifact 'toc' | 'search' | 'read'
|
|
102
|
+
- outline: Goal · Success criteria · Changes by subsystem · Tests · Assumptions · Rollback
|
|
103
|
+
- head (first 900 chars, verbatim): `# Carry session plans into chapters …`
|
|
104
|
+
- mode: plan mode ACTIVE from seq 118 (log event plan/mode, seq 118)
|
|
105
|
+
- superseded by the same-titled plan at seq 455 — that one is operative
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Frontmatter gains `plans: [412:approved:8f3a1c9d]` — one `seq:status:sha8` triple per plan — and
|
|
109
|
+
`planMode: active|inactive|unknown`, BOTH emitted only when the chapter really carries a plan or a mode
|
|
110
|
+
flip. *Why the compact triple instead of the nested list this record first specified:* the only reader of
|
|
111
|
+
frontmatter is `indexing.parseFrontmatter`, which understands `key: value` and flat `key: [a, b]`. Nested
|
|
112
|
+
YAML maps parse as nothing at all — an index that looks populated and reads empty, the worst kind of lie
|
|
113
|
+
here. So the machine-readable home for the full facts is the **chapter record** (`ChapterRecord.plans`,
|
|
114
|
+
zod-validated in the registry, `src/store.ts`), which is what the notice builder consumes; the
|
|
115
|
+
frontmatter line is the human-visible claim of the same fact, and the prose lives in the body block.
|
|
116
|
+
|
|
117
|
+
Display bounds, added at 0.1.8 (two of them — the second is the same mistake one scale down):
|
|
118
|
+
|
|
119
|
+
- the OUTLINE line is an index, not the document: `PLAN_OUTLINE_MAX_ENTRIES` (12) at
|
|
120
|
+
`PLAN_OUTLINE_TITLE_MAX` (60) chars each, and when entries are dropped the block says
|
|
121
|
+
`+more (chapters_artifact 'toc' lists every heading)`. A silently shortened index is the lie this store
|
|
122
|
+
exists to avoid, so truncation is always announced and the full map is one query away.
|
|
123
|
+
- the TITLE is printed at most 120 characters
|
|
124
|
+
(`PLAN_TITLE_MAX`, `src/plans.ts`) — a 4 KB H1 would otherwise have been repeated in every chapter carrying it
|
|
125
|
+
AND in every child notice built from that path, unbounded and un-costed until the budget gate refused the
|
|
126
|
+
continuation. The artifact keeps the heading whole; only the display is bounded, which is the same
|
|
127
|
+
distinction `planInlineChars` draws for the body. Closed ATX headings (`# Title #`) lose their trailing
|
|
128
|
+
hashes in the title, not in the bytes.
|
|
129
|
+
|
|
130
|
+
The sha256 anchor convention must hash what `parseChapterFile`
|
|
131
|
+
returns — body **including** the trailing newline (`src/engine-core.ts:297-302`; the T9 lesson: a
|
|
132
|
+
mismatch silently blocked enrichment for an era).
|
|
133
|
+
|
|
134
|
+
`plan/mode` gets an explicit `case` so its handling is *stated* rather than a silent skip: it renders as
|
|
135
|
+
the `mode:` line above and does not enter `unrenderedSeqs`.
|
|
136
|
+
|
|
137
|
+
### 3. Invocation artifacts, generally (the honest fix for "always inline")
|
|
138
|
+
|
|
139
|
+
Any `tool/call` whose serialized arguments exceed `callArgArtifactTokens` gets the same treatment:
|
|
140
|
+
full blob to the artifact store, bounded head + path inline. Plans are simply the case that earns the
|
|
141
|
+
mechanism. In the same commit, correct `docs/architecture.md:73` and `:97`, which claim invocations and
|
|
142
|
+
arguments "always stay inline" while the code caps at 400 (`src/render.ts:255`). The real rule:
|
|
143
|
+
|
|
144
|
+
> Invocations are always inline; a bounded head is always artifact-backed. Every byte of an
|
|
145
|
+
> invocation and its result remains retrievable.
|
|
146
|
+
|
|
147
|
+
### 4. The notice: `plansSection`, built exactly like `rulesSection`
|
|
148
|
+
|
|
149
|
+
`assembleNotice` takes an optional `plansSection: string` (`src/continue-core.ts:111-141` precedent).
|
|
150
|
+
Empty or absent ⇒ **zero injected bytes**, which is what keeps this tape-safe (§ Tapes). Placement:
|
|
151
|
+
between the project line and the preamble, so it is above the fold:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
## Plans on this path (3: 1 approved, 1 rejected, 1 pending)
|
|
155
|
+
- ACTIVE PLAN: "Carry session plans into chapters" — APPROVED (ch. 4, seq 412, sha 8f3a1c…, ~4.6K tok)
|
|
156
|
+
full text: .dsh-chapters/artifacts/8f/8f3a1c….txt — read it with chapters_artifact before implementing;
|
|
157
|
+
do NOT re-plan from recall.
|
|
158
|
+
- REJECTED v1 "…" (ch. 4, seq 388) — user feedback is archived in that chapter
|
|
159
|
+
- pending "…" (seq 501) — the review was never answered. NOT approved; do not implement it.
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Cost is counted, never exempted: the section is part of `noticeTokens` and flows through the same
|
|
163
|
+
`preflight` (`src/continue-core.ts:146-162`) — `tocTokens + handoffNoteTokens ≤ ratio × (window − header)`.
|
|
164
|
+
Over budget ⇒ refuse with numbers. **Do not "remove the token measurement" for plans.** The measurement
|
|
165
|
+
is the only thing that keeps the refuse-with-numbers rule true; exempting a multi-K-token prose form
|
|
166
|
+
would make the 8K chapter target and the child's cold head meaningless on exactly the 32K hardware this
|
|
167
|
+
project targets. Economy comes from *carriage* (artifact + bounded head), not from blinding the meter.
|
|
168
|
+
|
|
169
|
+
### 5. `chapters_segment` reports the facts
|
|
170
|
+
|
|
171
|
+
Add to the output (`src/tools.ts:186-205`): `plans: [...]` (every field a model needs to choose ranges,
|
|
172
|
+
minus the text), `modeActive`, and `livePlans` — plans whose `seq > archiveCeiling` (an open turn:
|
|
173
|
+
precisely the mid-review case), explicitly labelled *not archived, parent log only*. Ranges stay the
|
|
174
|
+
model's call and chapter text stays the plugin's (invariant 4); the model never guesses a verdict again.
|
|
175
|
+
|
|
176
|
+
### 6. Composition and titles
|
|
177
|
+
|
|
178
|
+
- Forced split points: an `exit_plan_mode` call seq is a topic boundary — feed them to
|
|
179
|
+
`composeChapters` (`src/compose.ts:78`) so a plan is never buried mid-chapter.
|
|
180
|
+
- `deriveIdentity` prefers the plan H1 when a span opens with a submission (`src/engine-core.ts:251`);
|
|
181
|
+
`HARNESS_PROSE` (`:248`) stays as-is — a plan is not harness prose.
|
|
182
|
+
|
|
183
|
+
### 7. Config register (Schemastery `Config`, `src/index.ts:53-110`) — no hardcoded tunables
|
|
184
|
+
|
|
185
|
+
| Key | Default | Meaning |
|
|
186
|
+
|---|---|---|
|
|
187
|
+
| `planInlineChars` | 900 | Verbatim head in the chapter block. The artifact holds the rest. 0 = pointer only. |
|
|
188
|
+
| `callArgArtifactTokens` | **0 (off)** | Above this, a tool-call argument is artifact-backed. |
|
|
189
|
+
| `plansInNotice` | true | Renders zero bytes when the path holds no plans. |
|
|
190
|
+
|
|
191
|
+
*Two amendments, both made by measurement rather than taste.*
|
|
192
|
+
|
|
193
|
+
- **`planModeEvents` was cut.** A `plan/mode` event is either printed in the Plans block or it is a
|
|
194
|
+
silently dropped byte, and silently dropped bytes are the one thing this store does not do. There is no
|
|
195
|
+
honest second value for that knob, so the rule is unconditional: the flip prints, and the folded state
|
|
196
|
+
lands in `planMode:`.
|
|
197
|
+
- **`callArgArtifactTokens` ships at 0, not 600.** Turning it on changes chapter bytes wherever a `write`
|
|
198
|
+
call carried a whole file — and the recorded model tapes are full of such turns (§ Tapes). Plans are
|
|
199
|
+
unaffected: their carriage is unconditional and named. A deployment that wants argument blobs opts in
|
|
200
|
+
deliberately, knowing it owes a re-record.
|
|
201
|
+
|
|
202
|
+
### 8. Search, and why no new index
|
|
203
|
+
|
|
204
|
+
A plan is discoverable through the **chapter that cites it**: the chapter's `topics` frontmatter is
|
|
205
|
+
already the index unit (`src/indexing.ts:140-152`, `src/search.ts:104`), so `chapters_search` returns
|
|
206
|
+
the chapter, whose body hands over the artifact path, and `chapters_artifact` (toc / search / read) does
|
|
207
|
+
the recursive descent — a Markdown plan has headings, so `action:'toc'` yields a line-numbered map for
|
|
208
|
+
free. **No second index** (hard rule; the TOC is the only index). Making plans corpus documents
|
|
209
|
+
(`kind: plan`, indexed like rules — the `sync.ts:203` "rules are chapters of a different kind, same
|
|
210
|
+
transport" precedent) stays deferred to § Deferred.
|
|
211
|
+
|
|
212
|
+
Registry note: **do not** use `state.plans` — that name is the compaction manifest
|
|
213
|
+
(`src/registry.ts:37`, `src/store.ts:67`). If a cross-session lookup is ever needed, name it
|
|
214
|
+
`submittedPlans`.
|
|
215
|
+
|
|
216
|
+
## Invariant audit
|
|
217
|
+
|
|
218
|
+
| Invariant | This design |
|
|
219
|
+
|---|---|
|
|
220
|
+
| 1 — never rewrite the durable log | Read-only over the log; artifacts and chapters are appends. Original plan events stay in the parent forever. |
|
|
221
|
+
| 2 — never seed from parent events | Unchanged: still one synthetic notice event at `seq 0` (decision 2 — no `plan/mode` seed). |
|
|
222
|
+
| 3 — never contribute a system-prompt section | The plans section is per-continuation notice text, not a header contribution. We only *read* plan mode's contribution. |
|
|
223
|
+
| 4 — never let the model author chapter bodies | The plan text is copied from the log by the plugin; the model supplies ranges and gets facts back. |
|
|
224
|
+
| Hard rules | No new index; no renumbering (plan blobs are content-addressed, chapters untouched); ranges still validated (`validateRanges`, `src/render.ts:96-123`); never truncate to fit — the head cap is a *display* choice with the full bytes retrievable, and the budget still refuses. |
|
|
225
|
+
|
|
226
|
+
## Tapes and e2e impact (measured, not assumed)
|
|
227
|
+
|
|
228
|
+
- `grep -rlo "exit_plan_mode\|plan mode\|plan-mode" tests/fixtures/` → **no hits** (2026-10-09). No
|
|
229
|
+
recorded scenario submits a plan, so a conditional plans section renders zero bytes in every replay:
|
|
230
|
+
**no re-record required** for the notice or renderer changes. Re-confirm after implementation — if a
|
|
231
|
+
product prompt *is* edited (personas, tool descriptions, notice text), the rule stands: re-record
|
|
232
|
+
**whole projects**, never one spec of a shared boot.
|
|
233
|
+
- New e2e project `plans` (own boot, own `E2E_PORT`, throwaway home `var/e2e-home-<port>`, one journey =
|
|
234
|
+
one boot — the fork- and fanout-carve-out lesson), recorded once then replayed. Prompts must be
|
|
235
|
+
tool-free **in words the model obeys** (the RR2 OOM lesson). **LANDED 2026-10-10 as the eighth chain
|
|
236
|
+
project** — but NOT from the live model: the box's `:8080` server closed the stream mid-turn
|
|
237
|
+
(`turn/end` `reason {kind:"error", code:"TRANSPORT"}`), so the tape was distilled through the scripted
|
|
238
|
+
upstream `tests/e2e/stub-model.ts` (wire format copied form-for-form from recorded llama.cpp traffic).
|
|
239
|
+
`tests/fixtures/model-tape/plans/PROVENANCE.md` states what that proves (the whole carriage, in a real
|
|
240
|
+
browser, against the real review channel) and what it does not (a model choosing to plan), and carries
|
|
241
|
+
the re-record command that retires the stub.
|
|
242
|
+
- **The threshold is pinned to 0.99 for this project (0.1.10), and the journey asserts its own
|
|
243
|
+
precondition.** At the stress regime's 0.9 the hosted runner crossed the compaction trigger and issued
|
|
244
|
+
a plot-elicitation call that no locally-distilled tape had ever seen — a MISS that only CI could
|
|
245
|
+
produce, after three local greens. So this journey pins its own determinism: no compaction may interrupt
|
|
246
|
+
it, and the spec fails if `compaction/*` appears, with the reason written where the assertion lives.
|
|
247
|
+
Determinism that only holds on the machine that recorded the tape is not determinism.
|
|
248
|
+
- **0.1.11: the pin is structural, not numeric.** 0.1.10's threshold pin did NOT stop the hosted runner
|
|
249
|
+
compacting (same annotated miss), so the journey now boots with `auto: false` + `elicitedPlot: false`
|
|
250
|
+
(`E2E_NO_AUX_MODEL=1` in the chain, applied by `boot.ts` into the throwaway preset). No auxiliary model
|
|
251
|
+
request can exist for a tape to be missing. The `compaction/*`-absent guard stays, to catch someone
|
|
252
|
+
undoing it.
|
|
253
|
+
- **The replay chain reports its own failure** (`scripts/ci-replay.sh`): project + failing tests, the
|
|
254
|
+
proxy's tape-miss journal tail, and the engine diagnostics tail that every boot already writes.
|
|
255
|
+
|
|
256
|
+
### What the browser plane corrected (this is why it exists)
|
|
257
|
+
|
|
258
|
+
Every item below was an assumption the deterministic plane had encoded — and the live run said otherwise:
|
|
259
|
+
|
|
260
|
+
- **`planMode:` at a plan's chapter end is `inactive`, not `active`.** Approving a plan EXITS plan mode,
|
|
261
|
+
and the host appends that flip at the next accepted pre-step — inside the same turn, before
|
|
262
|
+
`turn/end`. My expectation said `active`; the archived chapter said `inactive`, and both mode flips
|
|
263
|
+
printed. The fold was right; my belief was wrong.
|
|
264
|
+
- **The toolbar chip is an EXIT control only.** `PlanModeControl` renders when
|
|
265
|
+
`plan.pending ? !plan.active : plan.active` (`dsh-client-ui-plan/lib/client.js:39-41`), so there is no
|
|
266
|
+
"press to turn plan mode ON". Entry is `/plan <message>` in the composer, which appends
|
|
267
|
+
`plan/mode {active:true}` before the turn and steers the text as one real user message. Learning that
|
|
268
|
+
cost one 40-minute timeout on a locator that could never appear.
|
|
269
|
+
- **`text=Approve this plan…` hangs; `getByRole('region', {name: /Approve this plan/})` does not.** The
|
|
270
|
+
question is the review REGION's accessible name, not matchable element text — read off the failure
|
|
271
|
+
snapshot, which is the only honest way to find this out.
|
|
272
|
+
- **A seeded event read out of the session log is JSON-escaped.** `expect(raw).toMatch(/ACTIVE PLAN: "x"/)`
|
|
273
|
+
can pass as a substring and fail as a regex; parse the event and assert on `data.content[0].text`.
|
|
274
|
+
- **`expect.poll(..., {intervals: 3000})` throws a TypeError** — intervals is an array. Trivial, but it
|
|
275
|
+
killed the first two recording runs.
|
|
276
|
+
- **A scripted upstream that string-matches the message array lies confidently.** The stub's first
|
|
277
|
+
`decide()` searched all messages for `exit_plan_mode`/`approved` — both words live in the SYSTEM PROMPT
|
|
278
|
+
(tool catalog + plan policy), so every turn answered as if the review were done and the review card
|
|
279
|
+
never opened. The honest signal is the transcript's SHAPE (`role:'tool'` exists only once a tool ran).
|
|
280
|
+
Pinned by `tests/unit/stub-model.test.ts`, because a stub that answers wrongly records a tape of a
|
|
281
|
+
session that never planned.
|
|
282
|
+
- **Acceptance assertions must be scoped to the session tree the run created.** Scanning the whole store
|
|
283
|
+
lets a previous run's plan satisfy the next run — a green that proves nothing.
|
|
284
|
+
- Coverage: `npm run coverage:ci` gate (≥85 all four) must stay green; `src/plans.ts` is pure and gets a
|
|
285
|
+
full-branch row like the other planners, and the new `render.ts` arms need the success/refusal
|
|
286
|
+
surface covered the way the 2026-09-23 sweep did it.
|
|
287
|
+
|
|
288
|
+
## Verification rows to add to `docs/verify.md`
|
|
289
|
+
|
|
290
|
+
| # | Claim | Evidence |
|
|
291
|
+
|---|---|---|
|
|
292
|
+
| P1 | Verdict classification is right for all four shapes + superseded | `tests/unit/plans.test.ts` over host-shaped fixtures (mirror the real nesting the way `tests/unit/render.test.ts:18-22` mirrors tool-result shape) |
|
|
293
|
+
| P2 | A plan with no result is `pending`, and `pending` never renders as approval | unit + a notice golden test |
|
|
294
|
+
| P3 | Full plan bytes are retrievable: archive → artifact exists → `chapters_artifact` read returns them byte-identical (modulo redaction) | integration, same style as `corpus-hygiene.test.ts` |
|
|
295
|
+
| P4 | `plan/mode` no longer lands in `unrenderedSeqs`, and no other event type silently vanishes | `tests/unit/render.test.ts` rows (the `mystery/event` row at `:124-129` must stay) |
|
|
296
|
+
| P5 | The plans section is counted by `preflight` and an over-budget archive refuses **with numbers** | unit on `preflight` + integration on `runContinue` |
|
|
297
|
+
| P6 | A plan above the ceiling is reported as `livePlans` and carried, marked not-archived | integration on `chapters_segment` |
|
|
298
|
+
| P7 | Injected spans inside a plan body are screened; a plan quoting `AGENTS.md` in prose survives verbatim | `injections.test.ts` rows (H1 family) |
|
|
299
|
+
| P8 | Zero bytes when the path has no plans (tape neutrality) | notice golden test, both directions |
|
|
300
|
+
| P9 | Both carriage paths agree (continuation AND in-place engine compaction) | `tests/unit/plans-engine.test.ts` |
|
|
301
|
+
| P10 | A chapter's `artifacts/…` citation resolves whether the blob sits at the store root or inside the ancestry tree | `tests/unit/artifact-query.test.ts` + the integration row |
|
|
302
|
+
| P11 | The browser journey, once its tape is recorded | `tests/e2e/plans.spec.ts` (project `plans-dev`, self-skips without the live model) |
|
|
303
|
+
|
|
304
|
+
`docs/verify.md` § Plans Carriage is the ledger; this table is the design's own list of what it claims.
|
|
305
|
+
|
|
306
|
+
## Landing stages
|
|
307
|
+
|
|
308
|
+
| Stage | Deliverable | Status (2026-10-09) |
|
|
309
|
+
|---|---|---|
|
|
310
|
+
| 0 | This record lands; no code | done — reviewed, decisions 1–3 held |
|
|
311
|
+
| 1 | `src/plans.ts` + `src/log-shape.ts` (the shared pairing readers) + unit rows | done — P1/P2/P7 |
|
|
312
|
+
| 2 | `render.ts` Plans block + frontmatter + invocation artifacts; `docs/architecture.md` wording fix | done — P3/P4/P8 |
|
|
313
|
+
| 3 | `chapters_segment` facts + composer split points + `deriveIdentity` preference | done — P6 |
|
|
314
|
+
| 4 | `plansSection` in `assembleNotice` + wiring + config register | done — P5/P8/P9 |
|
|
315
|
+
| 5 | Site docs (§9 anatomy, §16 tools, §17 configuration, §18 notice) | done in the same release |
|
|
316
|
+
| — | Seven-segment tape replay | green WITHOUT re-recording (2026-10-09, `npm run test:e2e:replay`, EXIT=0) |
|
|
317
|
+
| — | `plans` browser journey | LANDED as the eighth chain project (2026-10-10) and immediately **extended to two legs** so § 12's continue-vs-branch distinction is tested, not described: a `chapters_segment`→`chapters_continue` continuation (asserts `## Active plan`, the plan's document path, and NO seeded gate because approving a plan exits plan mode) and a fork-button branch (asserts **no** directive section, archive still cited). Replays in 17 s on a committed tape; the eight-project chain re-runs for the 0.1.9 ship. Its tape is scripted-upstream distilled — see § Tapes and `PROVENANCE.md` |
|
|
318
|
+
| 8 | `/chapters-continue` — the deterministic door for continuations (§ 15): continuation leg back in the tape chain, both intents covered in the browser, no frozen ranges |
|
|
319
|
+
| 7 | Journey made hardware-independent (0.1.10): 0.99 threshold pin + `compaction/*`-absent guard; replay chain annotates its own failure and ships the engine diagnostics | built — tape re-distilled under the pin, eight-project chain `CHAIN_EXIT=0` locally, hosted acceptance green at the 0.1.10 tag |
|
|
320
|
+
| 7 | Findability round (§ 14): local search door over a shared scorer, `## Finding things` in the notice (`retrievalHints`), kernel-drift gate on the seed contract | built — verify.md P18–P20; two pinned contract rows amended with reason; eight-project chain re-run |
|
|
321
|
+
| 6 | Second-round decisions (§§ 9–13): intent-aware notice, seeded plan mode for continuations, `kind: plan` documents, checkpoint carriage, the one injection-screen exception | built 2026-10-10 — verify.md P12–P17; 582 deterministic rows green, gate 98.05 / 86.44 / 93.67 |
|
|
322
|
+
| — | Follow-up, not blocking | re-record the `plans` tape from the live model — ATTEMPTED TWICE on 2026-10-10 and both times the model container dropped the stream mid-turn (`TRANSPORT`), one after 15 minutes with zero tool calls; `PROVENANCE.md` says what to check first. The carriage evidence does not change, only the claim about model behaviour |
|
|
323
|
+
| — | Browser-plane ledger | `npm run coverage:e2e` over all EIGHT replays: **79.92 stmts / 64.64 branch / 76.28 funcs** — the non-gated plane, reported rather than tuned (the deterministic gate is the enforced one) |
|
|
324
|
+
|
|
325
|
+
Deterministic ledger at landing: **549 unit+integration rows, zero failures** with the TreeDX container
|
|
326
|
+
up (80 of them new, across `plans`, `render-plans`, `continue-plans`, `plans-segmentation`, `plans-tools`,
|
|
327
|
+
`plans-engine`, `log-shape` and the `plans-carriage` integration), `npm run coverage:ci` **98.11 % stmts /
|
|
328
|
+
86.44 % branch / 93.8 % funcs** — the gate (≥85 on all four) green, branches *up* from the 85.61 % the
|
|
329
|
+
record started at. The seven-segment tape replay then ran against the BUILT artifacts: EXIT=0, 8 tests,
|
|
330
|
+
with no tape re-recorded.
|
|
331
|
+
|
|
332
|
+
### Two things the implementation found that the record had not predicted
|
|
333
|
+
|
|
334
|
+
- **`renderChapter` cannot pair a verdict inside its own range.** A composed chapter boundary can fall
|
|
335
|
+
between an `exit_plan_mode` call and the `tool/result` that answers it, so a range-local scan printed
|
|
336
|
+
APPROVED plans as PENDING — the exact inversion this feature exists to prevent. The scan therefore runs
|
|
337
|
+
over the whole event list the caller passes (`buildFinalizedChapters` now hands it the full shadowed
|
|
338
|
+
span instead of one chapter's slice) and only the *display* is range-filtered. Pinned by P9.
|
|
339
|
+
- **An ancestor path's plans cannot be ordered by `seq`.** Every session numbers its own events from zero,
|
|
340
|
+
so a root chapter's seq 40 and a child chapter's seq 3 are incomparable numbers, and sorting the notice
|
|
341
|
+
rows by seq would crown the wrong plan as operative — the exact failure mode the block exists to remove.
|
|
342
|
+
The rows therefore travel in **causal order**: the registry walk's root-first chapter sequence, plans
|
|
343
|
+
within a chapter by seq, then the caller's unarchived tail, and `buildPlansSection` prints that order and
|
|
344
|
+
takes the last row as operative without re-sorting. (This is the same flattening rule the chapter list
|
|
345
|
+
already follows — reading order IS causal order — applied to a new record type; pinned by a unit row.)
|
|
346
|
+
- **Deferred artifacts were already unreachable, for every blob, not just plans.** `writeArchive` writes
|
|
347
|
+
to `<storeRoot>/<rootSession>/artifacts/…`, the arrival stubber writes to `<storeRoot>/artifacts/…`, and
|
|
348
|
+
`resolveArtifactWithMirror` looked only at the second place plus the mirror — so a chapter's own
|
|
349
|
+
`artifacts/aa/<sha>.txt` citation, the standard form the body prints, could 404 on the machine that
|
|
350
|
+
wrote it. The resolver now walks the session trees between the store root and the mirror (P10), which
|
|
351
|
+
is what makes "every byte remains retrievable" true rather than intended.
|
|
352
|
+
|
|
353
|
+
## 14. Findability: a plan nobody can reach is a plan nobody follows
|
|
354
|
+
|
|
355
|
+
The four decisions made plans *carried*. The audit that followed ("make sure a new session can find the
|
|
356
|
+
plans, rules, and other important details when asked") found the index was already correct and the
|
|
357
|
+
**access path** was not — two separate defects, both now pinned:
|
|
358
|
+
|
|
359
|
+
1. **`chapters_search` needed a pool mirror.** Unlinked, it answered `[]` plus "link a remote" — while
|
|
360
|
+
every `kind: plan` document sat in the workspace store two directories away. Search now has a local
|
|
361
|
+
door over the **same scorer** (`rankEntries`), falls back to it when the pool has no match, names
|
|
362
|
+
which door answered, and distinguishes an empty workspace from a missed query. Artifacts stay out of
|
|
363
|
+
the index on purpose: they answer to `chapters_artifact`, and indexing them twice is the second index
|
|
364
|
+
the record forbids.
|
|
365
|
+
2. **The child was never told the verbs.** The notice taught `read` and nothing else; `chapters_search`
|
|
366
|
+
and `chapters_artifact` were installed but unmentioned — so for a small model they did not exist. The
|
|
367
|
+
notice gains `## Finding things` (three lines) and says the one thing a session cannot infer: that
|
|
368
|
+
plans are searchable **as plans**.
|
|
369
|
+
|
|
370
|
+
Measured before believing: **no committed tape prefix contains a seeded notice anywhere** (checked
|
|
371
|
+
entry-by-entry across all eight projects), so the notice change costs zero re-records and the chain
|
|
372
|
+
replays green with it. That is coverage luck, not design — no current journey makes a *continuation
|
|
373
|
+
child* speak — which is why the child-facing prose now has golden rows, why the live-model leg exists,
|
|
374
|
+
and why "a child answers a findability question in a browser" is on the open list rather than claimed.
|
|
375
|
+
|
|
376
|
+
Two rows in the suite pinned the OLD contract and failed loudly, which is the only reason this section
|
|
377
|
+
is honest: `no mirror ⇒ honest empty` became `no mirror ⇒ the local answer, with the door named`. Amended
|
|
378
|
+
in place with the reason in the diff, because a silently-passing old expectation would have meant the
|
|
379
|
+
feature shipped broken and green.
|
|
380
|
+
|
|
381
|
+
## 15. The deterministic door: `/chapters-continue`
|
|
382
|
+
|
|
383
|
+
Attempt 5 settled an argument that had cost three releases. Two facts, both measured:
|
|
384
|
+
|
|
385
|
+
1. **A tape cannot carry a model-authored range.** The continuation leg broke on the runner because the
|
|
386
|
+
recorded `chapters_continue` arguments said `endSeq: 30` while the runner's own ceiling was 29.
|
|
387
|
+
`validateRanges` refused — correctly. Nothing about pinning a threshold can fix a number frozen in a
|
|
388
|
+
recording.
|
|
389
|
+
2. **A small local model will not reliably chain two tool calls.** Live, it called `chapters_segment` and
|
|
390
|
+
then answered in prose. So the leg could not be made green by asking the model harder.
|
|
391
|
+
|
|
392
|
+
Both have the same cure: the plugin already knew how to create a child without a model. `/chapters-fork`
|
|
393
|
+
derives everything itself — *"no model involvement — `deriveRanges` is deterministic, so a click needs no
|
|
394
|
+
agent turn and no proposed ranges."* The gap was that this door was hard-wired to `intent: 'branch'`, and a
|
|
395
|
+
branch by decision 3 asserts **nothing** about plans. So every property of the *continuation* shape
|
|
396
|
+
(`## Active plan`, `## Previous plans on this path`, the seeded `plan/mode`, the `plan document:` citation)
|
|
397
|
+
was provable only through the model's door.
|
|
398
|
+
|
|
399
|
+
**`/chapters-continue` is that same builder with the other intent.** Archive semantics are identical by
|
|
400
|
+
user decision (everything since the watermark). It is one `deterministicCommand(door)` producing both
|
|
401
|
+
defs — name, verbs, intent, and the no-directive prose are the only differences, and
|
|
402
|
+
`tests/unit/continue-command.test.ts` pins that the two doors **archive identically** (same ranges, same
|
|
403
|
+
titles, same plan facts) so the factoring cannot drift into two implementations. Consequences:
|
|
404
|
+
|
|
405
|
+
- the continuation leg is **back in the tape chain**, replaying on every push with no GPU and no frozen
|
|
406
|
+
number (it needs no model request at all — the child is created by the command, and asserted from its
|
|
407
|
+
durable log without the UI switching to it);
|
|
408
|
+
- the browser plane now covers **both intents**, which is where decision 3 actually lives;
|
|
409
|
+
- and the tool `chapters_continue` keeps existing for the model's own use — it is simply no longer the
|
|
410
|
+
only way to learn whether a continuation carries its plan.
|
|
411
|
+
|
|
412
|
+
### The seeded-gate case: measured, and why there is no browser row for it yet
|
|
413
|
+
|
|
414
|
+
The open question was whether the host gives a completed turn to archive while a plan review is still
|
|
415
|
+
pending. Answered from attempt 5's own log (no speculation): `plan/mode` active at seq 4,
|
|
416
|
+
`exit_plan_mode` at 20, the verdict at 21, `plan/mode` inactive at 23, `turn/end` at **41** — the turn
|
|
417
|
+
containing a submission does not end until the review is answered. So:
|
|
418
|
+
|
|
419
|
+
- a session whose *only* turn is pending has **no ceiling**, and `/chapters-continue` refuses with its
|
|
420
|
+
first guard (`nothing to archive yet — the conversation has no completed turn`);
|
|
421
|
+
- the seeded case is reachable in a browser only when an earlier turn already closed — which means a
|
|
422
|
+
second distilled session (a plain turn, then `/plan`), not a rewording of the current one;
|
|
423
|
+
- the plugin-side behaviour for that shape **is** pinned at L0: a pending-review fixture yields
|
|
424
|
+
seed `['user/message', 'plan/mode']`, the notice line `Plan mode is ACTIVE in this session`, and the
|
|
425
|
+
plan marked PENDING/un-stored (`continue-command.test.ts`).
|
|
426
|
+
|
|
427
|
+
So the honest statement is: the seeded continuation is covered deterministically and structurally, and
|
|
428
|
+
NOT yet observed in a real browser. An attempt at that browser row on 2026-10-11 was built and then
|
|
429
|
+
REVERTED, unshipped, for two reasons worth recording so the next person does not re-derive them:
|
|
430
|
+
(a) the scripted upstream had to learn to answer PLAINLY in plan mode, and its decision rule first keyed
|
|
431
|
+
on the LAST user message — but the harness appends its own injected user messages (instructions, runtime
|
|
432
|
+
snapshot, skill catalog) AFTER the human's, so the ask was invisible and the journey hung on a review card
|
|
433
|
+
that could never appear; that ordering trap is now pinned by a unit row; and (b) a stale stub process
|
|
434
|
+
serving pre-edit logic made the failure look like a product problem. Neither is a product defect. The
|
|
435
|
+
shape is sound — `plan/mode {active:true}` logged, turn completed at seq 21, no tool call — so a future
|
|
436
|
+
leg needs the plain branch plus a fresh distillation, and nothing else. Building that row needs new stub rules and a fresh distillation; it is
|
|
437
|
+
recorded as the remaining piece rather than quietly claimed.
|
|
438
|
+
|
|
439
|
+
## Deferred (human decisions, not ours)
|
|
440
|
+
|
|
441
|
+
- ~~`kind: plan` corpus documents~~ **decided and built** (§ 11).
|
|
442
|
+
- ~~Seeding `plan/mode`~~ **decided and built** (§ 10), for continuations only.
|
|
443
|
+
- ~~Whether the mode-switch notice stays screened~~ **decided and built** (§ 13).
|
|
444
|
+
- **Per-message anchoring** — still open, and decision 3 clarified rather than settled it: a fork
|
|
445
|
+
splits the task, so the interesting anchor is *"branch from THIS plan"*, which `deriveRanges`
|
|
446
|
+
already supports mechanically but the button still forks at conversation end. Needs the UI
|
|
447
|
+
affordance and an answer on how the child is told what it did not inherit.
|
|
448
|
+
- Whether the `plan-mode` mode-switch *notice* should stay screened out of chapters (decision 2026-09-21
|
|
449
|
+
says injected context is not conversation; a human-initiated mode flip arguably is). Small text, but
|
|
450
|
+
it is the one place the blanket screen drops something a reader might want.
|
|
451
|
+
|
|
452
|
+
## Proposed AGENTS.md lines (deliberately NOT applied yet — it is the unimplemented-feature rule)
|
|
453
|
+
|
|
454
|
+
```
|
|
455
|
+
- `docs/plans-carriage.md` — **read before writing code in this area**: how a session plan reaches the
|
|
456
|
+
chapters, the TOC, and the artifact store; and why the token meter is never exempted.
|
|
457
|
+
```
|
|
458
|
+
plus a find-fast row `plans in chapters / active plan in TOC → docs/plans-carriage.md (+ site §§ notice, archive anatomy)`.
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
## Decisions taken (second round, 2026-10-10) — all four deferred items resolved
|
|
462
|
+
|
|
463
|
+
The human answered the deferred list, and #2 came with a distinction the first round
|
|
464
|
+
had not drawn: **a continuation extends the same stream of work; a fork splits it.**
|
|
465
|
+
That single difference decides what each child is told about the operative plan, and
|
|
466
|
+
it is now the axis the whole carriage hangs on.
|
|
467
|
+
|
|
468
|
+
| # | Decision | What it means in code |
|
|
469
|
+
|---|---|---|
|
|
470
|
+
| 1 | **`kind: plan` documents — yes** | each plan becomes its own indexed document (§ 11) |
|
|
471
|
+
| 2 | **Seed plan mode into children — yes, but only for continuations** | `plan/mode {active:true}` as the child's own second seed event (§ 10), `intent === 'continue'` only |
|
|
472
|
+
| 3 | **Fork = split the task; compaction/continuation = keep it** | `PlanIntent` selects the notice shape: `## Active plan` + `## Previous plans` for a continuation, NOTHING for a branch (§ 12) |
|
|
473
|
+
| 4 | **Targeted exemption for the mode-switch notice — yes** | `isSessionStateNarration` archives it verbatim; the general screen is unchanged (§ 13) |
|
|
474
|
+
|
|
475
|
+
Plus, from the same message: *"I support anything that is going to keep the agent anchored to
|
|
476
|
+
the current task"* — which is why the operative plan is now carried into every **in-place
|
|
477
|
+
checkpoint** as well, the way the plot is (§ 9).
|
|
478
|
+
|
|
479
|
+
### 9. The checkpoint carries the operative plan (the plot mechanism, applied to directives)
|
|
480
|
+
|
|
481
|
+
`extractPlot` proved the pattern: state that must survive a shadowed region travels *inside*
|
|
482
|
+
the checkpoint text, bounded, with its provenance. `carryActivePlan` does the same for plans —
|
|
483
|
+
with one deliberate difference in **scope**: the plot reads the region being condensed, while
|
|
484
|
+
the operative plan is chosen **session-wide, newest submission first**, because "what am I
|
|
485
|
+
building" is not a property of the region being hidden. `planSummarize` prefixes it on both the
|
|
486
|
+
composed and the legacy path.
|
|
487
|
+
|
|
488
|
+
Two honesty notes recorded rather than smoothed:
|
|
489
|
+
- **No sha is cited in the checkpoint.** At summarize time the chapter does not exist yet, and
|
|
490
|
+
an unverifiable citation is worse than none — the carry says "the archive chapter holding
|
|
491
|
+
this plan cites its complete text" instead.
|
|
492
|
+
- **Truncation is announced** (`carried truncated — …`), never silent, even though the
|
|
493
|
+
bound (`planCheckpointChars`, 1600) is a display economy choice and the archive holds all.
|
|
494
|
+
- The pre-existing asymmetry that `plot` is prefixed only on the legacy path was **left
|
|
495
|
+
alone**: changing it would rewrite checkpoint bytes for every plot-carrying session, i.e. the
|
|
496
|
+
committed tapes. Plans are new, so carrying them on both paths costs no re-record.
|
|
497
|
+
|
|
498
|
+
### 10. Seeding plan mode — measured against the installed kernel, not assumed
|
|
499
|
+
|
|
500
|
+
The claim needed proof, because invariant 2 pins the seed as *one synthetic event at seq 0*.
|
|
501
|
+
Probed directly against the shipped `Session` (`0.1.5-rc.1`) in the exact production header
|
|
502
|
+
shape (`isSeeded:false`, `inheritedEventCount:0`):
|
|
503
|
+
|
|
504
|
+
| probe | result |
|
|
505
|
+
|---|---|
|
|
506
|
+
| is `plan/mode` a kernel-known event type? | **yes** — it is in `KNOWN_SESSION_EVENT_TYPES` (56 types) |
|
|
507
|
+
| seed `[notice@0, plan/mode@1]` accepted? | **yes** — log becomes `0:user/message → 1:plan/mode → 2:session/end-seed` |
|
|
508
|
+
| is it surface-visible? | **no** — surface nodes stay `[0]`, matching the host's own log-only `plan/mode` |
|
|
509
|
+
| does the HOST's own projection fold it? | **yes** — `planProjectionDefinition.apply` over the child's log yields `active: true`; the one-event control yields `active: false` |
|
|
510
|
+
|
|
511
|
+
So the seeded child is genuinely gated, not merely told. The surface validator narrows by type
|
|
512
|
+
rather than whitelisting (`assertSessionEventEnvelope` checks the envelope and continuity),
|
|
513
|
+
which is why this works without touching the kernel. **Invariant 2 is intact in substance**: the
|
|
514
|
+
seeded event is a synthetic fact this plugin authors about the boundary, never a slice of the
|
|
515
|
+
parent's log — the rule's letter ("exactly one event") is amended here and in AGENTS, and the
|
|
516
|
+
amendment is only safe because it was measured.
|
|
517
|
+
|
|
518
|
+
Seeding is refused — correctly, not defensively — when the fold says inactive, which is the
|
|
519
|
+
normal case after an approval: **approving a plan exits plan mode**, so a continuation whose plan
|
|
520
|
+
was approved gets `## Active plan … APPROVED` as a work order to implement, and no gate.
|
|
521
|
+
|
|
522
|
+
### 11. Plans as `kind: plan` documents
|
|
523
|
+
|
|
524
|
+
A plan is now also a file of its own: `<storeRoot>/<rootSession>/chapters/plan-<NNN>-<slug>.md`,
|
|
525
|
+
frontmatter leading with `kind: plan`, then `status`, `planSeq`, the citing `chapter`, `sha256`
|
|
526
|
+
(the blob's hash — the same bytes, so the two handles cannot drift silently), `bodySha256`,
|
|
527
|
+
`tokens`, `topics`. The body is the plan and nothing else: no transcript, no wrapper.
|
|
528
|
+
|
|
529
|
+
Choices worth their reasons:
|
|
530
|
+
- **In `chapters/`, not a new tree.** The pool transport, the mirror rehydrate (0.1.5) and the
|
|
531
|
+
corpus index then carry plan documents with zero special casing. `loadCorpus` no longer
|
|
532
|
+
flattens a file's own kind back to the directory's, so `kind: plan` survives indexing and
|
|
533
|
+
`chapters_search` can answer *"what did we plan about X"* with the directive itself.
|
|
534
|
+
- **Its own registry counter** (`nextPlanNumber`, allocated through `reservePlan` under a
|
|
535
|
+
distinct attempt key). A plan never consumes a chapter number or reorders the TOC, and names
|
|
536
|
+
are still allocated through the registry rather than by scanning a directory — so a
|
|
537
|
+
concurrent sibling cannot pick the same file. `plan-NNN-` (not `NNN-plan-`) so a listing
|
|
538
|
+
never reads as two chapters claiming 001.
|
|
539
|
+
- **The blob stays.** Two handles, one purpose each: the content-addressed blob is what a
|
|
540
|
+
chapter cites without knowing any number, and what `chapters_artifact` takes (it accepts a
|
|
541
|
+
bare sha); the document is what the index searches and what a human browses. Same bytes, and
|
|
542
|
+
the record ties them with one sha.
|
|
543
|
+
- **Enrichment skips plan documents.** The ladder annotates narrative; a directive's body is
|
|
544
|
+
approved text, and paraphrasing an order is precisely the drift this store exists to prevent.
|
|
545
|
+
Guarded on the frontmatter `kind`, not the filename, so renaming cannot disarm it.
|
|
546
|
+
- **Superseded plans keep both documents.** Evolution is history: the v1 file says
|
|
547
|
+
`status: superseded` and is never rewritten, the v2 file says `approved`.
|
|
548
|
+
|
|
549
|
+
### 12. The notice is intent-aware
|
|
550
|
+
|
|
551
|
+
`buildPlansSection(…, { intent, modeActive, seeded })`:
|
|
552
|
+
- **`continue`** → `## Active plan` (the operative directive, with its verdict, chapter, blob
|
|
553
|
+
and document paths) and, only when earlier submissions exist, `## Previous plans on this
|
|
554
|
+
path` marked *history — cited for evolution and status, not directives*. If plan mode was
|
|
555
|
+
seeded, the block says the directive is live and implementation waits for an approval; if
|
|
556
|
+
it was not, the child is told plainly that it starts outside plan mode.
|
|
557
|
+
- **`branch`** → **empty string, zero bytes.** A fork splits off a new direction; re-asserting
|
|
558
|
+
the parent's directive into the branch would be an order nobody gave. The plans are still
|
|
559
|
+
fully present — in the chapters the TOC already cites, and as searchable `kind: plan`
|
|
560
|
+
documents.
|
|
561
|
+
|
|
562
|
+
### 13. The one exception to the injection screen
|
|
563
|
+
|
|
564
|
+
`isSessionStateNarration` matches `source.kind === 'plugin' && plugin === 'plan-mode' &&
|
|
565
|
+
form === 'notice'` — the harness's own "The user switched this session to plan mode." — and
|
|
566
|
+
`screenConversation` archives it verbatim instead of marking it omitted. Named by plugin AND
|
|
567
|
+
form, so it cannot widen by accident; an instruction file or a runtime snapshot arriving under
|
|
568
|
+
any other shape still screens. `isHumanAuthored` deliberately stays strict, because the topic-
|
|
569
|
+
signature layer is what decides chapter boundaries and the r28 lesson was that injected text
|
|
570
|
+
poisons that signal. The archive gains a fact about the session; the index gains nothing it
|
|
571
|
+
should not have.
|