ytdb-slate 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,9 @@
4
4
 
5
5
  Slate is a thread-weaving orchestration extension for the [pi coding agent](https://pi.dev).
6
6
 
7
- The orchestrator (your main pi session) dispatches **bounded actions** to persistent **worker threads**. Each completed action is compressed by an LLM into an **episode** — a durable, structured record (intent, actions, findings, artifacts, open issues, handoff notes) that the orchestrator composes into further dispatches instead of re-reading raw transcripts. On top of that, Slate injects a mandatory workflow doctrine: research before design, design review, adversarial review, and track review — with optional umbrella **draft-PR publishing** for tracks. An opt-in **model-failover** map adds high availability: when a model API fails, the orchestrator, worker threads, and episode compression each retry once on a configured equal-quality alternative. A second opt-in, **action-level model routing**, gives each dispatched action a model and an effort level chosen to be up to the task and no more, so cost is bounded per action instead of per session.
7
+ The orchestrator (your main pi session) dispatches **bounded actions** to persistent **worker threads**. Each completed action is compressed by an LLM into an **episode** — a durable, structured record (intent, actions, findings, artifacts, open issues, handoff notes) that the orchestrator composes into further dispatches instead of re-reading raw transcripts. Slate also injects a mandatory workflow doctrine. Its design and review gates scale with the confirmed change class. Optional umbrella **draft-PR publishing** covers tracks.
8
+
9
+ An opt-in **model-failover** map adds high availability: when a model API fails, the orchestrator, worker threads, and episode compression each retry once on a configured equal-quality alternative. A second opt-in, **action-level model routing**, gives each dispatched action a model and an effort level chosen to be up to the task and no more, so cost is bounded per action instead of per session.
8
10
 
9
11
  ## Why Slate?
10
12
 
@@ -36,16 +38,29 @@ Full rationale: [`docs/design-principles.md`](docs/design-principles.md), shippe
36
38
 
37
39
  ## Feature development workflow
38
40
 
39
- In orchestrator mode, Slate injects a track-based development workflow as doctrine — it is not optional; project configuration can extend it (`doctrineExtraPath`) but never replace it. The flow:
41
+ In orchestrator mode, Slate injects a mandatory track-based development workflow. Project configuration can extend it through `doctrineExtraPath`. Configuration cannot replace it.
42
+
43
+ Slate uses four change classes. Evaluate these tests in order. The first match
44
+ sets the class:
45
+
46
+ 1. **Risky:** The change touches concurrency, durability, recovery,
47
+ transactional semantics, security, or user data. This class also covers a
48
+ public API or behavioral change, or a silent failure mode.
49
+ 2. **Complex:** The shape is unclear, several parts interact, or a live design
50
+ alternative exists.
51
+ 3. **Trivial:** The change is an obvious fix with no behavioral change, and one
52
+ reviewer pass covers it.
53
+ 4. **Medium:** Otherwise.
54
+
55
+ After classification, follow these workflow steps:
40
56
 
41
- 1. **Research** — interactive exploration; a research log opens lazily when defined triggers fire (non-trivial decisions, surprises, risky invariants, session boundaries).
42
- 2. **User design review** — the user approves the design direction before any machine review.
43
- 3. **Adversarial review** — a fresh-context reviewer attacks the design pre-implementation; findings are triaged with the user.
44
- 4. **Track split approval** — the user approves how the change splits into independently reviewable tracks.
45
- 5. **Per-track loop** — implement → agent code review → fixes → mandatory user review → marker commit (an empty commit marking the track's boundary in history).
46
- 6. **Delivery** — with draft-PR publishing enabled, the squash-merge is performed by the user; the agent never merges. With it disabled (the default), the change lands as a squashed commit that carries the workflow log's key content in its message.
57
+ 1. **Classification and research** — the orchestrator proposes the change class, and the user confirms it. A research log is required for medium changes and above.
58
+ 2. **Design gates** — medium changes need high-level design approval before implementation. Complex and risky changes add an adversarial review between two user approvals.
59
+ 3. **Track split** — the orchestrator owns the split. Size determines tracks and marker commits, not workflow gates.
60
+ 4. **Per-track loop** — each track gets implementation, fixes, and mandatory user review. Agent code review is skipped only when the whole change is trivial.
61
+ 5. **Delivery** — the user performs the squash merge when draft publishing is enabled. Otherwise, the squashed commit carries the workflow log's key content.
47
62
 
48
- The workflow scales with change size: multi-track changes get the full flow, single-track changes skip the split and the marker commits, and trivial changes shrink the pre-implementation reviews to consent plus a micro-review (skippable with explicit user consent) — the agent code review and the mandatory user review still apply at every tier. Umbrella draft-PR publishing activates only when `workflow.draftPRs` is `true`.
63
+ Trivial changes have no high-level design gate. Every change keeps mandatory user review of its track diff. Umbrella draft-PR publishing activates only when `workflow.draftPRs` is `true`.
49
64
 
50
65
  This summary is orientation only; the shipped docs listed below are normative.
51
66
 
@@ -104,6 +119,7 @@ Optional config file: `slate.json` in the project's pi config dir (`.pi/slate.js
104
119
  | `orchestratorPromptDocs` | string[] | `[]` | Project markdown files (paths relative to the project root) whose **contents** are appended to the orchestrator system prompt. |
105
120
  | `workerPromptDocs` | string[] | `[]` | Project markdown files whose **contents** are appended to every worker-thread system prompt. |
106
121
  | `workflow.draftPRs` | boolean | `false` | Enable umbrella draft-PR publishing for tracks. |
122
+ | `workflow.followUpIssues` | boolean | `false` | When true, the orchestrator asks which suggestions become follow-up issues in the project tracker. Suggestions are always reported whatever the value. |
107
123
  | `writing.check` | boolean | `false` | Add the writing doctrine rule, per-turn status, and worker guidance. An absent option is silently off. A malformed `writing` value, or a non-boolean `check` value, warns and defaults to off. Unknown `writing` keys warn and are ignored. See [`docs/writing-guidance.md`](docs/writing-guidance.md). |
108
124
  | `writing.remind` | boolean | `false` | Send a hidden reminder after an eligible tool result. All gates must pass: orchestrator mode, project trust, `writing.check`, this option, no pause, cadence or handoff force, and one-per-round control. See [`docs/writing-guidance.md`](docs/writing-guidance.md). |
109
125
  | `writing.remindPercent` | number | `10` | Set cadence as a percentage of Slate's current effective context budget. The value must be finite and in `(0, 100]`. The interval has an 8,192-token floor and no cap. A handoff force bypasses this threshold. See [`docs/writing-guidance.md`](docs/writing-guidance.md). |
@@ -124,7 +140,7 @@ Example `.pi/slate.json` (the `docs/agents/...` paths are placeholders — point
124
140
  "maxConcurrent": 4,
125
141
  "orchestratorPromptDocs": ["docs/agents/orchestrator-guidelines.md"],
126
142
  "workerPromptDocs": ["docs/agents/thread-guidelines.md"],
127
- "workflow": { "draftPRs": true },
143
+ "workflow": { "draftPRs": true, "followUpIssues": false },
128
144
  "writing": {
129
145
  "check": true,
130
146
  "remind": false,
@@ -170,7 +186,7 @@ Slate reads project configuration (`.pi/slate.json`) and injects project files (
170
186
 
171
187
  In orchestrator mode, Slate appends a short **doctrine** (a block of numbered rules) to the orchestrator's system prompt each turn. The doctrine does not embed the workflow docs — it cites them by **absolute path**, resolved inside the installed package (not your project), and the orchestrator reads them on demand. Those embedded paths are also why the block's character count depends on your install location; [`docs/context-budget.md`](docs/context-budget.md) has the measured sizes, with and without the optional rules, and the arithmetic for your own install:
172
188
 
173
- - `docs/track-workflow.md` — the track-based workflow (research → design review → adversarial review → track review)
189
+ - `docs/track-workflow.md` — the class-scaled workflow for research, high-level design, adversarial review, and track review
174
190
  - `docs/pr-publishing.md` — umbrella draft-PR publishing (cited only when `workflow.draftPRs` is `true`)
175
191
  - `docs/review-rules.md` — review discipline and finding rules
176
192
  - `docs/design-principles.md` — Slate's own design rationale
@@ -142,13 +142,16 @@ schema. A tool description and a parameter description are therefore both
142
142
  always loaded.
143
143
 
144
144
  The current `thread` tool description is 1,386 UTF-8 bytes. Its serialized
145
- parameter schema is 1,791 bytes, measured as `JSON.stringify(parameters)`, and
146
- includes the 289-byte `type` parameter description. The description and schema
147
- are 3,177 bytes together before provider framing. The thread-type wording in
148
- the description and schema adds 142 bytes to the previous combined figure. The
149
- increase is roughly 36 tokens at four characters per token. The figure excludes
150
- the prompt snippet, prompt guidelines, tool name,
151
- provider framing, and any serialization outside the parameter schema.
145
+ parameter schema is 2,005 bytes, measured as `JSON.stringify(parameters)`.
146
+ The schema includes the 289-byte `type` parameter description and the
147
+ `freshContext` argument.
148
+
149
+ The description and schema total 3,391 bytes before provider framing. The
150
+ `freshContext` schema entry adds 214 bytes to the previous combined figure.
151
+ The increase is roughly 54 tokens at four characters per token.
152
+
153
+ The figure excludes the prompt snippet, prompt guidelines, tool name,
154
+ provider framing, and serialization outside the parameter schema.
152
155
 
153
156
  The always-loaded doctrine has a separate measurement after the compaction
154
157
  policy.
@@ -174,14 +177,14 @@ you pay its input tokens every turn.
174
177
 
175
178
  **Its size depends on where the package is installed.** That is not
176
179
  obvious, and it catches anyone who measures: the doctrine cites the
177
- shipped docs by ABSOLUTE path, so three to six full filesystem paths
178
- are embedded in the block — three at minimum
179
- (`track-workflow.md`, `review-rules.md`, `design-principles.md`),
180
- plus `pr-publishing.md` when `workflow.draftPRs` is on, plus
181
- `model-routing.md` when the routing rule renders, plus
182
- `writing-guidance.md` when `writing.check` is on. Every additional
183
- character in the installed `docs/` directory therefore costs 3–6
184
- characters of doctrine.
180
+ shipped docs by ABSOLUTE path, so four to seven full filesystem paths
181
+ are embedded in the block — four at minimum
182
+ (`thread-cache-cost.md`, `track-workflow.md`, `review-rules.md`,
183
+ `design-principles.md`), plus `pr-publishing.md` when
184
+ `workflow.draftPRs` is on, plus `model-routing.md` when the routing
185
+ rule renders, plus `writing-guidance.md` when `writing.check` is on.
186
+ Every additional character in the installed `docs/` directory therefore
187
+ costs 4–7 characters of doctrine.
185
188
 
186
189
  The figures below are **portable characters**, defined exactly as
187
190
  Slate's own automated `doctrine-budget` check defines them: the
@@ -204,18 +207,18 @@ must be named instead of presented as the same measurement:
204
207
 
205
208
  | router | models | `draftPRs` | `writing.check` | paths | portable | lines |
206
209
  | --- | --- | --- | --- | --- | --- | --- |
207
- | off | — | off | off | 3 | 1,929 | 38 |
208
- | off | — | off | on | 4 | 2,999 | 60 |
209
- | off | — | on | off | 4 | 1,948 | 38 |
210
- | off | — | on | on | 5 | 3,018 | 60 |
211
- | on | `.pi/slate.json` six | off | off | 4 | 3,879 | 58 |
212
- | on | `.pi/slate.json` six | off | on | 5 | 4,949 | 80 |
213
- | on | `.pi/slate.json` six | on | off | 5 | 3,898 | 58 |
214
- | on | `.pi/slate.json` six | on | on | 6 | 4,968 | 80 |
215
- | on | all 9 shipped | off | off | 4 | 4,434 | 61 |
216
- | on | all 9 shipped | off | on | 5 | 5,504 | 83 |
217
- | on | all 9 shipped | on | off | 5 | 4,453 | 61 |
218
- | on | all 9 shipped | on | on | 6 | 5,523 | 83 |
210
+ | off | — | off | off | 4 | 2,701 | 46 |
211
+ | off | — | off | on | 5 | 3,771 | 68 |
212
+ | off | — | on | off | 5 | 2,720 | 46 |
213
+ | off | — | on | on | 6 | 3,790 | 68 |
214
+ | on | `.pi/slate.json` six | off | off | 5 | 4,731 | 67 |
215
+ | on | `.pi/slate.json` six | off | on | 6 | 5,801 | 89 |
216
+ | on | `.pi/slate.json` six | on | off | 6 | 4,750 | 67 |
217
+ | on | `.pi/slate.json` six | on | on | 7 | 5,820 | 89 |
218
+ | on | all 9 shipped | off | off | 5 | 5,286 | 70 |
219
+ | on | all 9 shipped | off | on | 6 | 6,356 | 92 |
220
+ | on | all 9 shipped | on | off | 6 | 5,305 | 70 |
221
+ | on | all 9 shipped | on | on | 7 | 6,375 | 92 |
219
222
 
220
223
  An untrusted project reads the first row whatever its `slate.json`
221
224
  says: no project config is loaded, so no optional rule renders. Line
@@ -231,9 +234,9 @@ path:
231
234
 
232
235
  - The routing rule is a live table with ONE ROW PER ROUTABLE MODEL,
233
236
  so what renders is the models you CONFIGURE — not the nine Slate
234
- ships profiles for. In this snapshot it is 1,950 portable
235
- characters and adds 20 doctrine lines for six configured models;
236
- for all nine it is 2,505 characters and adds 23 lines. The six
237
+ ships profiles for. In this snapshot it is 2,030 portable
238
+ characters and adds 21 doctrine lines for six configured models;
239
+ for all nine it is 2,585 characters and adds 24 lines. The six
237
240
  model rows are 146–181 characters; all nine are 146–183. The
238
241
  legend adds a one-off clause per marker it has to explain, so
239
242
  growth is not only the sum of new rows.
@@ -247,17 +250,19 @@ worker extensions and support verification decisions:
247
250
 
248
251
  | basis | models | worker extensions | paths | portable | lines | rough tokens |
249
252
  | --- | ---: | --- | ---: | ---: | ---: | ---: |
250
- | current `.pi/slate.json` dogfood config | 6 | real 2 units / 4 tools | 6 | 5,884 | 90 | ≈1,471 |
251
- | stable maximal fixture | 9 | synthetic 2 units / 4 tools, every rendered field at its cap | 6 | 6,870 | 93 | ≈1,718 |
253
+ | current `.pi/slate.json` dogfood config | 6 | pinned-package 2 units / 4 tools | 7 | 6,736 | 99 | ≈1,684 |
254
+ | stable maximal fixture | 9 | synthetic 2 units / 4 tools, every rendered field at its cap | 7 | 7,722 | 102 | ≈1,931 |
255
+ | follow-up-issues maximal fixture | 9 | synthetic 2 units / 4 tools, every rendered field at its cap | 7 | 7,800 | 103 | ≈1,950 |
252
256
 
253
257
  The dogfood row uses `workflow.draftPRs: true`, `writing.check: true`, and the
254
258
  six models in `.pi/slate.json`. Its extension basis is
255
259
  `pi-smart-fetch@0.3.12` plus `pi-web-search@1.3.1`. Those packages resolve to two
256
260
  units and four tools. Their worker-extension rule is 916 portable characters /
257
- 11 lines. Raw size remains symbolic: `portable + 6 × length(installed docs
261
+ 11 lines. Raw size remains symbolic: `portable + 7 × length(installed docs
258
262
  directory)`. No maintainer checkout path belongs in this shipped document.
259
263
 
260
264
  The stable maximal row uses all nine shipped profiles, draft PRs, and writing.
265
+ The follow-up-issues row uses the same basis with `workflow.followUpIssues: true`.
261
266
  Its direct post-resolution worker fixture has two units and four tools. Unit
262
267
  labels are 128 characters. Tool names are 64 characters. Descriptions are 140
263
268
  characters.
@@ -267,24 +272,67 @@ independent of installed extension labels, descriptions, versions and other
267
272
  prose. The worker rule measures 1,347 characters against its 1,600-character
268
273
  verification budget.
269
274
 
270
- `doctrine-budget` caps that representative maximal doctrine at 7,200 portable
271
- characters. The measured 6,870 leaves 330 characters, or 4.6 percent of the
272
- 7,200-character bound. A positive control adds one capped tool
273
- and three copies of the largest measured model row. It measures 7,634 portable
274
- characters and exceeds the bound by 434. These figures are verification budgets,
275
- never runtime limits. Arbitrary user extension rosters can exceed them.
276
-
277
- The exact values are maintenance tripwires. A deliberate doctrine wording,
278
- requirement roster, renderer cap, or fixture change requires a fresh render
279
- through `doctrine-budget`. Update its exact expectations and every published
280
- figure in the same commit. Keep the positive control steps unchanged unless the
281
- fixture design itself changes.
282
-
283
- Against a 256,000-token context budget, these blocks remain small. At four
284
- characters per token as a rough estimate, the shipped-rule table ranges from
285
- about 482 tokens to about 1,381 tokens. The current dogfood basis is about 1,471
286
- tokens. The representative maximum is about 1,718 tokens, or 0.67 percent of the
287
- default budget.
275
+ The reserve policy is: every doctrine upper bound must exceed its current
276
+ measurement by at least five percent, with a required character-bound raise
277
+ rounded up to the next hundred characters. Existing bounds stay in place when
278
+ they already satisfy the policy. Line bounds use the same five-percent rule and
279
+ the next whole line.
280
+
281
+ Two guards have different jobs. An exact pinned literal is the sensitive guard.
282
+ It fails for every size change in its rendered fixture, including one character.
283
+ The maximal fixtures cover draft pull requests enabled, draft pull requests
284
+ disabled, and follow-up issues enabled. Each budget term also has one enforced
285
+ bound. The bound is a coarse ceiling that stops unbounded growth over time. A
286
+ bound close to a fixture adds friction but no detection, because its exact
287
+ literal already detects the change. Real reserve therefore weakens nothing.
288
+
289
+ A doctrine change updates the exact literal and every published measurement. A
290
+ maintainer revisits a bound only when the reserve policy requires it. The check
291
+ applies the five-percent rule to every upper bound, so this decision is auditable.
292
+
293
+ | budget term | current | enforced bound | current reserve |
294
+ | --- | ---: | ---: | ---: |
295
+ | routing rule characters | 2,585 | 4,000 | 1,415 |
296
+ | routing rule lines | 25 | 34 | 9 |
297
+ | routing fixed prose | 1,110 | 1,500 | 390 |
298
+ | largest model row | 183 | 300 | 117 |
299
+ | router-on doctrine | 5,286 | 6,500 | 1,214 |
300
+ | writing-only doctrine | 3,771 | 5,600 | 1,829 |
301
+ | writing plus router | 6,356 | 6,700 | 344 |
302
+ | writing plus extensions | 4,026 | 6,000 | 1,974 |
303
+ | writing plus router and extensions | 6,611 | 7,000 | 389 |
304
+ | maximal doctrine, draft PRs enabled | 7,722 | 8,200 | 478 |
305
+ | maximal doctrine, draft PRs disabled | 7,703 | 8,200 | 497 |
306
+ | maximal doctrine, follow-up issues enabled | 7,800 | 8,200 | 400 |
307
+ | capped worker rule | 1,347 | 1,600 | 253 |
308
+ | writing rule characters | 1,070 | 1,150 | 80 |
309
+ | writing rule lines | 23 | 25 | 2 |
310
+
311
+ The four changed bound decisions record the user's choices. The fuller
312
+ Option 3 text puts the enforcement trigger, class confirmation, presentation
313
+ order, and two previously unstated gates in the injected prompt. Writing plus
314
+ router moved from 6,500 to 6,700. Writing plus router and extensions moved from
315
+ 6,800 to 6,900. The maximal family moved from 7,900 to 8,200. Writing plus router
316
+ and extensions then moved from 6,900 to 7,000 with user approval so rule 8 could
317
+ keep the skip-the-read clause that rules 9 and 10 also carry. A redundant read of
318
+ the track workflow document costs far more than the clause.
319
+
320
+ The positive control adds one capped tool and four copies of the largest
321
+ measured model row. It measures 8,670 portable characters and exceeds the
322
+ 8,200-character maximal bound by 470. That margin remains larger than the
323
+ 184-character maximum model-row growth and the 212-character capped tool growth.
324
+ The raised bound does not blunt the positive control.
325
+
326
+ These figures are verification budgets, never runtime limits. Arbitrary user
327
+ extension rosters can exceed them. Keep the positive-control steps unchanged
328
+ unless the fixture design itself changes.
329
+
330
+ Against a 256,000-token context budget, these blocks remain small. The rough
331
+ estimate divides each measured portable-character render by four and rounds to
332
+ the nearest whole token. The shipped-rule table ranges from about 675 tokens to
333
+ about 1,594 tokens. The current dogfood basis is about 1,684 tokens. The stable
334
+ representative maximum is about 1,931 tokens. The follow-up-issues maximum is
335
+ about 1,950 tokens, or 0.76 percent of the default budget.
288
336
 
289
337
  No tokenizer was run, and tables are denser than prose. The block is re-sent on every request rather than paid once. These figures show how
290
338
  much headroom Slate consumes before conversation content.
@@ -381,7 +429,9 @@ long-context rates listed above.
381
429
 
382
430
  With action-level routing on, this override also settles a warning
383
431
  you will otherwise see once per session for each configured
384
- `gpt-5.6-*` model: Slate's own profile for those models records a
432
+ `gpt-5.6-*` model during resolution. Dispatch-time price divergence warnings
433
+ are conditional and can repeat on later dispatches. Slate's own profile for those
434
+ models records a
385
435
  1,050,000-token window, pi's stock registry reports 272,000, and the
386
436
  router reports that divergence without adjudicating it (routing uses
387
437
  the registry figure). Raising the registry window removes the
@@ -312,19 +312,19 @@ the load-on-demand discipline the extension itself prescribes:
312
312
 
313
313
  - **Tier 1 — always loaded.** The doctrine in `mode.ts` is the operational
314
314
  distillation of P1–P10, the review discipline defined in
315
- `review-rules.md`, and a pointer to the track-based workflow protocol
316
- that ships with this package (`track-workflow.md` in this directory),
315
+ `review-rules.md`, and a pointer to the class-scaled track workflow
316
+ protocol that ships with this package (`track-workflow.md` in this directory),
317
317
  appended to the system prompt every turn while orchestrator mode is
318
318
  on. Sizing it matters to the tiering argument, so it is measured
319
- rather than asserted: with both conditional tail rules and
320
- `workflow.draftPRs` off, the block is **1,929 portable characters /
321
- 38 lines**, and configuring the router for six models roughly
322
- DOUBLES it, to **3,870** on that same basis — the routing rule
319
+ rather than asserted: with every conditional tail rule off and
320
+ `workflow.draftPRs` off, the block is **2,701 portable characters /
321
+ 46 lines**, and configuring the router for six models nearly
322
+ doubles it, to **4,731** on that same basis — the routing rule
323
323
  renders one table row per routable model. (Portable characters, and
324
324
  the rest of the configurations, are defined and tabulated in
325
325
  `context-budget.md`; the raw count also depends on where the package
326
326
  is installed, because the doctrine embeds absolute doc paths.) At a
327
- rough 4 characters per token that is order-500 tokens, order-1,000
327
+ rough 4 characters per token that is order-700 tokens, order-1,200
328
328
  with the router on — so the original "a few hundred tokens" holds
329
329
  only for the fixed rules, and a configured router buys its table with
330
330
  a real slice of every request. It still covers everything routine
@@ -279,6 +279,7 @@ warning, but removal is not the ADD remedy in condition 2. Condition
279
279
  | pi has no usable credentials configured for it | dropped | "has no usable credentials configured in pi. Slate drops it from routing." | configuration fault |
280
280
  | every entry is dropped | router turns OFF | "survived validation. The warnings above name each dropped entry" | configuration fault |
281
281
  | no usable input price exists for today's date | kept, ordered last | "Slate cannot compare its cost with the other models." | model data note |
282
+ | a profile price is negative or non-finite | kept, ordered last or unavailable | "has invalid input price data" or "has invalid output price data" | model data note |
282
283
  | no usable effort ladder exists in the profile | kept | "Such a level passes through to pi, which clamps it" | model data note |
283
284
  | profile context window differs from the registry window | kept, registry value used | "differs between two sources" | model data note |
284
285
  | the first profile names unknown routing-critical fields | kept | "picks models from a research table shipped inside slate" | model data note |
@@ -288,20 +289,30 @@ warning, but removal is not the ADD remedy in condition 2. Condition
288
289
  | every configured candidate is marked non-preferred | cheapest candidate becomes the base | "profiles mark every configured model as one it must never pick by itself" | configuration fault |
289
290
  | resolution throws | router turns OFF | "routing is disabled. The router could not resolve its model list" | configuration fault |
290
291
 
291
- Every warning is emitted at most once per session. The complete
292
- resolution retains every warning regardless of display filtering.
293
- With `router.showWarnings: true`, every warning reaches the same
294
- channel as the other config warnings: a UI notification when a UI is
295
- attached, the console otherwise. With the default `false`, every
296
- configuration fault still reaches that channel and model data notes
297
- do not.
298
-
299
- When at least one note is hidden, Slate emits one discoverability
300
- line after resolution. It gives the hidden warning count, names
301
- `router.showWarnings`, and says that a hidden warning can affect
302
- which model runs an action. The count is warnings, not physical
303
- display lines: one warning can wrap across several lines. No
304
- discoverability line appears when nothing was hidden.
292
+ Each resolution warning is deduplicated by a condition key and retained in the
293
+ frozen resolution regardless of display filtering. With `router.showWarnings:
294
+ true`, every resolution warning reaches the normal UI notification or console
295
+ channel. With the default `false`, every configuration fault remains visible
296
+ and model data notes are hidden.
297
+
298
+ When resolution hides at least one note, Slate emits one discoverability line.
299
+ It gives the hidden warning count, names `router.showWarnings`, and says that a
300
+ hidden warning can affect model selection. The count is warnings, not physical
301
+ display lines. The line appears at most once per session.
302
+
303
+ Dispatch-time warnings are separate. Context substitution, effort evidence
304
+ gaps, long-context billing, failover window notices, and the model-visible
305
+ registry-price divergence warning are evaluated for each planned action. They
306
+ can repeat on later dispatches. The divergence warning is hardened and capped
307
+ through the router sanitizer. It omits exact rates and is not deduplicated.
308
+
309
+ The exact-rate companion uses the class-aware router sink as a model data note.
310
+ It is hidden by default and shown when `router.showWarnings` is true. Its
311
+ condition key includes the model, date, and differing rates, so identical live
312
+ evidence reports once while changed evidence reports again. It passes through
313
+ the same control stripping, citation cleanup, field caps, and whole-message cap
314
+ as resolution warnings. A hidden dispatch-time note can trigger the one-time
315
+ discoverability line only when resolution did not already trigger it.
305
316
 
306
317
  Half a list is still a routing policy, so partial drops leave the
307
318
  router ON. Nothing surviving is not a policy: the router turns OFF
@@ -485,17 +496,18 @@ model's ladder is unreadable.
485
496
  `(unmeasured)` where that applies. `live=<model> (failover)` means
486
497
  a held fallback currently overrides the nominal base for the live
487
498
  session.
488
- - **At the session level:** the resolution's own warnings, as UI
489
- notifications (console when no UI is attached).
499
+ - **At the session level:** configuration faults and model data notes enabled by
500
+ `router.showWarnings`, as UI notifications or console output. The default
501
+ instead shows one discoverability line when it hides notes.
490
502
  - **In the orchestrator's own system prompt, every turn:** the
491
503
  doctrine gains a routing rule — a table with one row per routable
492
504
  model, plus the rules for reading it. This is the surface you do
493
- not see, and it is the router's standing cost: 1,950 characters /
494
- 20 added doctrine lines for six configured models, 2,505 / 23 for
505
+ not see, and it is the router's standing cost: 2,030 characters /
506
+ 21 added doctrine lines for six configured models, 2,585 / 24 for
495
507
  all nine. In the current snapshot a model row costs 146–183
496
508
  characters, plus a one-off legend clause for each marker it
497
509
  introduces. Configuring the router roughly doubles Slate's
498
- always-loaded block (1,929 characters with routing and writing
510
+ always-loaded block (2,701 characters with routing and writing
499
511
  off). Those are PORTABLE characters — the
500
512
  doctrine with each occurrence of the installed `docs/` directory
501
513
  removed, filenames kept — because the doctrine embeds absolute doc
@@ -506,10 +518,12 @@ model's ladder is unreadable.
506
518
 
507
519
  ## Expected first-session warnings
508
520
 
509
- The stock count below is a render over this repository's
510
- `.pi/slate.json`, the shipped profile table, and the provider data
511
- inside the pinned pi 0.83.0 package. It does not read the live model
512
- registry. The packaged OpenAI data at
521
+ The stock count below comes from executing `resolveModelRouter` with this
522
+ repository's sanitized `.pi/slate.json`, the shipped profile table, and a real
523
+ `ModelRuntime` from the pinned pi 0.83.0 package. The runtime used
524
+ `modelsPath: null` and `allowModelNetwork: false`, so no local registry override
525
+ or network refresh could affect it. Dummy OpenAI and Anthropic credentials made
526
+ the configured-auth premise explicit. The packaged OpenAI data at
513
527
  `@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai/dist/providers/data/openai.json`
514
528
  reports a **272,000-token** context window for
515
529
  `openai/gpt-5.6-luna`, `openai/gpt-5.6-terra` and
@@ -528,8 +542,8 @@ data notes**. The default `router.showWarnings: false` therefore
528
542
  shows **0 of those warnings** and one discoverability line. That line
529
543
  begins `slate: there are 11 hidden warnings in the model router.`
530
544
  Enabling the option shows all 11 warnings and no discoverability
531
- line. Each warning fires at most once per session, and the resolution
532
- is frozen after the first consultation.
545
+ line. Each resolution warning fires at most once per session, and the
546
+ resolution is frozen after the first consultation.
533
547
 
534
548
  | warnings | class | condition keys | why | shown by default |
535
549
  | --- | --- | --- | --- | --- |
@@ -576,9 +590,31 @@ A hidden warning can still alter which model runs an action. Context-
576
590
  window divergence is the concrete case, and the stock registry emits
577
591
  three such hidden notes for this list. Routing uses the registry
578
592
  window, and that value drives the context-window substitution guard.
579
- `router.showWarnings` changes display only; it does not change the
593
+ `router.showWarnings` changes display only. It does not change the
580
594
  resolution or the guard input.
581
595
 
596
+ ## Thread choice after routing
597
+
598
+ Routing resolves the model and effort for one action. Thread choice then uses that applied pair when it evaluates a continuation.
599
+
600
+ A model or effort change makes the existing prefix cold. Current cache-write prices are 12.5 times the matching cache-read prices.
601
+
602
+ The `thread` tool owns the economic comparison. The orchestrator still decides whether named episodes can replace the live transcript.
603
+
604
+ On a continuation with `threadChoice.act: true`, `freshContext` is required. With acting off, it is optional. Every supplied valid value reaches the planner on either continuation path. An empty array refuses a restart. A non-empty list of existing episode ids permits one and seeds the new thread.
605
+
606
+ Whenever `freshContext` is supplied, malformed input or an unknown episode is a tool error before creation or mutation. A valid value on a creation call is accepted and unused because creation has no planner.
607
+
608
+ The planner can return `continue`, `fresh`, `abstain`, or `refused`. Only `fresh` can trigger a restart when `threadChoice.act` is enabled.
609
+
610
+ Acting defaults off. The default `threadChoice.report` setting returns the verdict without moving the action.
611
+
612
+ The planner uses current price rows, recorded prefix size, episode size, and dated retention evidence. It abstains when required price or size evidence is missing.
613
+
614
+ A stale retention row grants no retention window. Missing retention or elapsed time does not prove expiry, so an unchanged route remains warm.
615
+
616
+ The complete mechanism and evidence limits are in [Thread cache cost](thread-cache-cost.md).
617
+
582
618
  ## What the router does NOT enforce
583
619
 
584
620
  Two obligations carried by the shipped profile data and stated in
@@ -724,11 +760,9 @@ concern; a green suite says nothing about it.
724
760
  the shipped profile for the plan target when pi's registry can serve
725
761
  that model. The `model` argument itself is passed through
726
762
  byte-for-byte, so a malformed spec still produces pi's own error.
727
- - **A mid-thread model switch costs the prompt cache.** A long
728
- thread runs overwhelmingly on cache reads; routing a small action
729
- to a cheaper model on a cold cache can cost more than staying on
730
- the warm one. The router compares base rates, not the cache state
731
- of your thread.
763
+ - **A mid-thread route can cost the prompt cache.** A model or effort
764
+ change makes the prefix cold. The thread-choice planner prices that
765
+ cache state after routing. See [Thread choice after routing](#thread-choice-after-routing).
732
766
  - **The window guard can move an action to a model you did not
733
767
  choose** (it warns, and names what it replaced), and it never
734
768
  blocks: if nothing listed is wider, the action runs and pi
@@ -9,21 +9,36 @@ workflow-log lifecycle in that mode is owned by track-workflow.md
9
9
 
10
10
  ## Creation
11
11
 
12
- The umbrella draft PR is created once the design has passed the user
13
- design review and the adversarial review, BEFORE implementation:
12
+ For every change with a high-level design, draft the PR description
13
+ before the pre-implementation presentation. Present the draft alongside
14
+ the high-level design, so one approval covers both. The description has
15
+ no separate approval gate.
16
+
17
+ The umbrella draft PR is created after the gate required by the
18
+ confirmed change class passes and BEFORE implementation. For a complex
19
+ or risky change, that gate is the final high-level design approval after
20
+ the adversarial review. For a medium change, it is the high-level design
21
+ approval. A trivial change has no high-level design gate, so the PR is
22
+ created after the user confirms the class and before implementation:
14
23
 
15
24
  - Created as a DRAFT, based on the repository's default development
16
25
  branch.
17
26
  - If the working branch has no diff against the base yet, land a
18
27
  bootstrap empty commit so the PR can be created.
19
- - At creation, the research log's content folds into the PR
20
- description — Key decisions, Risks, and Open questions feed the
21
- corresponding Planned-changes subsections, and the design review and
22
- adversarial review verdict lines land in Risks & accepted
23
- trade-offs — and the log file is deleted. Decisions made after PR
24
- creation are appended to the PR description directly.
25
- - The user approves the description (and the track split — see
26
- track-workflow.md) before implementation starts.
28
+ - At creation for a medium, complex, or risky change, the research
29
+ log's Planned changes content folds into the PR description. Key
30
+ decisions, Risks, and Open questions feed the corresponding
31
+ Planned-changes subsections. The applicable design review verdict
32
+ lines land in Risks & accepted trade-offs. The adversarial review
33
+ verdict line lands there only when that review ran; a medium change
34
+ carries the design verdict lines alone.
35
+ - For a trivial change, the initial request supplies Motivation. The
36
+ confirmed class proposal and intended fix supply Planned changes. If
37
+ a log exists, its relevant decisions and Open Questions also fold
38
+ into the description.
39
+ - The research log is retained until delivery, and its Decision Log
40
+ keeps appending during implementation. track-workflow.md § Research
41
+ log owns the full lifecycle.
27
42
 
28
43
  ## Description rules
29
44
 
@@ -46,8 +61,13 @@ Subsections activate when their content exists:
46
61
  - **Key decisions** — chosen vs rejected; preempts "why not X?" review
47
62
  comments.
48
63
  - **Out of scope** — explicit non-goals.
49
- - **Risks & accepted trade-offs** — including the design review and
50
- adversarial review verdict lines.
64
+ - **Risks & accepted trade-offs** — including the applicable design
65
+ review verdict lines, and the adversarial review verdict line only
66
+ when that review ran.
67
+ - **Suggestions** — a one-line index per suggestion: identifier,
68
+ location, and one-line summary. The standalone text lives in the
69
+ final report to the user or in a tracker issue when
70
+ `workflow.followUpIssues` enables that prompt.
51
71
  - **Verification approach** — 1–2 lines.
52
72
 
53
73
  "Deep enough" test: a reviewer who knows the codebase but not this
@@ -113,7 +133,10 @@ executed in order:
113
133
  review) is completed or explicitly user-waived — flipping never
114
134
  discards a pending review.
115
135
  - All commits landed since the last user-approved gate are presented
116
- to the user.
136
+ to the user. For a trivial change, present the description here
137
+ because no design gate presented it before implementation.
138
+ - Every remaining suggestion is reported to the user, and the
139
+ Suggestions index is present in the description.
117
140
  - Strip the whole Tracks section from the description, whatever its
118
141
  form — the table for multi-track changes or the "N/A (single-track)"
119
142
  placeholder — plus any notes under it. Track numbers are ephemeral
@@ -153,6 +176,9 @@ of any recorded size exception.
153
176
 
154
177
  ## After the merge
155
178
 
179
+ Complete the research-log delivery cleanup defined by
180
+ track-workflow.md § Research log.
181
+
156
182
  Any cleanup a layered peer-review process requires (closing its review
157
183
  PRs, deleting its pinned branches) is an agent duty, executed when the
158
184
  user reports the merge or a later session detects it, per that