ytdb-slate 0.5.0 → 0.6.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 +17 -2
- package/docs/context-budget.md +64 -40
- package/docs/design-principles.md +3 -1
- package/docs/model-failover.md +5 -5
- package/docs/model-routing.md +189 -52
- package/docs/pr-publishing.md +40 -4
- package/docs/review-rules.md +66 -13
- package/docs/track-workflow.md +29 -0
- package/docs/writing-guidance.md +322 -0
- package/extension/index.ts +7 -4
- package/extension/mode.ts +126 -2
- package/extension/model-router.ts +14 -11
- package/extension/paths.ts +13 -8
- package/extension/route.ts +39 -38
- package/extension/state.ts +22 -15
- package/extension/threads.ts +46 -45
- package/extension/tools.ts +40 -33
- package/extension/worker.ts +15 -2
- package/extension/writing-check.mjs +1061 -0
- package/extension/writing.ts +117 -0
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# ytdb-slate
|
|
2
2
|
|
|
3
|
+
[](https://github.com/JetBrains/ytdb-slate/actions/workflows/tier-1.yml)
|
|
4
|
+
|
|
3
5
|
Slate is a thread-weaving orchestration extension for the [pi coding agent](https://pi.dev).
|
|
4
6
|
|
|
5
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.
|
|
@@ -53,10 +55,20 @@ Slate can ride through model API outages. The opt-in `modelFailover` map in `.pi
|
|
|
53
55
|
|
|
54
56
|
## Action-level model routing
|
|
55
57
|
|
|
56
|
-
Slate can pick the model and the effort level per dispatched action rather than running every action on whatever model the orchestrator happens to be on. `router.models` in `.pi/slate.json` (trusted projects only) is a closed list of `provider/id` specs
|
|
58
|
+
Slate can pick the model and the effort level per dispatched action rather than running every action on whatever model the orchestrator happens to be on. `router.models` in `.pi/slate.json` (trusted projects only) is a closed list of `provider/id` specs. Each action uses its `model` and `effort` arguments when present. Otherwise, it uses the thread's base model and a level DERIVED for the model it routes to. That level is the lowest one with a capability measurement, never a fixed default. The list is empty by default. An empty list disables candidate-list enforcement, base seeding, effort derivation, and the router's window, billing, and substitution paths. Per-action `model` and `effort` arguments stay active on both router states. Slate always checks an explicit effort against pi's vocabulary, and against the ladder of the model it routes to whenever it has that model's profile data. An action that omits `model` plans for the thread's pre-router pin, and a thread with no pin gives it no plan target. An action that omits `effort` resolves no level. In both cases the worker session returns to what it opened on. [`docs/model-routing.md`](docs/model-routing.md) holds the full contract, including the known cases where the model or level that runs differs. Only models Slate ships a benchmark profile for can be routed to; entries pi cannot serve, or has no profile for, are dropped with a warning naming them.
|
|
57
59
|
|
|
58
60
|
What the code enforces is deliberately narrower than what the routing data advises. The dispatch guards refuse an unlisted model, a level off the target model's ladder and a level the provider rejects outright; they warn about — and mark — an unmeasured level, refusing it only when `router.allowUnmeasuredEffort` is `false`; and the context-window guard substitutes a wider listed model instead of ever blocking an action. The obligations that come from the profile data itself — keeping review and gate actions on measured levels, and honoring a REFUSE such as `anthropic/claude-fable-5`'s for zero-data-retention work — are stated in the doctrine for the orchestrator to honor; they are **not** code-enforced guards. Full semantics: the `router.*` rows in [Configuration](#configuration) and the shipped [`docs/model-routing.md`](docs/model-routing.md) — if they disagree, that document wins.
|
|
59
61
|
|
|
62
|
+
## Optional writing guidance
|
|
63
|
+
|
|
64
|
+
Set `writing.check` to `true` to add writing guidance and human-only telemetry. In orchestrator mode, Slate adds a doctrine rule that states the convention. It also adds a status value such as `writing 1/4`. This value means that one of four measured prose turns had a fail-level finding. Each worker gets a shorter preamble reminder about sentence length, semicolons, and contractions.
|
|
65
|
+
|
|
66
|
+
The option has prompt cost. Each orchestrator system prompt contains the doctrine rule. You pay for this text on every turn. Each worker session also gets a gated preamble addition. Both additions are absent when the option is off. The worker preamble then stays byte-identical to its pre-feature form.
|
|
67
|
+
|
|
68
|
+
> **No conformance claim:** This is a dictionary-free proxy. It embeds no controlled vocabulary and claims no ASD-STE100 conformance.
|
|
69
|
+
|
|
70
|
+
The shipped CLI checks plain files, JSONL records, and unified diffs. See [`docs/writing-guidance.md`](docs/writing-guidance.md) for its rules, limits, output, and command examples.
|
|
71
|
+
|
|
60
72
|
## Install
|
|
61
73
|
|
|
62
74
|
Install into a project (pinned, project-scoped — recorded in `.pi/settings.json`):
|
|
@@ -82,11 +94,12 @@ Optional config file: `slate.json` in the project's pi config dir (`.pi/slate.js
|
|
|
82
94
|
| `orchestratorPromptDocs` | string[] | `[]` | Project markdown files (paths relative to the project root) whose **contents** are appended to the orchestrator system prompt. |
|
|
83
95
|
| `workerPromptDocs` | string[] | `[]` | Project markdown files whose **contents** are appended to every worker-thread system prompt. |
|
|
84
96
|
| `workflow.draftPRs` | boolean | `false` | Enable umbrella draft-PR publishing for tracks. |
|
|
97
|
+
| `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). |
|
|
85
98
|
| `doctrineExtraPath` | string | — | Project markdown whose **content** is appended to the orchestrator doctrine (project-specific workflow additions). |
|
|
86
99
|
| `reviewPerspectivesPath` | string | — | Project review charters, each declaring its own finding-ID prefix. The doctrine references this **path**; the orchestrator reads the file alongside the shipped review rules. |
|
|
87
100
|
| `modelFailover` | object (string → string) | — (empty, failover off) | Map of `provider/id` → equal-quality alternative model; on a model API failure the affected site retries once on the mapped model (worker/orchestrator sites only after pi's own retries are exhausted; episode compression has no pi retry loop). Read once at session start — see [`docs/model-failover.md`](docs/model-failover.md). |
|
|
88
101
|
| `preserveGlobalModelDefault` | boolean | `true` | Put pi's global model defaults (`defaultProvider`, `defaultModel`, `defaultThinkingLevel`) back after a Slate-initiated model switch — orchestrator failover and handoff adoption — so the switch stays session-scoped. `false` turns that restore off at both sites (the switches themselves are unaffected), leaving each one persisted in pi's global settings. Best-effort: Slate reverts only a value its own switch produced, and can stand down or give up — limits in [`docs/model-failover.md`](docs/model-failover.md). |
|
|
89
|
-
| `router.models` | string[] | `[]` (empty — routing off) | Closed list of models (`provider/id`) an action may
|
|
102
|
+
| `router.models` | string[] | `[]` (empty — routing off) | Closed list of models (`provider/id`) an action may use. Empty or absent means the router is OFF. Slate enforces no candidate list, seeds no thread base, derives no effort level, and runs no router-only window, billing, or substitution path. Per-action `model` and `effort` arguments stay active on both router states. Slate still checks explicit effort against pi's vocabulary and the routed model's ladder when it has that model's profile data. An action that omits `model` plans for the thread's pre-router pin, and a thread with no pin gives it no plan target. An action that omits `effort` resolves no level. In both cases the worker session returns to what it opened on. The known cases where the model or level that runs differs are listed in [`docs/model-routing.md`](docs/model-routing.md). A listed model becomes routable only if Slate has its benchmark profile, pi registers it, and credentials are configured. Slate drops other entries with a warning. If all entries drop, routing turns off with one summary warning. Slate reads and resolves the list once per session. See [`docs/model-routing.md`](docs/model-routing.md). |
|
|
90
103
|
| `router.allowUnmeasuredEffort` | boolean | `true` | What happens when an explicitly requested effort level is on the target model's ladder but has no capability measurement in Slate's profiles: `true` dispatches it with a ⚠ notice and marks the episode `(unmeasured level)`; `false` refuses the dispatch. Either way a level that is off the model's ladder, or one the provider rejects outright, is refused, and a level Slate DERIVES for an action is always a measured one. |
|
|
91
104
|
|
|
92
105
|
Example `.pi/slate.json` (the `docs/agents/...` paths are placeholders — point them at markdown files that actually exist in **your** project):
|
|
@@ -99,6 +112,7 @@ Example `.pi/slate.json` (the `docs/agents/...` paths are placeholders — point
|
|
|
99
112
|
"orchestratorPromptDocs": ["docs/agents/orchestrator-guidelines.md"],
|
|
100
113
|
"workerPromptDocs": ["docs/agents/thread-guidelines.md"],
|
|
101
114
|
"workflow": { "draftPRs": true },
|
|
115
|
+
"writing": { "check": true },
|
|
102
116
|
"doctrineExtraPath": "docs/agents/workflow-additions.md",
|
|
103
117
|
"reviewPerspectivesPath": "docs/agents/review-perspectives.md",
|
|
104
118
|
"modelFailover": { "anthropic/claude-sonnet-5": "openai/gpt-5.2" },
|
|
@@ -143,6 +157,7 @@ In orchestrator mode, Slate appends a short **doctrine** (a block of numbered ru
|
|
|
143
157
|
- `docs/design-principles.md` — Slate's own design rationale
|
|
144
158
|
- `docs/model-failover.md` — the opt-in `modelFailover` map (**reference documentation** — unlike the entries above it is not workflow doctrine and is not cited by the doctrine)
|
|
145
159
|
- `docs/context-budget.md` — the orchestrator `contextBudget`: defaults, per-model overrides, the window clamp, and the pricing rationale (also **reference documentation**, not cited by the doctrine)
|
|
160
|
+
- `docs/writing-guidance.md` — the optional writing convention, `writing.check` behavior, status line, and checker CLI
|
|
146
161
|
- `docs/model-routing.md` — action-level model routing: the two `router` keys, how a model becomes routable, how an omitted effort level resolves, the dispatch guards, and the warnings a first routed session emits. Unlike the two entries above it **is** cited by the doctrine, by absolute path, but only while the routing rule renders — it is the extra embedded path that appears with the rule, the fourth normally and the fifth when `workflow.draftPRs` is also on, which the size arithmetic in `docs/context-budget.md` accounts for. The routable model list itself is never read from this file: the rule renders it live from the session's own resolution, because that set depends on your registry and credentials.
|
|
147
162
|
|
|
148
163
|
Project-specific additions layer on top — they extend, not replace, the shipped doctrine — via two distinct mechanisms:
|
package/docs/context-budget.md
CHANGED
|
@@ -129,69 +129,93 @@ you pay its input tokens every turn.
|
|
|
129
129
|
|
|
130
130
|
**Its size depends on where the package is installed.** That is not
|
|
131
131
|
obvious, and it catches anyone who measures: the doctrine cites the
|
|
132
|
-
shipped docs by ABSOLUTE path, so three to
|
|
133
|
-
|
|
132
|
+
shipped docs by ABSOLUTE path, so three to six full filesystem paths
|
|
133
|
+
are embedded in the block — three at minimum
|
|
134
134
|
(`track-workflow.md`, `review-rules.md`, `design-principles.md`),
|
|
135
135
|
plus `pr-publishing.md` when `workflow.draftPRs` is on, plus
|
|
136
|
-
`model-routing.md` when the routing rule renders
|
|
137
|
-
|
|
136
|
+
`model-routing.md` when the routing rule renders, plus
|
|
137
|
+
`writing-guidance.md` when `writing.check` is on. Every additional
|
|
138
|
+
character in the installed `docs/` directory therefore costs 3–6
|
|
138
139
|
characters of doctrine.
|
|
139
140
|
|
|
140
|
-
|
|
141
|
-
Slate's own automated
|
|
142
|
-
occurrence of the installed `docs/` DIRECTORY
|
|
143
|
-
filename. That makes them install-invariant by
|
|
144
|
-
|
|
145
|
-
58-character one, which give identical portable counts — and it
|
|
146
|
-
makes the arithmetic one multiplication:
|
|
141
|
+
The figures below are **portable characters**, defined exactly as
|
|
142
|
+
Slate's own automated `doctrine-budget` check defines them: the
|
|
143
|
+
block with each occurrence of the installed `docs/` DIRECTORY
|
|
144
|
+
removed, keeping the filename. That makes them install-invariant by
|
|
145
|
+
construction and makes the arithmetic one multiplication:
|
|
147
146
|
|
|
148
147
|
> rendered characters = portable + (embedded paths × length of your
|
|
149
148
|
> installed `docs/` directory)
|
|
150
149
|
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
`doctrine-budget` in `verification/resolver-checks.mjs` is the
|
|
151
|
+
definition of record for this convention and its enforced bounds.
|
|
152
|
+
This table is a measured snapshot, not a second authority. It uses
|
|
153
|
+
the same real shipped-profile fixture as that check; the six-model
|
|
154
|
+
rows use the six configured models in this repository's documented
|
|
155
|
+
example. Every row states its full basis, because router state,
|
|
156
|
+
model count, `draftPRs`, and `writing.check` each move the number:
|
|
153
157
|
|
|
154
|
-
| router | models | `draftPRs` | paths | portable | lines |
|
|
158
|
+
| router | models | `draftPRs` | `writing.check` | paths | portable | lines |
|
|
155
159
|
| --- | --- | --- | --- | --- | --- | --- |
|
|
156
|
-
| off | — | off | 3 | 1,929 | 38 |
|
|
157
|
-
| off | — | on | 4 |
|
|
158
|
-
|
|
|
159
|
-
|
|
|
160
|
-
| on |
|
|
161
|
-
| on |
|
|
160
|
+
| off | — | off | off | 3 | 1,929 | 38 |
|
|
161
|
+
| off | — | off | on | 4 | 2,683 | 53 |
|
|
162
|
+
| off | — | on | off | 4 | 1,948 | 38 |
|
|
163
|
+
| off | — | on | on | 5 | 2,702 | 53 |
|
|
164
|
+
| on | 6 configured | off | off | 4 | 3,879 | 58 |
|
|
165
|
+
| on | 6 configured | off | on | 5 | 4,633 | 73 |
|
|
166
|
+
| on | 6 configured | on | off | 5 | 3,898 | 58 |
|
|
167
|
+
| on | 6 configured | on | on | 6 | 4,652 | 73 |
|
|
168
|
+
| on | all 9 shipped | off | off | 4 | 4,434 | 61 |
|
|
169
|
+
| on | all 9 shipped | off | on | 5 | 5,188 | 76 |
|
|
170
|
+
| on | all 9 shipped | on | off | 5 | 4,453 | 61 |
|
|
171
|
+
| on | all 9 shipped | on | on | 6 | 5,207 | 76 |
|
|
162
172
|
|
|
163
173
|
An untrusted project reads the first row whatever its `slate.json`
|
|
164
|
-
says: no project config is loaded, so
|
|
165
|
-
|
|
166
|
-
|
|
174
|
+
says: no project config is loaded, so no optional rule renders. Line
|
|
175
|
+
counts do not vary with the install path. Enabling `draftPRs` costs
|
|
176
|
+
19 portable characters plus one embedded path. Enabling
|
|
177
|
+
`writing.check` costs 754 portable characters, adds 15 lines to the
|
|
178
|
+
whole doctrine, and adds one embedded path; considered alone, the
|
|
179
|
+
writing rule is 754 portable characters / 16 lines because its
|
|
180
|
+
leading newline becomes the separator when appended.
|
|
167
181
|
|
|
168
182
|
Two parts of the block grow with configuration rather than with the
|
|
169
183
|
path:
|
|
170
184
|
|
|
171
185
|
- The routing rule is a live table with ONE ROW PER ROUTABLE MODEL,
|
|
172
186
|
so what renders is the models you CONFIGURE — not the nine Slate
|
|
173
|
-
ships profiles for.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
first six average 171.
|
|
187
|
+
ships profiles for. In this snapshot it is 1,950 portable
|
|
188
|
+
characters and adds 20 doctrine lines for six configured models;
|
|
189
|
+
for all nine it is 2,505 characters and adds 23 lines. The six
|
|
190
|
+
model rows are 146–181 characters; all nine are 146–183. The
|
|
191
|
+
legend adds a one-off clause per marker it has to explain, so
|
|
192
|
+
growth is not only the sum of new rows.
|
|
180
193
|
- The worker-extension rule grows per whitelisted extension and per
|
|
181
194
|
tool that extension contributes, so it has no fixed size; one
|
|
182
195
|
extension contributing two tools measured 373 portable characters
|
|
183
196
|
/ 7 lines.
|
|
184
197
|
|
|
185
|
-
Against a 256,000-token budget none of this is material
|
|
186
|
-
|
|
187
|
-
table is denser than prose,
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
re-sent on every request rather than paid once. It is
|
|
192
|
-
because it is context Slate itself puts in front of the
|
|
193
|
-
every turn, and because it is the number to check before
|
|
194
|
-
the budget's headroom is all conversation.
|
|
198
|
+
Against a 256,000-token budget none of this is material. At four
|
|
199
|
+
characters per token as a rough estimate (no tokenizer was run, and
|
|
200
|
+
the routing table is denser than prose), the portable block ranges
|
|
201
|
+
from about 482 tokens at the baseline to about 1,302 with all nine
|
|
202
|
+
models, `draftPRs`, and writing enabled. That is close to half a
|
|
203
|
+
percent of the default budget at the largest measured configuration,
|
|
204
|
+
though it is re-sent on every request rather than paid once. It is
|
|
205
|
+
listed here because it is context Slate itself puts in front of the
|
|
206
|
+
model on every turn, and because it is the number to check before
|
|
207
|
+
assuming the budget's headroom is all conversation.
|
|
208
|
+
|
|
209
|
+
### Worker writing preamble
|
|
210
|
+
|
|
211
|
+
The doctrine table does not include the worker preamble. It belongs
|
|
212
|
+
to each worker session's system prompt, not to the orchestrator's
|
|
213
|
+
per-turn doctrine. The feature-off preamble is 226 bytes. With
|
|
214
|
+
`writing.check` on it is 384 bytes, a 158-byte increase (the
|
|
215
|
+
157-byte guidance plus its separating space). These are UTF-8 byte
|
|
216
|
+
counts; the current text is ASCII, so its character counts happen to
|
|
217
|
+
be identical. This separate figure states the worker-session cost
|
|
218
|
+
without presenting it as orchestrator doctrine.
|
|
195
219
|
|
|
196
220
|
## Worker threads: the routing window guard
|
|
197
221
|
|
|
@@ -194,7 +194,9 @@ context budget, also decide per dispatch whether a worker thread still
|
|
|
194
194
|
fits the model it is routed to, in which case the action moves to the
|
|
195
195
|
widest listed model rather than being blocked.
|
|
196
196
|
|
|
197
|
-
|
|
197
|
+
Candidate routing is OFF until `router.models` lists something — the
|
|
198
|
+
per-action `model` and `effort` arguments, and a fallback a failover
|
|
199
|
+
is holding, stay active either way. The feature is
|
|
198
200
|
built on a deliberate split that is worth holding onto when reasoning
|
|
199
201
|
about the architecture: the code ENFORCES less than the routing data
|
|
200
202
|
ADVISES. Dispatch guards refuse what is mechanically wrong — a model
|
package/docs/model-failover.md
CHANGED
|
@@ -89,11 +89,11 @@ Failover applies at three sites:
|
|
|
89
89
|
model and the failed action is re-prompted once with a continuation
|
|
90
90
|
nudge. The transcript is preserved; usage and cost accumulate across
|
|
91
91
|
both attempts. The thread stays on the mapped model while its
|
|
92
|
-
session is live
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
session is live, including across later model-less actions, and the
|
|
93
|
+
`threads` listing marks it `live=<model> (failover)`. The hold, the
|
|
94
|
+
marker and the retry inside the failing dispatch are cases M1, M6
|
|
95
|
+
and M2 of [Known cases where the model or level
|
|
96
|
+
differs](model-routing.md#known-cases-where-the-model-or-level-differs).
|
|
97
97
|
- **Episode compression.** The compression call is retried once with
|
|
98
98
|
the mapped compressor model — only if it is distinct from the
|
|
99
99
|
original, resolvable, and authed. If the retry also fails, the
|
package/docs/model-routing.md
CHANGED
|
@@ -6,14 +6,19 @@ list of models an action may run on; every dispatch then resolves to
|
|
|
6
6
|
one model from that list and one effort level from that model's own
|
|
7
7
|
ladder — chosen to be up to the task and no more, so the bill is
|
|
8
8
|
bounded by the action rather than by the session's current model.
|
|
9
|
-
The list is empty by default
|
|
10
|
-
model list is enforced, no thread gets a seeded base,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
The list is empty by default. With no entries the router is OFF: no
|
|
10
|
+
model list is enforced, no thread gets a seeded base, no effort level
|
|
11
|
+
is derived, and the router's window, billing and substitution paths
|
|
12
|
+
are inert. A dispatch that names no model then plans for the thread's
|
|
13
|
+
pre-router pin, and for no model at all when the thread has none: a
|
|
14
|
+
new worker opens on the host session's model, and a reused one stays
|
|
15
|
+
on the model it opened on. The per-action `model` and `effort`
|
|
16
|
+
arguments are honoured on both router states. What a dispatch plans
|
|
17
|
+
is not always what it runs; the cases known to bite are collected
|
|
18
|
+
under [Known cases where the model or level
|
|
19
|
+
differs](#known-cases-where-the-model-or-level-differs).
|
|
20
|
+
|
|
21
|
+
This document is reference documentation, not workflow doctrine.
|
|
17
22
|
|
|
18
23
|
A model can only be routed to if Slate ships a benchmark profile for
|
|
19
24
|
it. An entry with no profile is named in a warning and excluded —
|
|
@@ -26,36 +31,44 @@ from](#where-the-numbers-come-from-and-how-stale-they-can-be).
|
|
|
26
31
|
|
|
27
32
|
## What routing decides, per action
|
|
28
33
|
|
|
29
|
-
Two
|
|
34
|
+
Two route inputs are per dispatch — on a new thread and on a
|
|
30
35
|
continuation alike:
|
|
31
36
|
|
|
32
37
|
- **the model** — the `model` argument (`"provider/id"`), else the
|
|
33
|
-
thread's base model (router ON), else the thread's pre-router pin
|
|
34
|
-
|
|
38
|
+
thread's base model (router ON), else the thread's pre-router pin,
|
|
39
|
+
else no plan target at all, in which case the session returns to
|
|
40
|
+
the model it opened on (router OFF; M1, M3, M7);
|
|
35
41
|
- **the effort level** — the `effort` argument (one of pi's thinking
|
|
36
|
-
levels), else a level Slate resolves for the model
|
|
37
|
-
|
|
42
|
+
levels), else a level Slate resolves for the model it routes to
|
|
43
|
+
(router ON) or the level the worker session opened on (router
|
|
38
44
|
OFF). See [Effort levels](#effort-levels).
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
of them is "whatever the last action asked for":
|
|
46
|
+
The arguments themselves do not become thread defaults. An explicit
|
|
47
|
+
`effort` never governs a later action. An explicit `model` governs
|
|
48
|
+
only its own action too, outside M1, M3, M6 and M7. An omitted `effort`
|
|
49
|
+
is never "whatever the last action asked for":
|
|
45
50
|
|
|
46
51
|
- **router on** — the thread's base effort, re-validated, or a level
|
|
47
|
-
derived for the model
|
|
52
|
+
derived for the model it routes to (see [Effort
|
|
48
53
|
levels](#effort-levels)). A thread based on `luna@medium` whose
|
|
49
54
|
previous action ran at `max` runs the next one at `medium`.
|
|
50
|
-
- **router off** —
|
|
51
|
-
|
|
55
|
+
- **router off** — the worker session is put back to the level it
|
|
56
|
+
opened on, which is pi's own settings default
|
|
52
57
|
(`defaultThinkingLevel`, else pi's built-in default). Slate passes
|
|
53
58
|
that level explicitly at every open, so it is re-clamped against
|
|
54
59
|
the session's current model rather than inherited from the session
|
|
55
60
|
file.
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
The model axis carries live-session cases the effort axis does not,
|
|
63
|
+
M1 and M3 among them, and they predate action-level routing. The
|
|
64
|
+
effort axis shares only M7. All of them, with the code that
|
|
65
|
+
implements each, are under [Known cases where the model or level
|
|
66
|
+
differs](#known-cases-where-the-model-or-level-differs).
|
|
67
|
+
|
|
68
|
+
A thread's base model and base effort are separate planning state,
|
|
69
|
+
visible in the `threads` listing, and are never set from an action's
|
|
70
|
+
own route. A live failover marker, when present, identifies the
|
|
71
|
+
model that currently overrides that nominal base.
|
|
59
72
|
|
|
60
73
|
Two things can come back instead of a normal episode:
|
|
61
74
|
|
|
@@ -68,6 +81,120 @@ Two things can come back instead of a normal episode:
|
|
|
68
81
|
result — advisory: an evidence gap, a window substitution, a
|
|
69
82
|
long-context billing cliff, a re-seeded base. The action ran.
|
|
70
83
|
|
|
84
|
+
## Known cases where the model or level differs
|
|
85
|
+
|
|
86
|
+
The model and the level an action runs on are not always the ones it
|
|
87
|
+
asked for, or the ones its thread implies. The cases below are the
|
|
88
|
+
ones known to bite, each with the code that implements it. **This
|
|
89
|
+
list is not exhaustive.** `planRoute`, `decideModelSwitch` and
|
|
90
|
+
`decideEffortSwitch` in `extension/route.ts`, applied by `applyRoute`
|
|
91
|
+
in `extension/threads.ts`, are the authority: read them when you need
|
|
92
|
+
certainty rather than a map. This section is the reference for these
|
|
93
|
+
cases. Other sites may name or summarise them; if a summary disagrees,
|
|
94
|
+
this section governs, with the source functions above the ultimate
|
|
95
|
+
authority.
|
|
96
|
+
|
|
97
|
+
Why the MODEL can differ:
|
|
98
|
+
|
|
99
|
+
- **M1 — a live failover holds the thread.** After a successful
|
|
100
|
+
`modelFailover` the worker stays on its fallback, and a dispatch
|
|
101
|
+
that omits `model` keeps it: the revert to the session's opening
|
|
102
|
+
model stands down (BG16). The hold ends when the session is
|
|
103
|
+
disposed, and when a later non-`openOnly` route moves it — an
|
|
104
|
+
explicit `model` on either router state, or the thread's routed
|
|
105
|
+
base while the router is ON. An OFF pre-router pin is `openOnly`
|
|
106
|
+
and does not release it. A reopen ends it too, except in M6.
|
|
107
|
+
*Code:* the `failover-held` keep in `decideModelSwitch`
|
|
108
|
+
(`route.ts`); `applyRoute` clears the marker after a plan-driven
|
|
109
|
+
`setModel`, and `liveFailoverModel` reports it (`threads.ts`).
|
|
110
|
+
- **M2 — a failover fires inside the dispatch.** When the attempt
|
|
111
|
+
fails with a model-API error, Slate switches the live session to
|
|
112
|
+
the mapped model and re-prompts once. One episode covers both
|
|
113
|
+
attempts, and its `ran:` line names the model the action ended on
|
|
114
|
+
rather than the planned one. *Code:* the `isFailoverCandidate`
|
|
115
|
+
block in `runDispatchInner` (`threads.ts`) and `planFailoverSwitch`,
|
|
116
|
+
guard 7 (`route.ts`).
|
|
117
|
+
- **M3 — the revert was attempted and failed.** A dispatch that names
|
|
118
|
+
no model switches the session back to the model it opened on. When
|
|
119
|
+
that spec no longer resolves, or `setModel` throws, Slate warns and
|
|
120
|
+
leaves the session on the model the previous action left it on
|
|
121
|
+
(BG24). A PLAN-driven switch that fails is the opposite case: the
|
|
122
|
+
dispatch aborts, nothing runs and no episode is written. M7 is the
|
|
123
|
+
case where no revert is attempted at all. *Code:* `giveUp` inside
|
|
124
|
+
`applyRoute` (`threads.ts`).
|
|
125
|
+
- **M4 — the window guard substitutes** (router ON). A model that
|
|
126
|
+
cannot hold the thread's context is replaced by the widest listed
|
|
127
|
+
candidate, and only when one is STRICTLY wider than the model it
|
|
128
|
+
would replace. When nothing listed is wider, the action runs on the
|
|
129
|
+
original model and a warning says pi will compact. It never blocks
|
|
130
|
+
the action, and `substitutedFrom` records a replacement. *Code:*
|
|
131
|
+
`strictlyWider` in `planRoute`'s guard 5 (`route.ts`).
|
|
132
|
+
- **M5 — the thread's base was not routable** (router ON). A base
|
|
133
|
+
that is absent or has fallen off the list is re-seeded to a listed
|
|
134
|
+
candidate with a warning, so a model-less dispatch runs on the
|
|
135
|
+
seeded base and not on the value the record carried. *Code:* the
|
|
136
|
+
re-seed branch of `planRoute` with `defaultBase` (`route.ts`),
|
|
137
|
+
written back by `persistReseededBase` (`threads.ts`).
|
|
138
|
+
- **M6 — a reopened session restores a model from its file** (CQ3).
|
|
139
|
+
Slate passes the reopening model explicitly whenever the dispatch
|
|
140
|
+
resolves one, and that overrides the session file. When the
|
|
141
|
+
dispatch resolves no model at all and the host session has none
|
|
142
|
+
either, pi restores the file's last model change instead, which can
|
|
143
|
+
be a fallback M1 was holding. The `live=` marker is cleared on every
|
|
144
|
+
reopen, so in this one shape the fallback outlives its marker.
|
|
145
|
+
*Code:* `opts.model ? resolveModel(ctx, opts.model) : ctx.model` in
|
|
146
|
+
`openWorkerSession` (`worker.ts`); the marker delete in
|
|
147
|
+
`openWorkerFor` and the CQ3 note on `liveFailoverModel`
|
|
148
|
+
(`threads.ts`).
|
|
149
|
+
- **M7 — nothing was captured to return to.** When the worker session
|
|
150
|
+
reported no model as it opened, there is no revert target, and a
|
|
151
|
+
dispatch that names no model leaves the session where it is with no
|
|
152
|
+
switch attempted. The effort axis has the same keep when no opening
|
|
153
|
+
level was captured. *Code:* `NO_SESSION_BASELINE` and the
|
|
154
|
+
`no-baseline` keeps in `decideModelSwitch` and `decideEffortSwitch`,
|
|
155
|
+
fed by `captureSessionBaseline` (`route.ts`).
|
|
156
|
+
|
|
157
|
+
Why the LEVEL can differ. There is no counterpart to M1 here: a
|
|
158
|
+
dispatch that names no `effort` returns the session to the level it
|
|
159
|
+
opened on. M7 and E6 are known cases in which it does not.
|
|
160
|
+
|
|
161
|
+
- **E1 — pi clamps a level Slate cannot judge.** When the ladder for
|
|
162
|
+
the judged model is unreadable — no profile, a throwing lookup, a
|
|
163
|
+
non-array ladder — the guards stand down and the level goes to pi,
|
|
164
|
+
which clamps it to something the model offers. Slate reports
|
|
165
|
+
nothing, because it has no evidence to report. *Code:*
|
|
166
|
+
`checkEffortFor` and `guardEffort`'s `ladderKnown` (`route.ts`).
|
|
167
|
+
- **E2 — M4 moved the model under an explicit level.** A level that
|
|
168
|
+
is not valid on the substituted model is DROPPED with a warning
|
|
169
|
+
rather than refusing the action — a context size must never
|
|
170
|
+
hard-block a dispatch — and the session's opening level applies.
|
|
171
|
+
*Code:* the `soft` branch of `guardEffort` (`route.ts`).
|
|
172
|
+
- **E3 — a stored base level no longer holds.** A base effort is a
|
|
173
|
+
cached derivation over a table that ships with Slate. When today's
|
|
174
|
+
table no longer reads it as `ok`, it is silently re-derived for
|
|
175
|
+
that model: nobody asked for that level, so a stale cache is
|
|
176
|
+
Slate's to correct rather than news to report. *Code:* the stored
|
|
177
|
+
base-effort branch of `planRoute`, with `lowestMeasuredEffort`
|
|
178
|
+
(`route.ts`).
|
|
179
|
+
- **E4 — the action's model is not the base model.** An explicit
|
|
180
|
+
`model`, or M4, drops the inherited base level and derives the new
|
|
181
|
+
model's own lowest measured level instead. A level derived for one
|
|
182
|
+
model is never carried onto another. *Code:* the effort resolution
|
|
183
|
+
in `planRoute`, keyed on `judgedModel` (`route.ts`).
|
|
184
|
+
- **E5 — M2 re-clamps the level.** The failover route requests no
|
|
185
|
+
level, and pi re-clamps the session's current level for the
|
|
186
|
+
fallback model. *Code:* `session.setModel(mapped)` in the failover
|
|
187
|
+
block (`threads.ts`); `openWorkerSession` passes the settings
|
|
188
|
+
default explicitly at every open for the same reason (`worker.ts`).
|
|
189
|
+
- **E6 — the level was judged for one model and another model runs.**
|
|
190
|
+
When M1, M3 or M7 leaves the session on a model other than the plan
|
|
191
|
+
target, the level the plan resolved is still applied, and pi clamps
|
|
192
|
+
it to that model's own ladder with nothing reported.
|
|
193
|
+
`effortJudgedFor` names the model the judgement was about, which is
|
|
194
|
+
then not the model that ran. *Code:* the effort switch in
|
|
195
|
+
`applyRoute`, which runs after a model `keep` (`threads.ts`), and
|
|
196
|
+
`decideEffortSwitch` (`route.ts`).
|
|
197
|
+
|
|
71
198
|
## Configuration
|
|
72
199
|
|
|
73
200
|
`router` lives in `.pi/slate.json` and, like the rest of that file,
|
|
@@ -196,7 +323,8 @@ The vocabulary is pi's ladder and nothing else: `off`, `minimal`,
|
|
|
196
323
|
actually offers is per model — the shipped table records a ladder
|
|
197
324
|
per model id, not a family rule.
|
|
198
325
|
|
|
199
|
-
**An omitted `effort` never resolves to a fixed default.**
|
|
326
|
+
**An omitted `effort` never resolves to a fixed default.** With the
|
|
327
|
+
router ON, in order:
|
|
200
328
|
|
|
201
329
|
1. the thread's stored base effort — but only when the action runs
|
|
202
330
|
on the thread's base model, and only while that stored level
|
|
@@ -205,8 +333,8 @@ per model id, not a family rule.
|
|
|
205
333
|
the ladder, or onto the provider's rejection list) is silently
|
|
206
334
|
re-derived; nobody asked for it, so a stale cache is Slate's
|
|
207
335
|
problem to correct rather than news to report;
|
|
208
|
-
2. otherwise the **lowest measured level of the model
|
|
209
|
-
|
|
336
|
+
2. otherwise the **lowest measured level of the model it routes to**
|
|
337
|
+
— the lowest level on that model's ladder that carries a traced
|
|
210
338
|
capability measurement. A level derived for one model is never
|
|
211
339
|
carried onto another, so an explicit `model`, or a window
|
|
212
340
|
substitution, re-derives it;
|
|
@@ -214,14 +342,19 @@ per model id, not a family rule.
|
|
|
214
342
|
the level the worker session opened on applies, which is pi's own
|
|
215
343
|
settings default — never a level another action left behind.
|
|
216
344
|
|
|
345
|
+
With the router OFF Slate resolves no level at all, so that same
|
|
346
|
+
opening level applies to every action that omits `effort`.
|
|
347
|
+
|
|
217
348
|
So a higher level is only ever reached by naming it: pass `effort`
|
|
218
349
|
explicitly on the dispatch. A derived level is measured by
|
|
219
350
|
construction and therefore cannot trip the effort guards; only an
|
|
220
351
|
explicit one can.
|
|
221
352
|
|
|
222
|
-
An explicit level is judged against the model the action
|
|
223
|
-
|
|
224
|
-
|
|
353
|
+
An explicit level is judged against the model the action routes to,
|
|
354
|
+
which `effortJudgedFor` names. [Known cases where the model or level
|
|
355
|
+
differs](#known-cases-where-the-model-or-level-differs) collects what
|
|
356
|
+
can move either afterwards. The difference between a warning and a
|
|
357
|
+
refusal is:
|
|
225
358
|
|
|
226
359
|
- **evidence gap** (the level is on that model's ladder but no
|
|
227
360
|
traced source reports a capability result there) — advisory. The
|
|
@@ -230,8 +363,8 @@ is:
|
|
|
230
363
|
`router.allowUnmeasuredEffort: false` turns this one case into a
|
|
231
364
|
hard refusal for the whole project;
|
|
232
365
|
- **off the model's ladder** — refused, whenever the ladder is
|
|
233
|
-
known. pi would otherwise clamp the level silently and
|
|
234
|
-
orchestrator would believe the action ran at a level the model
|
|
366
|
+
known, outside E2. pi would otherwise clamp the level silently and
|
|
367
|
+
the orchestrator would believe the action ran at a level the model
|
|
235
368
|
never offered;
|
|
236
369
|
- **rejected outright by the provider** — refused, always. Such a
|
|
237
370
|
level is still on pi's ladder for the model (the table records the
|
|
@@ -300,26 +433,27 @@ model's ladder is unreadable.
|
|
|
300
433
|
its decision to make), the same lines in the live progress output,
|
|
301
434
|
and `details.ranModel` / `details.ranEffort` /
|
|
302
435
|
`details.ranEffortUnmeasured` / `details.warnings` for a renderer.
|
|
303
|
-
- **In the `threads` listing:** `base=<model>@<level>?` —
|
|
304
|
-
dispatch
|
|
436
|
+
- **In the `threads` listing:** `base=<model>@<level>?` — the nominal
|
|
437
|
+
plan target when a dispatch omits `model`. The trailing `?` marks
|
|
305
438
|
the level, not the model, as provisional: it is a stored default,
|
|
306
|
-
re-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
live
|
|
439
|
+
re-validated against the model's current capability data on every
|
|
440
|
+
dispatch and silently re-derived if it no longer holds. `last=` is
|
|
441
|
+
the model and level that the last action actually ran on, with
|
|
442
|
+
`(unmeasured)` where that applies. `live=<model> (failover)` means
|
|
443
|
+
a held fallback currently overrides the nominal base for the live
|
|
444
|
+
session.
|
|
310
445
|
- **At the session level:** the resolution's own warnings, as UI
|
|
311
446
|
notifications (console when no UI is attached).
|
|
312
447
|
- **In the orchestrator's own system prompt, every turn:** the
|
|
313
448
|
doctrine gains a routing rule — a table with one row per routable
|
|
314
449
|
model, plus the rules for reading it. This is the surface you do
|
|
315
|
-
not see, and it is the router's standing cost: 1,
|
|
316
|
-
20 lines for six configured models, 2,
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
characters with routing off). Those are PORTABLE characters — the
|
|
450
|
+
not see, and it is the router's standing cost: 1,950 characters /
|
|
451
|
+
20 added doctrine lines for six configured models, 2,505 / 23 for
|
|
452
|
+
all nine. In the current snapshot a model row costs 146–183
|
|
453
|
+
characters, plus a one-off legend clause for each marker it
|
|
454
|
+
introduces. Configuring the router roughly doubles Slate's
|
|
455
|
+
always-loaded block (1,929 characters with routing and writing
|
|
456
|
+
off). Those are PORTABLE characters — the
|
|
323
457
|
doctrine with each occurrence of the installed `docs/` directory
|
|
324
458
|
removed, filenames kept — because the doctrine embeds absolute doc
|
|
325
459
|
paths and its raw size therefore depends on where the package is
|
|
@@ -519,13 +653,16 @@ concern; a green suite says nothing about it.
|
|
|
519
653
|
dispatch guard and anything else that consults the router agree.
|
|
520
654
|
A key or provider added mid-session, and an edit to
|
|
521
655
|
`slate.json`, both need a new pi session.
|
|
522
|
-
- **Router OFF
|
|
523
|
-
|
|
524
|
-
seeded base
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
656
|
+
- **Router OFF disables candidate routing, not action arguments or
|
|
657
|
+
failover.** With an empty list there is no model list, window
|
|
658
|
+
guard, billing notice, seeded base or candidate substitution, and
|
|
659
|
+
Slate derives no effort level. A model-less plan targets the
|
|
660
|
+
thread's pre-router pin, else nothing, so the session returns to the
|
|
661
|
+
model it opened on outside M1, M3 and M7, and to the level it opened
|
|
662
|
+
on outside M7 and E6. An explicit `effort` is still judged against
|
|
663
|
+
the shipped profile for the plan target when pi's registry can serve
|
|
664
|
+
that model. The `model` argument itself is passed through
|
|
665
|
+
byte-for-byte, so a malformed spec still produces pi's own error.
|
|
529
666
|
- **A mid-thread model switch costs the prompt cache.** A long
|
|
530
667
|
thread runs overwhelmingly on cache reads; routing a small action
|
|
531
668
|
to a cheaper model on a cold cache can cost more than staying on
|