ytdb-slate 0.3.0 → 0.5.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 +36 -4
- package/docs/context-budget.md +125 -0
- package/docs/design-principles.md +156 -7
- package/docs/model-routing.md +553 -0
- package/extension/base-model.ts +545 -0
- package/extension/episodes.ts +387 -31
- package/extension/failover.ts +27 -24
- package/extension/handoff.ts +44 -12
- package/extension/index.ts +140 -13
- package/extension/mode.ts +338 -3
- package/extension/model-default.ts +6 -2
- package/extension/model-profiles.ts +812 -0
- package/extension/model-router.ts +858 -0
- package/extension/notify.ts +20 -0
- package/extension/paths.ts +1 -0
- package/extension/render.ts +20 -1
- package/extension/route.ts +1185 -0
- package/extension/state.ts +451 -5
- package/extension/threads.ts +815 -45
- package/extension/tools.ts +80 -16
- package/extension/worker-extensions.ts +392 -0
- package/extension/worker.ts +115 -11
- package/package.json +8 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Slate is a thread-weaving orchestration extension for the [pi coding agent](https://pi.dev).
|
|
4
4
|
|
|
5
|
-
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.
|
|
5
|
+
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.
|
|
6
6
|
|
|
7
7
|
## Why Slate?
|
|
8
8
|
|
|
@@ -51,6 +51,12 @@ This summary is orientation only; the shipped docs listed below are normative.
|
|
|
51
51
|
|
|
52
52
|
Slate can ride through model API outages. The opt-in `modelFailover` map in `.pi/slate.json` (trusted projects only) maps a model (`provider/id`) to an equal-quality alternative; on an eligible model API failure — not an abort or a context overflow, and for the orchestrator and worker sites only after pi's own retries are exhausted — the affected site (orchestrator, worker thread, or episode compression) retries once on the mapped model (single hop, never chained). An orchestrator failover — like the handoff adoption that re-applies a parent session's model — would otherwise leave pi's global model defaults changed, so Slate restores them on a best-effort basis unless `preserveGlobalModelDefault` is `false`. The map is empty by default (feature off) and read once at session start. Full semantics: the `modelFailover` row in [Configuration](#configuration) and the shipped [`docs/model-failover.md`](docs/model-failover.md) — if they disagree, that document wins.
|
|
53
53
|
|
|
54
|
+
## Action-level model routing
|
|
55
|
+
|
|
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; a dispatch then runs on the `model` and `effort` arguments of that action, else on the thread's own base model and a level DERIVED for the model that actually runs — the lowest level that model has a capability measurement at, never a fixed default. The list is empty by default, and empty means off: no list is enforced, nothing is seeded, and model selection is exactly what it was before the router existed. 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
|
+
|
|
58
|
+
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
|
+
|
|
54
60
|
## Install
|
|
55
61
|
|
|
56
62
|
Install into a project (pinned, project-scoped — recorded in `.pi/settings.json`):
|
|
@@ -68,8 +74,9 @@ Optional config file: `slate.json` in the project's pi config dir (`.pi/slate.js
|
|
|
68
74
|
| Key | Type | Default | Semantics |
|
|
69
75
|
| --- | --- | --- | --- |
|
|
70
76
|
| `orchestratorModeDefault` | boolean | `false` | Start fresh interactive sessions with orchestrator mode ON. |
|
|
71
|
-
| `episodeModel` | string | newest available Anthropic Sonnet, else the
|
|
77
|
+
| `episodeModel` | string | newest available Anthropic Sonnet, else the orchestrator's own base model | Model (`provider/id`) used to compress a finished action into an episode. Never the model an action was routed to: an episode is read by every later consumer of the thread, so one cheap route must not degrade the record. An unusable value is reported and the default is used. |
|
|
72
78
|
| `workerTools` | string[] | `["read", "bash", "edit", "write", "grep", "find", "ls"]` | Tools available to worker threads (an empty list also falls back to the default). |
|
|
79
|
+
| `workerExtensions` | string[] | `[]` | Regex patterns (matched **unanchored**) selecting which of the host session's already-loaded extensions also load into every worker thread; each matched extension's tools are added **on top of** `workerTools`. Empty (default) means workers load no extensions. The orchestrator keeps its restricted tool set but its doctrine is told what was whitelisted. Invalid patterns are dropped with a warning at session start. |
|
|
73
80
|
| `maxConcurrent` | number | `4` | Maximum number of worker actions running concurrently (must be ≥ 1 — unenforced: a value of 0 or less silently hangs all dispatches). Excess dispatches wait in a queue; actions on the same thread always run in dispatch order. Default rationale: shipped `docs/design-principles.md` §5 (repo-local note). |
|
|
74
81
|
| `contextBudget` | number \| object | `256000` (Anthropic models: `400000`) | Absolute orchestrator context budget (tokens) at which Slate auto-pauses and prepares a fresh-session handoff — semantics, defaults, per-model overrides, and rationale in [`docs/context-budget.md`](docs/context-budget.md). |
|
|
75
82
|
| `orchestratorPromptDocs` | string[] | `[]` | Project markdown files (paths relative to the project root) whose **contents** are appended to the orchestrator system prompt. |
|
|
@@ -79,6 +86,8 @@ Optional config file: `slate.json` in the project's pi config dir (`.pi/slate.js
|
|
|
79
86
|
| `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. |
|
|
80
87
|
| `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). |
|
|
81
88
|
| `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 be routed to. **Empty or absent means the router is OFF and dispatch behaves exactly as it did before the router existed** — no model list is enforced and no thread gets a seeded base model. A listed model becomes routable only where four things intersect: your list, a benchmark profile Slate ships for that model, pi's model registry, and credentials pi actually has configured; anything that fails one of those is dropped with a warning naming it (if every entry drops, routing turns off with one summary warning). Read once at session start and resolved once per session — see [`docs/model-routing.md`](docs/model-routing.md). |
|
|
90
|
+
| `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. |
|
|
82
91
|
|
|
83
92
|
Example `.pi/slate.json` (the `docs/agents/...` paths are placeholders — point them at markdown files that actually exist in **your** project):
|
|
84
93
|
|
|
@@ -92,19 +101,41 @@ Example `.pi/slate.json` (the `docs/agents/...` paths are placeholders — point
|
|
|
92
101
|
"workflow": { "draftPRs": true },
|
|
93
102
|
"doctrineExtraPath": "docs/agents/workflow-additions.md",
|
|
94
103
|
"reviewPerspectivesPath": "docs/agents/review-perspectives.md",
|
|
95
|
-
"modelFailover": { "anthropic/claude-sonnet-5": "openai/gpt-5.2" }
|
|
104
|
+
"modelFailover": { "anthropic/claude-sonnet-5": "openai/gpt-5.2" },
|
|
105
|
+
"router": { "models": ["openai/gpt-5.6-luna", "anthropic/claude-opus-5"] }
|
|
96
106
|
}
|
|
97
107
|
```
|
|
98
108
|
|
|
99
109
|
> **Silent skip:** the project-file keys fail silently — no error is shown. For the content-injected keys (`orchestratorPromptDocs`, `workerPromptDocs`, `doctrineExtraPath`) a missing, unreadable, or empty file is skipped and nothing is injected. For `reviewPerspectivesPath` the pointer is omitted only when the file is missing — the file is not read at injection time, so an unreadable or empty file is still cited. Verify your paths after copying the example.
|
|
100
110
|
|
|
111
|
+
**Worker extensions (`workerExtensions`).** By default worker threads load no extensions. This key is a list of regex patterns that select extensions the **host session has already loaded** and load them into every worker too. Each pattern is matched **unanchored** (unlike `contextBudget.overrides`, which is anchored) against a load unit's recorded source spec (e.g. `npm:pi-web-search@1.3.1`), its load-unit path, or the entry path of any tool that unit contributes, so a bare package name matches:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"workerExtensions": ["pi-smart-fetch", "pi-web-search"]
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Every worker then gets the fetch and web-search tools **on top of** `workerTools` (and on top of the per-dispatch `tools` argument of the `thread` tool — those two govern the built-in tools only). The orchestrator itself does **not** gain these tools — orchestrator mode keeps its restricted set — but its doctrine gains a rule naming each whitelisted extension and its tools, so it knows what it can delegate. pi's discovery, project-trust gating, and dedup remain the only ingress: a worker can never load an extension the host is not running, an extension that registers no tools cannot be whitelisted, and a host started with extensions disabled offers nothing to whitelist.
|
|
120
|
+
|
|
121
|
+
**What to know before whitelisting** — it reaches past Slate's isolation, so it is an operator decision:
|
|
122
|
+
|
|
123
|
+
- **Delegation is unbounded.** Slate guarantees only that no worker obtains Slate's own `thread`/`threads`/`episode` tools. A whitelisted extension that ships its own sub-agent or delegation tool under any other name gives workers delegation Slate can neither detect, bound, nor account for.
|
|
124
|
+
- **Credential and filesystem reach.** Inside a worker the extension has the same filesystem and credential access it has in the host; Slate's read-only settings snapshot blocks pi-settings writes and nothing else.
|
|
125
|
+
- **Unsynced model-scoped tools.** Worker sessions never fire the session-start event, so an extension whose tool set is model-scoped stays unsynced and may offer a tool the worker's model cannot serve (e.g. pi-web-search's `url_context` on a non-Google worker model) — the call simply fails and the episode records it.
|
|
126
|
+
- **Abort.** A third-party extension may ignore the abort signal, so its network activity can outlive an abort or a context-budget pause.
|
|
127
|
+
- **Cost.** Provider-native tool billing can escape Slate's worker cost accounting.
|
|
128
|
+
- **Pathological patterns.** The patterns are regexes from your own trusted config — the same file that already steers models, prompts, and tool lists — matched with no time bound while the extension set is resolved once per session. A pattern with catastrophic backtracking can stall that resolution; treat it as a footgun to avoid, not a privilege boundary.
|
|
129
|
+
|
|
130
|
+
The load-time recursion guard behind this — and the risks it does and does not cover — is in [`docs/design-principles.md`](docs/design-principles.md).
|
|
131
|
+
|
|
101
132
|
## Trust
|
|
102
133
|
|
|
103
134
|
Slate reads project configuration (`.pi/slate.json`) and injects project files (`orchestratorPromptDocs`, `workerPromptDocs`, `doctrineExtraPath`, `reviewPerspectivesPath`) **only in trusted projects**. In untrusted projects Slate runs with built-in defaults and injects nothing from the working tree.
|
|
104
135
|
|
|
105
136
|
## Shipped docs
|
|
106
137
|
|
|
107
|
-
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:
|
|
138
|
+
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:
|
|
108
139
|
|
|
109
140
|
- `docs/track-workflow.md` — the track-based workflow (research → design review → adversarial review → track review)
|
|
110
141
|
- `docs/pr-publishing.md` — umbrella draft-PR publishing (cited only when `workflow.draftPRs` is `true`)
|
|
@@ -112,6 +143,7 @@ In orchestrator mode, Slate appends a short **doctrine** (a block of numbered ru
|
|
|
112
143
|
- `docs/design-principles.md` — Slate's own design rationale
|
|
113
144
|
- `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)
|
|
114
145
|
- `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)
|
|
146
|
+
- `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.
|
|
115
147
|
|
|
116
148
|
Project-specific additions layer on top — they extend, not replace, the shipped doctrine — via two distinct mechanisms:
|
|
117
149
|
|
package/docs/context-budget.md
CHANGED
|
@@ -118,6 +118,114 @@ paused, threshold compactions pass through as an escape valve.
|
|
|
118
118
|
Overflow-recovery compaction and manual `/compact` are never
|
|
119
119
|
touched.
|
|
120
120
|
|
|
121
|
+
## What the always-loaded doctrine costs the budget
|
|
122
|
+
|
|
123
|
+
The budget's denominator includes Slate's own always-loaded block.
|
|
124
|
+
While orchestrator mode is on, the doctrine sits in the
|
|
125
|
+
orchestrator's system prompt for the whole session — and, like the
|
|
126
|
+
rest of the conversation (see the pricing cliff above), it is sent
|
|
127
|
+
again with every request. It occupies its space in the window once;
|
|
128
|
+
you pay its input tokens every turn.
|
|
129
|
+
|
|
130
|
+
**Its size depends on where the package is installed.** That is not
|
|
131
|
+
obvious, and it catches anyone who measures: the doctrine cites the
|
|
132
|
+
shipped docs by ABSOLUTE path, so three to five full filesystem
|
|
133
|
+
paths are embedded in the block — three at minimum
|
|
134
|
+
(`track-workflow.md`, `review-rules.md`, `design-principles.md`),
|
|
135
|
+
plus `pr-publishing.md` when `workflow.draftPRs` is on, plus
|
|
136
|
+
`model-routing.md` when the routing rule renders. Every additional
|
|
137
|
+
character in the installed `docs/` directory therefore costs 3–5
|
|
138
|
+
characters of doctrine.
|
|
139
|
+
|
|
140
|
+
So the figures below are **portable characters**, defined exactly as
|
|
141
|
+
Slate's own automated size check defines them: the block with each
|
|
142
|
+
occurrence of the installed `docs/` DIRECTORY removed, keeping the
|
|
143
|
+
filename. That makes them install-invariant by construction —
|
|
144
|
+
verified by measuring at a 12-character docs path and at this repo's
|
|
145
|
+
58-character one, which give identical portable counts — and it
|
|
146
|
+
makes the arithmetic one multiplication:
|
|
147
|
+
|
|
148
|
+
> rendered characters = portable + (embedded paths × length of your
|
|
149
|
+
> installed `docs/` directory)
|
|
150
|
+
|
|
151
|
+
Every row below states its full basis, because router state, model
|
|
152
|
+
count and `draftPRs` each move the number:
|
|
153
|
+
|
|
154
|
+
| router | models | `draftPRs` | paths | portable | lines | rendered at a 58-char docs dir |
|
|
155
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
156
|
+
| off | — | off | 3 | 1,929 | 38 | 2,103 |
|
|
157
|
+
| off | — | on | 4 | 1,948 | 38 | 2,180 |
|
|
158
|
+
| on | 6 configured | off | 4 | 3,870 | 58 | 4,102 |
|
|
159
|
+
| on | 6 configured | on | 5 | 3,889 | 58 | 4,179 |
|
|
160
|
+
| on | all 9 shipped | off | 4 | 4,448 | 61 | 4,680 |
|
|
161
|
+
| on | all 9 shipped | on | 5 | 4,467 | 61 | 4,757 |
|
|
162
|
+
|
|
163
|
+
An untrusted project reads the first row whatever its `slate.json`
|
|
164
|
+
says: no project config is loaded, so neither optional rule renders.
|
|
165
|
+
Line counts do not vary with the install path. Enabling `draftPRs`
|
|
166
|
+
costs 19 portable characters plus one more embedded path.
|
|
167
|
+
|
|
168
|
+
Two parts of the block grow with configuration rather than with the
|
|
169
|
+
path:
|
|
170
|
+
|
|
171
|
+
- The routing rule is a live table with ONE ROW PER ROUTABLE MODEL,
|
|
172
|
+
so what renders is the models you CONFIGURE — not the nine Slate
|
|
173
|
+
ships profiles for. It is 1,941 portable characters / 20 lines for
|
|
174
|
+
six configured models and 2,519 / 23 for all nine. A model ROW
|
|
175
|
+
costs 154–186 characters; the legend adds a one-off clause per
|
|
176
|
+
marker it has to explain, so listing the first cheap-tier model
|
|
177
|
+
costs ~50 characters more than its row (two new clauses). That is
|
|
178
|
+
why stepping from six to nine averages 193 per model where the
|
|
179
|
+
first six average 171.
|
|
180
|
+
- The worker-extension rule grows per whitelisted extension and per
|
|
181
|
+
tool that extension contributes, so it has no fixed size; one
|
|
182
|
+
extension contributing two tools measured 373 portable characters
|
|
183
|
+
/ 7 lines.
|
|
184
|
+
|
|
185
|
+
Against a 256,000-token budget none of this is material — as a rough
|
|
186
|
+
estimate, at 4 characters per token (no tokenizer was run, and the
|
|
187
|
+
table is denser than prose, so treat it as a floor) the whole block
|
|
188
|
+
is ≈480–525 tokens with both optional rules off and ≈970–1,025 with
|
|
189
|
+
six models routed, each range spanning portable to as-rendered-here.
|
|
190
|
+
That is under half a percent of the default budget, though it is
|
|
191
|
+
re-sent on every request rather than paid once. It is listed here
|
|
192
|
+
because it is context Slate itself puts in front of the model on
|
|
193
|
+
every turn, and because it is the number to check before assuming
|
|
194
|
+
the budget's headroom is all conversation.
|
|
195
|
+
|
|
196
|
+
## Worker threads: the routing window guard
|
|
197
|
+
|
|
198
|
+
The budget above is the ORCHESTRATOR's. Worker threads have no
|
|
199
|
+
budget and never pause; with action-level routing on, they get a
|
|
200
|
+
weaker but automatic counterpart. Before each dispatch — whenever
|
|
201
|
+
the thread's context size is knowable, so not for a thread whose
|
|
202
|
+
worker session has not been opened yet — the routed model's
|
|
203
|
+
REGISTRY context window is checked against that size using pi's OWN
|
|
204
|
+
compaction settings, the same settings whose `reserveTokens` clamps
|
|
205
|
+
the budget above. (The check runs even if you disabled
|
|
206
|
+
auto-compaction: turning compaction off does not make an
|
|
207
|
+
over-window dispatch safe, it only turns the compaction into an
|
|
208
|
+
overflow error.) If the thread no longer fits, the action is moved
|
|
209
|
+
to the widest listed model when that one is strictly wider, with a
|
|
210
|
+
warning; if nothing listed is wider it runs anyway and pi compacts.
|
|
211
|
+
The guard never blocks a dispatch and never pauses a thread.
|
|
212
|
+
|
|
213
|
+
Two consequences for this document's subject:
|
|
214
|
+
|
|
215
|
+
- The registry window is what decides the substitution, so the
|
|
216
|
+
overrides in the next section change worker routing too: raising
|
|
217
|
+
`gpt-5.6-*` to 1,050,000 makes those models genuinely wide
|
|
218
|
+
candidates rather than 272K ones.
|
|
219
|
+
- A narrow model in `router.models` is not a hazard the way it would
|
|
220
|
+
be for the orchestrator — it is a model long threads get routed
|
|
221
|
+
AWAY from, paying the wider model's rate instead. That is not
|
|
222
|
+
silent: the substituted action carries a ⚠ notice naming both
|
|
223
|
+
models.
|
|
224
|
+
|
|
225
|
+
Routing itself — the two `router` keys, what makes a model routable,
|
|
226
|
+
how effort levels resolve and the full guard list — is documented in
|
|
227
|
+
`model-routing.md` in this directory.
|
|
228
|
+
|
|
121
229
|
## Using GPT-5.6's full 1.05M window
|
|
122
230
|
|
|
123
231
|
Two deliberate steps. First, raise the registry window in
|
|
@@ -155,6 +263,17 @@ dropped:
|
|
|
155
263
|
WARNING: beyond 272K total input every request bills at the
|
|
156
264
|
long-context rates listed above.
|
|
157
265
|
|
|
266
|
+
With action-level routing on, this override also settles a warning
|
|
267
|
+
you will otherwise see once per session for each configured
|
|
268
|
+
`gpt-5.6-*` model: Slate's own profile for those models records a
|
|
269
|
+
1,050,000-token window, pi's stock registry reports 272,000, and the
|
|
270
|
+
router reports that divergence without adjudicating it (routing uses
|
|
271
|
+
the registry figure). Raising the registry window removes the
|
|
272
|
+
disagreement at its source. Leaving it alone is equally valid — the
|
|
273
|
+
registry figure is the conservative one — and `model-routing.md`
|
|
274
|
+
explains why Slate does not silence the warning by editing its own
|
|
275
|
+
number.
|
|
276
|
+
|
|
158
277
|
## Accepted limitations
|
|
159
278
|
|
|
160
279
|
- On models with a 272K pricing tier and a larger registry window
|
|
@@ -174,3 +293,9 @@ long-context rates listed above.
|
|
|
174
293
|
compaction can land in the same cycle and the handoff brief is
|
|
175
294
|
written from compacted context — episodes and thread state survive
|
|
176
295
|
on disk, so handoff still functions.
|
|
296
|
+
- The doctrine sizes above cover the shipped rules only. A project
|
|
297
|
+
that also injects `doctrineExtraPath` or `orchestratorPromptDocs`,
|
|
298
|
+
or whitelists worker extensions, pays for those on top in the same
|
|
299
|
+
always-loaded position, and Slate measures none of it against the
|
|
300
|
+
budget separately — it simply arrives as context the budget then
|
|
301
|
+
has less room for.
|
|
@@ -8,9 +8,22 @@ for its content. This document records the reasoning behind the
|
|
|
8
8
|
extension's design so maintainers — and the orchestrator itself, on
|
|
9
9
|
demand — can check changes and behavior against the original intent.
|
|
10
10
|
|
|
11
|
-
Note: module headers cite
|
|
12
|
-
original implementation plan
|
|
13
|
-
|
|
11
|
+
Note: module headers cite ids from design records that are not part of this
|
|
12
|
+
repository — the original implementation plan (ExecPlan: D3–D9, M1–M3), later
|
|
13
|
+
design rounds (higher-numbered D ids, and W ids for named warnings), and
|
|
14
|
+
per-round review findings, whose prefix says which review raised them:
|
|
15
|
+
AD (adversarial), AF (agent-failure), BG (blocker/bug), CN (concurrency),
|
|
16
|
+
CQ (code quality), DF (data fidelity), N (numeric), RG (regression),
|
|
17
|
+
RI (research integrity), SE (security), WB (worker boundary) and WS (worker
|
|
18
|
+
safety). `extension/model-profiles.ts` additionally cites research TRACE keys
|
|
19
|
+
(`G1a`, `GM3`, `O4.2`, `A4b`, …). Those name rows in the research corpus that
|
|
20
|
+
produced the profile table; the corpus lives in the project repository and is
|
|
21
|
+
NOT part of the published package, so in an installed copy the keys are
|
|
22
|
+
provenance marks to quote when reporting a data problem, not links to follow.
|
|
23
|
+
What a consumer can act on without the corpus is in `model-routing.md`
|
|
24
|
+
(§ where the numbers come from): the table's provenance rules, its `asOf`
|
|
25
|
+
date, and which fields are marked untraced. This document is the in-repo
|
|
26
|
+
source for the architecture rationale.
|
|
14
27
|
|
|
15
28
|
## 1. The problems Slate is built to solve
|
|
16
29
|
|
|
@@ -125,8 +138,11 @@ or two problems for the others:
|
|
|
125
138
|
not blind retry.
|
|
126
139
|
- **P6 — Make desired behavior the natural behavior.** In orchestrator
|
|
127
140
|
mode, tactical tools are removed, so delegation is the only way to act.
|
|
128
|
-
- **P7 — Guard against over-decomposition.** Workers
|
|
129
|
-
(
|
|
141
|
+
- **P7 — Guard against over-decomposition.** Workers never receive
|
|
142
|
+
Slate's dispatch tools (`thread`/`threads`/`episode`), so no worker can
|
|
143
|
+
spawn a Slate thread — Slate's own recursion stays depth-1. That is a
|
|
144
|
+
narrow load-time invariant, not a general delegation bound (see the
|
|
145
|
+
recursion-guard note in §5).
|
|
130
146
|
- **P8 — Per-episode feedback beats blind N-step execution.** Bounded
|
|
131
147
|
actions return before the next decision, so course correction is always
|
|
132
148
|
possible.
|
|
@@ -146,7 +162,7 @@ or two problems for the others:
|
|
|
146
162
|
| P4 context by reference | `tools.ts` `context` parameter injects prior episodes by id |
|
|
147
163
|
| P5 adaptive decomposition | doctrine rule 6 in `mode.ts`; no plan structure imposed anywhere |
|
|
148
164
|
| P6 natural behavior | `mode.ts` `ORCHESTRATOR_TOOLS` restriction (read-only + slate tools) |
|
|
149
|
-
| P7 over-decomposition guard | `worker.ts` recursion guard —
|
|
165
|
+
| P7 over-decomposition guard | `worker.ts` recursion guard — load-scoped barriers keep Slate's `thread`/`threads`/`episode` tools out of every worker (recursion-guard note below) |
|
|
150
166
|
| P8 per-episode feedback | `threads.ts` synchronous dispatch; episode returned to orchestrator |
|
|
151
167
|
| P9 parallelism | `threads.ts` `maxConcurrent` queueing; doctrine rule 2 in `mode.ts` |
|
|
152
168
|
| P10 context as RAM | `handoff.ts` context-budget auto-pause + fresh-session handoff |
|
|
@@ -164,6 +180,127 @@ while staying safe on common consumer API tiers; wider fan-outs only
|
|
|
164
180
|
pay tail latency, and projects with higher-tier keys raise the cap in
|
|
165
181
|
`slate.json`.
|
|
166
182
|
|
|
183
|
+
Repo-local note (not from the report): **action-level model routing**
|
|
184
|
+
applies P10's discipline to a second scarce resource. P10 treats
|
|
185
|
+
CONTEXT as RAM; the optional `router.models` list treats SPEND the same
|
|
186
|
+
way — each dispatched action gets a model and an effort level chosen to
|
|
187
|
+
be up to the task and no more, instead of every action inheriting
|
|
188
|
+
whatever model the orchestrator happens to be running. It is also a P1
|
|
189
|
+
corollary: an action is only a bounded unit if what it may cost is
|
|
190
|
+
bounded too, and the closed model list plus a derived (never inflated)
|
|
191
|
+
effort level is what bounds it. The two disciplines even share
|
|
192
|
+
machinery — pi's compaction settings, which clamp the orchestrator's
|
|
193
|
+
context budget, also decide per dispatch whether a worker thread still
|
|
194
|
+
fits the model it is routed to, in which case the action moves to the
|
|
195
|
+
widest listed model rather than being blocked.
|
|
196
|
+
|
|
197
|
+
The feature is OFF until `router.models` lists something, and it is
|
|
198
|
+
built on a deliberate split that is worth holding onto when reasoning
|
|
199
|
+
about the architecture: the code ENFORCES less than the routing data
|
|
200
|
+
ADVISES. Dispatch guards refuse what is mechanically wrong — a model
|
|
201
|
+
outside the configured list, a level a model does not offer — because
|
|
202
|
+
those are decidable from the data. The obligations that require
|
|
203
|
+
JUDGMENT about the work, such as which actions count as review or gate
|
|
204
|
+
actions and what a compliance constraint demands of a given task, are
|
|
205
|
+
stated in the doctrine for the orchestrator to honor and are not
|
|
206
|
+
guards. That is the same reasoning as P6: the harness makes the right
|
|
207
|
+
thing natural and blocks what it can decide, rather than pretending to
|
|
208
|
+
adjudicate what it cannot see. Which obligations fall on which side of
|
|
209
|
+
that line changes as the feature grows, so this document deliberately
|
|
210
|
+
does not restate the list — `model-routing.md` in this directory owns
|
|
211
|
+
it, along with the mechanics and the provenance of the routing data.
|
|
212
|
+
|
|
213
|
+
Repo-local note (not from the report): the P7 guard was originally
|
|
214
|
+
absolute — workers loaded no extensions, so Slate's `thread` tool simply
|
|
215
|
+
never existed for them. The optional `workerExtensions` key (`slate.json`,
|
|
216
|
+
trusted projects only — see the README config reference) relaxes that: it
|
|
217
|
+
whitelists extensions the host session has ALREADY loaded and loads them
|
|
218
|
+
into every worker. The guard therefore no longer rests on "workers load
|
|
219
|
+
nothing" but on a narrower, more precise invariant: no worker can ever
|
|
220
|
+
obtain SLATE's dispatch tools (`thread`/`threads`/`episode`).
|
|
221
|
+
|
|
222
|
+
Three load-scoped barriers buy that invariant, all applied per LOAD UNIT
|
|
223
|
+
— the owning package directory when the extension is package-originated
|
|
224
|
+
and the package's declared entries provably match what the host loaded
|
|
225
|
+
(BG20, below), otherwise the extension's own entry file:
|
|
226
|
+
|
|
227
|
+
1. Only whitelisted units load, through pi's resource loader in allowlist
|
|
228
|
+
mode; a worker's extension set is exactly the allowlist and no
|
|
229
|
+
non-whitelisted module is ever imported (load errors surface as
|
|
230
|
+
warnings). Candidates are only what the host session already loaded,
|
|
231
|
+
enumerated from its tool registry — so an extension registering no
|
|
232
|
+
tools is invisible, and a host started with extensions disabled yields
|
|
233
|
+
an empty candidate set.
|
|
234
|
+
2. Any unit that contains Slate's own package root is dropped whatever the
|
|
235
|
+
patterns say.
|
|
236
|
+
3. Any unit whose tools collide with Slate's `thread`/`threads`/`episode`
|
|
237
|
+
or with pi's built-ins (`read`/`bash`/`edit`/`write`/`grep`/`find`/`ls`)
|
|
238
|
+
is dropped WHOLE — pi's registry lets an extension tool overwrite a
|
|
239
|
+
same-named built-in, so a partial load could shadow the very tools the
|
|
240
|
+
guard protects.
|
|
241
|
+
|
|
242
|
+
Exclusion has to happen at load time, at unit granularity, rather than by
|
|
243
|
+
loading everything and filtering the tool list afterwards: a
|
|
244
|
+
worker-loaded extension is not tools-only. Its event handlers, tools, and
|
|
245
|
+
flags all go live on load, so once a unit has loaded its side effects have
|
|
246
|
+
already run and dropping its tools afterwards would leave its handlers and
|
|
247
|
+
flags active. Dropping the whole unit before it loads is the only clean
|
|
248
|
+
cut — which is also why barrier 3 rejects a colliding unit entirely
|
|
249
|
+
instead of suppressing just the offending tool. (A package directory is
|
|
250
|
+
handed to pi whole — letting pi's own manifest resolution expand it, so
|
|
251
|
+
no-tool companion entries load alongside the tool-bearing ones — only when
|
|
252
|
+
the manifest's declared entries are ALL literal relative paths AND the
|
|
253
|
+
host loaded every one of them. A glob or override-form entry, or a host
|
|
254
|
+
running only a filtered subset, fails that equivalence check and drops the
|
|
255
|
+
unit to exactly the entry files the host loaded; that fallback loses the
|
|
256
|
+
package's no-tool companion entries, since an entry that registered no
|
|
257
|
+
tool cannot be shown to be running.)
|
|
258
|
+
|
|
259
|
+
That collision barrier is best-effort and load-time only: it runs before a
|
|
260
|
+
unit loads, against its tools as the HOST registry reports them, and once
|
|
261
|
+
more after the load, against the tools the units actually registered — a
|
|
262
|
+
post-load collision fails the dispatch CLOSED rather than opening the
|
|
263
|
+
worker. Neither pass can see a tool an extension registers only LATER, on
|
|
264
|
+
the worker's first turn (a `before_agent_start` handler, say). Slate's own
|
|
265
|
+
dispatch tools do not depend on that scan: `createAgentSession` is given
|
|
266
|
+
an `excludeTools` denylist of `thread`/`threads`/`episode` that pi applies
|
|
267
|
+
AFTER the tool allowlist and re-applies on every tool-registry refresh, so
|
|
268
|
+
they can never be active in a worker no matter when or by whom they are
|
|
269
|
+
registered — a deferred registration included. A deferred registration
|
|
270
|
+
that shadows a pi BUILT-IN (`read`, `bash`, …) has no such gap-closer —
|
|
271
|
+
the worker needs the real built-in — so it stays outside Slate's control,
|
|
272
|
+
exactly as available to any extension the host session runs. The guard
|
|
273
|
+
bounds Slate's dispatch tools, not every tool an extension might register
|
|
274
|
+
at runtime.
|
|
275
|
+
|
|
276
|
+
The orchestrator does NOT get the whitelisted tools; orchestrator mode
|
|
277
|
+
keeps its restricted set. Instead its doctrine gains a rule listing each
|
|
278
|
+
whitelisted extension with its tool names and descriptions, so it knows
|
|
279
|
+
what it can delegate. That doctrine rule and the worker allowlists come
|
|
280
|
+
from a single memoized resolution per session, so the two cannot drift.
|
|
281
|
+
|
|
282
|
+
The invariant bought is exactly that: no worker gets Slate's dispatch
|
|
283
|
+
tools. It is NOT a general recursion or delegation bound, and the accepted
|
|
284
|
+
risks that follow are the operator's to weigh:
|
|
285
|
+
|
|
286
|
+
- A whitelisted extension that registers its OWN sub-agent or delegation
|
|
287
|
+
tool under any other name gives workers unbounded delegation that Slate
|
|
288
|
+
can neither detect nor bound, outside Slate's episode and cost
|
|
289
|
+
accounting. Whitelisting such an extension is a deliberate operator
|
|
290
|
+
decision.
|
|
291
|
+
- Inside a worker a whitelisted extension has the same filesystem and
|
|
292
|
+
credential reach it has in the host; Slate's read-only settings snapshot
|
|
293
|
+
blocks pi-settings writes and nothing else.
|
|
294
|
+
- Worker sessions never fire the session-start event, so an extension
|
|
295
|
+
whose model-scoped tool manager depends on it stays unsynced and may
|
|
296
|
+
offer a tool the worker's model cannot serve — the call simply fails and
|
|
297
|
+
the episode records it.
|
|
298
|
+
- Third-party extensions may ignore the abort signal, so their network
|
|
299
|
+
activity can outlive an abort or a context-budget pause.
|
|
300
|
+
- Host and worker copies of the same extension share module-level state
|
|
301
|
+
through pi's process-global module cache.
|
|
302
|
+
- Provider-native tool billing may escape Slate's worker cost accounting.
|
|
303
|
+
|
|
167
304
|
## 6. Runtime knowledge: what the orchestrator knows, and when
|
|
168
305
|
|
|
169
306
|
The orchestrator's knowledge of these principles is two-tier, following
|
|
@@ -174,7 +311,19 @@ the load-on-demand discipline the extension itself prescribes:
|
|
|
174
311
|
`review-rules.md`, and a pointer to the track-based workflow protocol
|
|
175
312
|
that ships with this package (`track-workflow.md` in this directory),
|
|
176
313
|
appended to the system prompt every turn while orchestrator mode is
|
|
177
|
-
on.
|
|
314
|
+
on. Sizing it matters to the tiering argument, so it is measured
|
|
315
|
+
rather than asserted: with both conditional tail rules and
|
|
316
|
+
`workflow.draftPRs` off, the block is **1,929 portable characters /
|
|
317
|
+
38 lines**, and configuring the router for six models roughly
|
|
318
|
+
DOUBLES it, to **3,870** on that same basis — the routing rule
|
|
319
|
+
renders one table row per routable model. (Portable characters, and
|
|
320
|
+
the rest of the configurations, are defined and tabulated in
|
|
321
|
+
`context-budget.md`; the raw count also depends on where the package
|
|
322
|
+
is installed, because the doctrine embeds absolute doc paths.) At a
|
|
323
|
+
rough 4 characters per token that is order-500 tokens, order-1,000
|
|
324
|
+
with the router on — so the original "a few hundred tokens" holds
|
|
325
|
+
only for the fixed rules, and a configured router buys its table with
|
|
326
|
+
a real slice of every request. It still covers everything routine
|
|
178
327
|
dispatching needs.
|
|
179
328
|
- **Tier 2 — on demand.** This document. The doctrine carries a short
|
|
180
329
|
pointer to it (doctrine rule 10); the orchestrator reads it only when
|