modelmix 5.0.2 → 5.0.3

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.
Files changed (37) hide show
  1. package/README.md +109 -7
  2. package/RLM_PLUGIN_SPEC.md +465 -0
  3. package/demo/gemini.js +3 -4
  4. package/demo/short.js +1 -1
  5. package/effort.js +2 -0
  6. package/index.d.ts +61 -1
  7. package/index.js +333 -45
  8. package/package.json +7 -4
  9. package/plugins/rlm/index.d.ts +194 -0
  10. package/plugins/rlm/index.js +25 -0
  11. package/plugins/rlm/lib/budget.js +153 -0
  12. package/plugins/rlm/lib/isolated-vm-sandbox.js +90 -0
  13. package/plugins/rlm/lib/markdown.js +156 -0
  14. package/plugins/rlm/lib/planner-prompt.js +137 -0
  15. package/plugins/rlm/lib/plugin.js +203 -0
  16. package/plugins/rlm/lib/runtime.js +146 -0
  17. package/plugins/rlm/lib/variable-descriptors.js +228 -0
  18. package/plugins/rlm/lib/worker-catalog.js +70 -0
  19. package/plugins/rlm/package.json +32 -0
  20. package/plugins/rlm/prompts/partials/processing-rules.md +8 -0
  21. package/plugins/rlm/prompts/planner.md +53 -0
  22. package/plugins/rlm/test/budget.test.js +86 -0
  23. package/plugins/rlm/test/fixtures/book.md +24 -0
  24. package/plugins/rlm/test/isolated-vm-sandbox.test.js +114 -0
  25. package/plugins/rlm/test/markdown.test.js +64 -0
  26. package/plugins/rlm/test/planner-template.test.js +140 -0
  27. package/plugins/rlm/test/plugin-contract.test.js +182 -0
  28. package/plugins/rlm/test/rlm-e2e.test.js +338 -0
  29. package/plugins/rlm/test/variable-descriptors.test.js +170 -0
  30. package/plugins/rlm/test/worker-catalog.test.js +104 -0
  31. package/pnpm-workspace.yaml +6 -0
  32. package/skills/modelmix/SKILL.md +22 -3
  33. package/test/effort.test.js +14 -1
  34. package/test/live.mcp.js +6 -6
  35. package/test/live.test.js +2 -2
  36. package/test/plugins.test.js +356 -0
  37. package/test/tokens.test.js +37 -5
package/README.md CHANGED
@@ -69,7 +69,7 @@ const setup = {
69
69
  const model = await ModelMix.new(setup)
70
70
  .sonnet5() // (main model) Anthropic claude-sonnet-5
71
71
  .gpt56luna() // (fallback 2) OpenAI gpt-5.6-luna
72
- .gemini36flash({ config: { temperature: 0 } }) // (fallback 3) Google gemini-36-flash
72
+ .gemini37flash() // (fallback 3) Google gemini-3.7-flash
73
73
  .grok46() // (fallback 4) Grok grok-4.6
74
74
  .addText("What's your name?");
75
75
 
@@ -101,6 +101,91 @@ This pattern allows you to:
101
101
  - Track token usage across all providers
102
102
  - Keep your code clean and maintainable
103
103
 
104
+ ## 🔌 Instance Plugins
105
+
106
+ Plugins wrap one ModelMix instance without changing global behavior. They run in registration order after templates are rendered and before provider-specific request conversion:
107
+
108
+ ```javascript
109
+ const metrics = {
110
+ name: 'metrics',
111
+ async execute(context, next) {
112
+ const startedAt = Date.now();
113
+ const result = await next();
114
+ return { ...result, elapsedMs: Date.now() - startedAt };
115
+ }
116
+ };
117
+
118
+ const model = ModelMix.new()
119
+ .gpt56luna()
120
+ .use(metrics)
121
+ .addText('Summarize this request.');
122
+ ```
123
+
124
+ A plugin may edit `context.request`, call `next()`, or return a complete ModelMix result itself. It can also create history-free child executions with `context.invoke()` and choose plugin inheritance:
125
+
126
+ ```javascript
127
+ const child = await context.invoke({
128
+ systemFile: './prompts/extract-entities.md',
129
+ assign: { outputLanguage: 'Spanish' },
130
+ messages: [{ role: 'user', content: section }],
131
+ plugins: { exclude: ['recursive-plugin'] },
132
+ history: false
133
+ });
134
+ ```
135
+
136
+ Supported policies are `'inherit'`, `'none'`, `{ include: [...] }`, and `{ exclude: [...] }`. Child metadata exposes `executionId`, `parentExecutionId`, and `depth` to middleware. `.new()` inherits registered plugins but not message history.
137
+
138
+ Child `systemFile` templates use the same EJS engine, `assign()` data contract, and relative Markdown includes as ordinary ModelMix templates. Use either `system` or `systemFile`, not both.
139
+
140
+ ### Recursive Language Model plugin
141
+
142
+ The separately publishable `@modelmix/rlm` workspace package keeps document parsing, planner prompts, and `isolated-vm` out of the core `modelmix` dependency tree. It requires Node.js 22 or newer.
143
+
144
+ ```javascript
145
+ const { ModelMix } = require('modelmix');
146
+ const { rlm } = require('@modelmix/rlm');
147
+
148
+ const fast = ModelMix.new().gpt41mini();
149
+
150
+ const result = await ModelMix.new()
151
+ .gpt56luna()
152
+ .use(rlm({
153
+ maxDepth: 2,
154
+ documents: {
155
+ book: {
156
+ format: 'markdown',
157
+ content: markdownBook
158
+ }
159
+ },
160
+ workers: {
161
+ fast: {
162
+ model: fast,
163
+ intelligence: 2,
164
+ cost: 1,
165
+ speed: 4,
166
+ description: 'Translation, extraction, and simple transformations'
167
+ }
168
+ },
169
+ limits: {
170
+ maxQueryBytes: 64 * 1024,
171
+ sandboxMemoryBytes: 64 * 1024 * 1024,
172
+ maxConcurrentQueries: 4,
173
+ maxCalls: 100,
174
+ maxOutputBytes: 8 * 1024 * 1024,
175
+ maxGeneratedTokens: 100000,
176
+ maxWallTimeMs: 120000
177
+ }
178
+ }))
179
+ .addText('Translate this book to neutral Latin American Spanish.')
180
+ .message();
181
+ ```
182
+
183
+ Markdown headings become stable nested sections, lists expose item arrays, and the original source order remains reconstructable. The planner receives only a content-free variable manifest: paths, types, array item counts, serialized UTF-8 byte estimates, string lengths, line and paragraph counts, structural summaries, and partition hints. The document values enter only the isolated sandbox, where generated JavaScript can inspect `variables` and call registered workers through `query()`.
184
+
185
+ A worker normally supplies `model: anotherModelMixInstance`. To offer the current parent chain under a name, register it with `useParent: true` instead; defining both is rejected.
186
+
187
+ Planner instructions live in Markdown templates under `plugins/rlm/prompts/`. The plugin supplies manifests and limits through ModelMix `assign()` and loads the system prompt through `systemFile`, so relative includes work and runtime values are rendered exactly once.
188
+
104
189
  ## 🎛️ Unified Effort Scale
105
190
 
106
191
  Control reasoning depth with one ModelMix policy value (`-1` adaptive, or `0`–`100`). It lives **outside** native `options` and is mapped to each provider’s effort API at request time.
@@ -120,13 +205,26 @@ ModelMix.new().effort(-1).minimaxM3().addText('...').message();
120
205
  |--|------|-------|-------|-------|--------|------|
121
206
  | OpenAI | `none` | `low` | `medium` | `high` | `xhigh` | — |
122
207
  | Anthropic | `low` | `medium` | `high` | `xhigh` | `max` | adaptive |
123
- | Gemini 3+\* | `minimal` | `low` | `medium` | `high` | — | dynamic |
208
+ | Gemini 3+ | `minimal` | `low` | `medium` | `high` | — | dynamic |
124
209
  | DeepSeek V4 | off | `low`↑ | `high`↑ | `high`↑ | `max`↑ | — |
125
210
  | MiniMax M3 | off | adaptive | adaptive | adaptive | adaptive | adaptive |
126
211
 
127
- \* Gemini bands: 0–24 / 25–49 / 50–74 / 75–100. DeepSeek `↑` = thinking on; `off` = thinking disabled. MiniMax `off`/`adaptive` = `thinking.disabled` / `thinking.type=adaptive`. Gemini 2.5 maps 0–100 to `thinkingBudget`. Anthropic maps adaptive thinking + `output_config.effort` on Claude 5 / Fable / Opus 4.6+ / Sonnet 4.6+; older models (Sonnet 4.5, Haiku 4.5) get `thinking.type=enabled` + `budget_tokens`. Grok 4.6 clamps 0–39 / 40–59 / 60–79 / 80–100 to `low` / `medium` / `high` / `xhigh`; without effort it uses the native `high` default. `-1` = provider adaptive/dynamic when available, else no-op. Levels clamp to what each model supports.
212
+ ### Provider-specific behavior
213
+
214
+ - **Gemini:** Gemini 3+ uses bands 0–24 / 25–49 / 50–74 / 75–100. Gemini 3.7 Flash clamps these bands to `low` / `low` / `medium` / `high`; `-1` leaves its native `medium` default unchanged. Gemini 2.5 maps 0–100 to `thinkingBudget`.
215
+ - **DeepSeek:** `↑` means thinking is enabled; `off` means it is disabled.
216
+ - **MiniMax:** `off` maps to `thinking.disabled`; `adaptive` maps to `thinking.type=adaptive`.
217
+ - **Anthropic:** Claude 5, Fable, Opus 4.6+, and Sonnet 4.6+ use adaptive thinking with `output_config.effort`. Sonnet 4.5 and Haiku 4.5 use `thinking.type=enabled` with `budget_tokens`.
218
+ - **Grok 4.6:** 0–39 / 40–59 / 60–79 / 80–100 map to `low` / `medium` / `high` / `xhigh`. Without effort, Grok uses its native `high` default.
128
219
 
129
- Migration: former `*think()` shorthands are removed use `.effort(n).<model>()` (or any 0–100 / `-1`). Kimi: `kimiK25()` / `kimiK26()`. Grok 4.20: `.grok420()` is non-reasoning; `.effort(20+).grok420()` (or `-1`) selects the reasoning model.
220
+ `-1` uses the provider's adaptive or dynamic mode when available; otherwise it is a no-op. Effort levels are clamped to each model's supported range.
221
+
222
+ ### Migrating from thinking shorthands
223
+
224
+ The former `*think()` methods were removed. Use `.effort(n).<model>()` with `0`–`100` or `-1` instead.
225
+
226
+ - **Kimi:** use `kimiK25()` or `kimiK26()`.
227
+ - **Grok 4.20:** `.grok420()` selects the non-reasoning model. Use `.effort(20+).grok420()` or `.effort(-1).grok420()` to select the reasoning model.
130
228
 
131
229
  ## 🔧 Model Context Protocol (MCP) Integration
132
230
 
@@ -188,7 +286,8 @@ Here's a comprehensive list of available methods:
188
286
  | `sonnet46()` | Anthropic | claude-sonnet-4-6 | [\$3.00/\$15.00][2] |
189
287
  | `haiku45()` | Anthropic | claude-haiku-4-5-20251001 | [\$1.00/\$5.00][2] |
190
288
  | `gemini31pro()` | Google | gemini-3.1-pro-preview | [\$2.00/\$12.00][3] |
191
- | `gemini36flash()` | Google | gemini-3.6-flash | [\$1.50/\$7.50][3] |
289
+ | `gemini37flash()` | Google | gemini-3.7-flash | [\$0.75/\$3.75][3] |
290
+ | `gemini36flash()` | Google | gemini-3.6-flash | [\$0.75/\$3.75][3] |
192
291
  | `gemini35flash()` | Google | gemini-3.5-flash | [\$0.75/\$4.50][3] |
193
292
  | `gemini35flashLite()`| Google | gemini-3.5-flash-lite | [\$0.30/\$2.50][3] |
194
293
  | `gemini31flashLite()`| Google | gemini-3.1-flash-lite-preview | [\$0.25/\$1.50][3] |
@@ -213,6 +312,8 @@ Here's a comprehensive list of available methods:
213
312
  | `kimiK25()` | Together | Kimi-K2.5 | [\$0.50/\$2.80][7] |
214
313
  | `kimiK26()` | Fireworks | models/kimi-k2p6 | [\$0.95/\$4.00][10] |
215
314
 
315
+ Gemini 3.7 Flash and 3.6 Flash use Google's introductory standard pricing through December 31, 2026; standard rates double on January 1, 2027.
316
+
216
317
  [1]: https://platform.openai.com/docs/pricing "Pricing | OpenAI"
217
318
  [2]: https://docs.anthropic.com/en/docs/about-claude/pricing "Pricing - Anthropic"
218
319
  [3]: https://ai.google.dev/gemini-api/docs/pricing "Google AI for Developers"
@@ -661,6 +762,7 @@ Every response from `raw()` now includes a `tokens` object with the following st
661
762
  tokens: {
662
763
  input: 1200, // Total input tokens, including cache reads and writes
663
764
  output: 50, // Number of output tokens
765
+ thinking: 0, // Internal reasoning tokens reported separately
664
766
  total: 1250, // Total tokens used
665
767
  cached: 1024, // Input tokens read from cache
666
768
  cacheWrite: 0, // Input tokens written to cache
@@ -696,7 +798,7 @@ console.log(model.lastRaw.tokens);
696
798
  // Same normalized token and cost structure returned by raw()
697
799
  ```
698
800
 
699
- `cached` aggregates cache reads reported by the provider, while `cacheWrite` aggregates cache writes. Anthropic additionally exposes `cacheWrite5m` and `cacheWrite1h` because those writes cost 1.25× and 2× the normal input rate, respectively. `cacheSavings` compares cache reads with the normal input rate, `cacheWritePremium` compares writes with that rate, and `breakEvenHits` estimates how many complete future hits recover the current write premium. For Anthropic, `input` is normalized to include uncached input, cache reads, and cache writes. Missing usage or pricing categories return `0`. The `speed` field is the generation speed measured in output tokens per second (integer).
801
+ `thinking` contains internal reasoning tokens when a provider reports them separately; cost calculation bills them at the output rate. `cached` aggregates cache reads reported by the provider, while `cacheWrite` aggregates cache writes. Anthropic additionally exposes `cacheWrite5m` and `cacheWrite1h` because those writes cost 1.25× and 2× the normal input rate, respectively. `cacheSavings` compares cache reads with the normal input rate, `cacheWritePremium` compares writes with that rate, and `breakEvenHits` estimates how many complete future hits recover the current write premium. For Anthropic, `input` is normalized to include uncached input, cache reads, and cache writes. Missing usage or pricing categories return `0`. The `speed` field is the generation speed measured in output tokens per second (integer).
700
802
 
701
803
  ### GPT-5.6 prompt caching
702
804
 
@@ -878,7 +980,7 @@ new ModelMix(args = { options: {}, config: {} })
878
980
  - `message`: The text response from the model
879
981
  - `think`: Reasoning/thinking content (if available)
880
982
  - `toolCalls`: Array of tool calls made by the model (if any)
881
- - `tokens`: Normalized token counts (`input`, `output`, `total`, `cached`, `cacheWrite`, `cacheWrite5m`, `cacheWrite1h`, `uncachedInput`, `cacheHitRate`), cache economics (`cacheSavings`, `cacheWritePremium`, `breakEvenHits`), plus `cost`, `costBreakdown` (USD), and `speed` (output tokens/sec)
983
+ - `tokens`: Normalized token counts (`input`, `output`, `thinking`, `total`, `cached`, `cacheWrite`, `cacheWrite5m`, `cacheWrite1h`, `uncachedInput`, `cacheHitRate`), cache economics (`cacheSavings`, `cacheWritePremium`, `breakEvenHits`), plus `cost`, `costBreakdown` (USD), and `speed` (output tokens/sec)
882
984
  - `response`: The raw API response
883
985
  - `stream(callback)`: Sends the message and streams the response, invoking the callback with each streamed part.
884
986
  - `json(schemaExample, descriptions = {}, options = {})`: Forces the model to return a response in a specific JSON format.
@@ -0,0 +1,465 @@
1
+ # ModelMix Plugin Architecture and RLM Plugin Specification
2
+
3
+ Status: living draft
4
+
5
+ This document records the product and engineering decisions for adding a generic
6
+ plugin architecture to ModelMix and implementing Recursive Language Model (RLM)
7
+ behavior in a separately installed plugin. Confirmed decisions are normative.
8
+ Items marked **Open** are not implementation-ready and will be resolved before
9
+ development starts.
10
+
11
+ ## 1. Goals
12
+
13
+ ### Generic ModelMix plugin architecture
14
+
15
+ - Let a plugin extend one `ModelMix` instance without affecting other instances.
16
+ - Let plugins inspect or transform a prepared request before provider execution.
17
+ - Let plugins wrap an execution as middleware and either call the next handler or
18
+ return the final result themselves.
19
+ - Let plugins start child ModelMix executions with explicit control over plugin
20
+ inheritance.
21
+ - Keep plugin-specific dependencies and behavior out of the ModelMix package.
22
+
23
+ ### RLM plugin
24
+
25
+ - Let applications submit prompts and documents that would otherwise consume a
26
+ very large context window.
27
+ - Keep bulk document content outside the planner model's context.
28
+ - Represent structured input as programmatically accessible variables.
29
+ - Let a planner model generate a program that inspects those variables,
30
+ decomposes an abstract task, and invokes other language models recursively.
31
+ - Let the planner choose faster, cheaper, or more capable workers for each
32
+ subtask using an explicit worker catalog.
33
+ - Execute model-generated code in a restricted environment with controlled
34
+ resource usage.
35
+
36
+ Translation is an example workload, not a special RLM operation. The same
37
+ mechanism must support summarization, extraction, analysis, rewriting,
38
+ classification, synthesis, and other tasks.
39
+
40
+ ## 2. Non-goals
41
+
42
+ - ModelMix will not contain Markdown parsing, code isolation, RLM prompts, worker
43
+ selection policy, or recursive orchestration logic.
44
+ - The ModelMix package will not depend on `isolated-vm` or the parser libraries
45
+ selected by the RLM plugin.
46
+ - ModelMix will not assign universal intelligence rankings to models.
47
+ - The initial implementation will not silently activate plugins globally.
48
+
49
+ ## 3. Package boundary
50
+
51
+ The feature is split into two deliverables:
52
+
53
+ 1. **ModelMix core:** generic plugin registration, middleware execution, child
54
+ invocation, execution metadata, and TypeScript declarations.
55
+ 2. **RLM plugin:** document structuring, planner instructions, worker catalog,
56
+ recursion policy, sandbox execution, limits, and RLM-specific diagnostics.
57
+
58
+ `@modelmix/rlm` is used as a working package name in examples and is not yet a
59
+ confirmed publication name.
60
+
61
+ ## 4. Instance-scoped registration
62
+
63
+ A plugin is installed separately and activated explicitly on an instance:
64
+
65
+ ```js
66
+ const { ModelMix } = require('modelmix');
67
+ const { rlm } = require('@modelmix/rlm');
68
+
69
+ const mmix = ModelMix.new()
70
+ .gpt56luna()
71
+ .use(rlm({
72
+ maxDepth: 3
73
+ }));
74
+ ```
75
+
76
+ Confirmed behavior:
77
+
78
+ - `.use(plugin)` affects only that instance and instances that inherit from it.
79
+ - Registering a plugin does not change global ModelMix behavior.
80
+ - Instances without plugins retain their existing execution behavior.
81
+
82
+ ## 5. Generic middleware contract
83
+
84
+ A plugin can wrap the complete ModelMix execution:
85
+
86
+ ```js
87
+ const plugin = {
88
+ name: 'example',
89
+
90
+ async execute(context, next) {
91
+ // Option A: inspect or modify the request, then continue.
92
+ return next();
93
+
94
+ // Option B: perform custom work and return a complete result without
95
+ // invoking the provider handler or later middleware.
96
+ }
97
+ };
98
+ ```
99
+
100
+ The execution context needs a provider-neutral request representation:
101
+
102
+ ```ts
103
+ interface PluginExecutionContext {
104
+ request: {
105
+ system: string;
106
+ messages: ChatMessage[];
107
+ options: ModelMixOptions;
108
+ config: ModelMixConfig;
109
+ outputMode: 'message' | 'json' | 'block' | 'raw' | 'stream';
110
+ };
111
+ execution: {
112
+ executionId: string;
113
+ parentExecutionId: string | null;
114
+ depth: number;
115
+ };
116
+ invoke(input: ChildInvocation): Promise<ModelMixResult>;
117
+ }
118
+ ```
119
+
120
+ Confirmed behavior:
121
+
122
+ - Calling `next()` continues the middleware chain and eventually invokes the
123
+ selected provider.
124
+ - A plugin can short-circuit the chain and return the final result.
125
+ - A short-circuit result must satisfy the normal `ModelMixResult` contract so
126
+ that `.message()`, `.json()`, `.block()`, and `.raw()` remain consistent.
127
+ - RLM-specific concepts must not appear in this core interface.
128
+
129
+ **Open:** exact mutability rules for `context.request`, middleware ordering,
130
+ duplicate plugin names, registration-time lifecycle hooks, teardown, and error
131
+ hooks.
132
+
133
+ ## 6. Child invocation and plugin inheritance
134
+
135
+ Plugins can start independent child executions through `context.invoke()`:
136
+
137
+ ```js
138
+ const result = await context.invoke({
139
+ system: 'Extract the named entities from this section.',
140
+ messages: [{ role: 'user', content: section }],
141
+ plugins: 'inherit',
142
+ history: false
143
+ });
144
+ ```
145
+
146
+ Confirmed defaults:
147
+
148
+ - Child executions inherit all plugins, including the plugin that created the
149
+ child invocation.
150
+ - Child executions never inherit conversation history by default.
151
+ - A child receives only the system prompt, messages, tools, options, and other
152
+ inputs explicitly supplied for that invocation.
153
+ - The runtime records `executionId`, `parentExecutionId`, and `depth` across the
154
+ execution tree.
155
+ - `.new()` inherits registered plugins but starts without message history,
156
+ consistent with its current instance-creation behavior.
157
+
158
+ The generic API must support selective inheritance:
159
+
160
+ ```js
161
+ plugins: 'inherit'
162
+ plugins: 'none'
163
+ plugins: { exclude: ['rlm'] }
164
+ plugins: { include: ['metrics'] }
165
+ ```
166
+
167
+ **Open:** whether `history: true` will be supported at all. RLM child calls will
168
+ always use `history: false`, even if the generic architecture later permits
169
+ other plugins to request history explicitly.
170
+
171
+ ## 7. Recursive depth
172
+
173
+ RLM uses an explicitly configured recursion limit:
174
+
175
+ ```js
176
+ rlm({ maxDepth: 3 })
177
+ ```
178
+
179
+ The current depth definition is:
180
+
181
+ | Depth | Meaning |
182
+ |---:|---|
183
+ | 0 | Initial planning execution |
184
+ | 1 | First recursive decomposition |
185
+ | 2 | Second recursive decomposition |
186
+ | 3 | Last RLM-enabled decomposition |
187
+ | 4 | Direct leaf execution with RLM excluded |
188
+
189
+ `maxDepth` is required. Registration must fail early when it is absent, invalid,
190
+ or negative. There is no implicit fallback value.
191
+
192
+ Current working decision: when the next invocation would exceed `maxDepth`, the
193
+ runtime excludes only RLM and sends the selected fragment directly to the chosen
194
+ worker. Other inherited plugins remain active.
195
+
196
+ ## 8. Worker catalog and model selection
197
+
198
+ The initial planner must be able to choose among named workers with different
199
+ capability, cost, and speed characteristics. Each worker is a ModelMix instance
200
+ and can therefore contain its own provider fallback chain.
201
+
202
+ Proposed configuration:
203
+
204
+ ```js
205
+ const workers = {
206
+ fast: {
207
+ model: ModelMix.new().gpt41nano(),
208
+ intelligence: 1,
209
+ cost: 1,
210
+ speed: 5,
211
+ description: 'Extraction, classification, and simple transformations'
212
+ },
213
+ balanced: {
214
+ model: ModelMix.new().gpt41mini(),
215
+ intelligence: 3,
216
+ cost: 2,
217
+ speed: 4,
218
+ description: 'General analysis and writing'
219
+ },
220
+ expert: {
221
+ model: ModelMix.new().gpt56luna(),
222
+ intelligence: 5,
223
+ cost: 5,
224
+ speed: 2,
225
+ description: 'Complex reasoning and final synthesis'
226
+ }
227
+ };
228
+
229
+ const mmix = ModelMix.new()
230
+ .gpt56luna()
231
+ .use(rlm({ maxDepth: 3, workers }));
232
+ ```
233
+
234
+ Confirmed requirements:
235
+
236
+ - The planner receives a manifest containing worker names and decision metadata,
237
+ not ModelMix objects, credentials, or provider internals.
238
+ - The generated program selects a worker by its registered name.
239
+ - Different subtasks in the same execution can use different workers.
240
+ - Independent calls can use different workers concurrently.
241
+ - Worker intelligence is developer-supplied rather than inferred by ModelMix.
242
+ - ModelMix pricing data can supplement the relative cost rating when pricing is
243
+ available.
244
+ - A worker can define its own fallback chain.
245
+ - RLM supports both an explicit worker pool and inherited use of the parent
246
+ instance's model chain.
247
+
248
+ Proposed sandbox API:
249
+
250
+ ```js
251
+ const entities = await query({
252
+ worker: 'fast',
253
+ system: 'Extract named entities.',
254
+ message: chapter
255
+ });
256
+ ```
257
+
258
+ **Open:** exact rating scale, whether the parent chain appears as a `default`
259
+ worker, worker capability tags, monetary price representation, and enforcement
260
+ of budgets for cost, calls, generated tokens, wall time, and concurrency.
261
+
262
+ ## 9. RLM input representation
263
+
264
+ The planner must not receive the full large document. It receives:
265
+
266
+ - the user's task;
267
+ - a compact description of the external data environment;
268
+ - variable names, types, sizes, and structural relationships;
269
+ - the worker manifest;
270
+ - the callable sandbox API;
271
+ - execution limits and output requirements.
272
+
273
+ The document remains available only inside the sandbox as structured variables.
274
+ For Markdown input, the intended semantic mapping is:
275
+
276
+ - headings define named sections and hierarchy;
277
+ - chapter-level sections become individually addressable variables;
278
+ - bullet and numbered lists become arrays when their structure permits it;
279
+ - nested headings become nested objects or an equivalent traversable tree;
280
+ - prose content remains external and is described by metadata such as character
281
+ count rather than copied into the planner prompt;
282
+ - ordering and enough source metadata are preserved to reconstruct an output in
283
+ the original document order.
284
+
285
+ For example, a ten-chapter book should expose ten addressable chapter values
286
+ instead of one opaque string. Generated code can select, inspect, partition, and
287
+ process those values without loading the entire book into the planner context.
288
+
289
+ ### Variable metadata
290
+
291
+ The planner receives a content-free descriptor for every external variable. The
292
+ descriptor is operational input, not debug-only information: generated programs
293
+ use it together with the execution limits to decide whether to process a value
294
+ directly, batch array items, or split large strings at semantic boundaries.
295
+
296
+ Confirmed metadata:
297
+
298
+ - every variable has a stable path, type, and estimated serialized size;
299
+ - sizes use UTF-8 bytes of the JSON-serialized value so they are deterministic
300
+ across JavaScript runtimes; strings also expose raw UTF-8 bytes;
301
+ - strings expose Unicode character, UTF-16 code-unit, line, and paragraph counts;
302
+ - arrays expose item count, element-type distribution, total serialized size,
303
+ and minimum, maximum, and average serialized item size;
304
+ - homogeneous string arrays expose aggregate character, byte, line, and
305
+ paragraph statistics;
306
+ - object arrays expose a content-free field manifest with presence, type, and
307
+ string-size statistics instead of listing every item;
308
+ - objects expose key count and recursively described properties;
309
+ - descriptors never contain source strings, samples, credentials, ModelMix
310
+ objects, or provider configuration;
311
+ - non-JSON-serializable values and circular references fail before planning.
312
+
313
+ The planner prompt includes the sandbox memory limit and maximum query payload
314
+ size beside the descriptor. It must instruct generated code to compare those
315
+ limits with variable and item sizes, split oversized strings at paragraph or
316
+ other semantic boundaries, batch compatible array items, process independent
317
+ work concurrently within the configured limit, and preserve source order when
318
+ reassembling output.
319
+
320
+ RLM prompts are developer-controlled Markdown templates stored in the plugin
321
+ package. They use ModelMix's existing EJS contract and are rendered through
322
+ `assign()` plus `setSystemFromFile()` on the planner execution. The plugin does
323
+ not own a second template renderer. Manifest JSON, limits, worker metadata, and
324
+ planning hints are assigned data, rendered once, and may not recursively execute
325
+ EJS contained in variable paths or other runtime values. Relative Markdown
326
+ includes use ModelMix's normal file-template resolution.
327
+
328
+ The byte count estimates serialized transfer size rather than the JavaScript
329
+ engine's heap usage. Runtime heap consumption is implementation-dependent and
330
+ is enforced separately by the sandbox memory limit.
331
+
332
+ **Open:** exact variable naming, duplicate headings, introductory content,
333
+ heading depth, code blocks, tables, links, frontmatter, mixed content, malformed
334
+ Markdown, non-Markdown prompts, and round-trip fidelity.
335
+
336
+ ## 10. RLM execution flow
337
+
338
+ The intended high-level flow is:
339
+
340
+ 1. ModelMix renders its system and message templates normally.
341
+ 2. The RLM middleware receives the prepared provider-neutral request.
342
+ 3. RLM separates the task instructions from large data-bearing content.
343
+ 4. RLM parses structured content and creates the external variable environment.
344
+ 5. RLM assigns the variable manifest, workers, callable API, limits, and output
345
+ requirements to its Markdown planner template and lets ModelMix render it.
346
+ 6. The initial model generates an executable orchestration program.
347
+ 7. RLM validates and runs the program inside the restricted sandbox.
348
+ 8. The program inspects variables and calls named workers through `query()`.
349
+ 9. Independent subtasks run concurrently when allowed by configured limits.
350
+ 10. Recursive calls re-enter RLM without history until the depth boundary.
351
+ 11. The program combines intermediate results and returns the final value.
352
+ 12. RLM converts that value into a normal `ModelMixResult`.
353
+
354
+ ## 11. Isolation and security boundary
355
+
356
+ The RLM package owns the sandbox implementation and its dependency on
357
+ `isolated-vm` or a future replacement.
358
+
359
+ Required properties:
360
+
361
+ - Generated code cannot access Node.js globals, the filesystem, environment
362
+ variables, network APIs, credentials, or ModelMix instances.
363
+ - Only serializable document variables and explicitly registered callbacks enter
364
+ the sandbox.
365
+ - The only LLM operation exposed to generated code is the validated `query()`
366
+ interface.
367
+ - Worker names and invocation arguments are validated outside the sandbox.
368
+ - Memory, execution time, recursion, calls, concurrency, and output size are
369
+ enforceable outside model instructions.
370
+ - Sandbox disposal occurs on success, failure, timeout, or cancellation.
371
+
372
+ **Open:** concrete limit configuration and whether generated code is accepted
373
+ directly or parsed against a restricted JavaScript subset before execution.
374
+
375
+ ## 12. Output modes
376
+
377
+ Because a plugin may complete an execution, RLM must preserve the caller's
378
+ chosen output mode:
379
+
380
+ - `.message()` returns a string.
381
+ - `.block()` returns extracted block content according to the existing contract.
382
+ - `.json()` returns data satisfying the requested schema.
383
+ - `.raw()` returns a complete `ModelMixResult`.
384
+ - `.stream()` requires an explicit streaming design.
385
+
386
+ **Open:** whether the first RLM version supports streaming. Recursive parallel
387
+ work does not naturally form one ordered token stream, so the likely initial
388
+ contract is either buffered final output or explicit rejection of `.stream()`.
389
+
390
+ ## 13. Observability and accounting
391
+
392
+ An RLM result should make the execution tree inspectable without exposing prompt
393
+ content by default. Candidate metadata includes:
394
+
395
+ - planner and worker calls;
396
+ - selected worker per call;
397
+ - parent/child execution identifiers and depth;
398
+ - elapsed time and concurrency;
399
+ - input, output, cached, and cache-write tokens;
400
+ - estimated cost per call and aggregate cost;
401
+ - termination reason, including depth or budget boundaries.
402
+
403
+ **Open:** the public shape of this metadata and how it integrates with
404
+ `lastRaw.tokens`, debug levels, and plugins that also collect metrics.
405
+
406
+ ## 14. Verification requirements
407
+
408
+ ### ModelMix core
409
+
410
+ - Plugins are isolated per instance.
411
+ - `.new()` inherits plugins without inheriting history.
412
+ - Middleware executes in deterministic order.
413
+ - A plugin can transform a request and call `next()`.
414
+ - A plugin can return a complete result without calling `next()`.
415
+ - Child invocations support inherit, none, include, and exclude policies.
416
+ - Execution identifiers and depth are correct across nested calls.
417
+ - Plugin failures do not silently fall through to providers.
418
+ - Existing behavior remains unchanged when no plugins are registered.
419
+ - All supported non-streaming output modes preserve their contracts.
420
+
421
+ ### RLM package
422
+
423
+ - Markdown fixtures produce stable semantic variable environments.
424
+ - Large content is absent from the planner request.
425
+ - The planner can choose among named workers.
426
+ - Independent queries execute concurrently within configured limits.
427
+ - Recursive calls contain no conversation history.
428
+ - The depth boundary excludes RLM and terminates recursion.
429
+ - Invalid workers and malformed generated code fail clearly.
430
+ - Sandbox access to filesystem, environment, network, and Node APIs is denied.
431
+ - Time, memory, call, concurrency, and output limits are enforced.
432
+ - Results and accounting aggregate planner and worker executions.
433
+ - A large-document fixture covers an abstract operation end to end with mocked
434
+ providers; translation can be one example but not the only tested task.
435
+
436
+ ## 15. Evidence from the current laboratory
437
+
438
+ The prototypes under `demo/lab/` establish the initial direction:
439
+
440
+ - `rlm-translate.js` describes external data to a planner, generates an async
441
+ JavaScript program, exposes an LLM callback, and executes the program in
442
+ `isolated-vm`.
443
+ - `rlm-story.js` demonstrates parallel and sequential query planning over an
444
+ abstract task without document input.
445
+ - The current translation prototype exposes the entire Markdown document as one
446
+ `input` string and chunks it by character count. The production plugin must
447
+ replace this with semantic document structure and enforced runtime policy.
448
+
449
+ The generic hook belongs after ModelMix has rendered templates into a request
450
+ snapshot and before provider-specific message conversion. This preserves one
451
+ neutral plugin contract across OpenAI, Anthropic, Google, and other providers.
452
+
453
+ ## 16. Decisions still required before implementation
454
+
455
+ 1. Hard budgets and what happens when each budget is exhausted.
456
+ 2. Exact worker metadata and selection contract.
457
+ 3. Middleware ordering and plugin lifecycle.
458
+ 4. Request mutability and validation between middleware stages.
459
+ 5. Markdown-to-variable mapping and non-Markdown behavior.
460
+ 6. Separation of task instructions from data-bearing prompt content.
461
+ 7. Tools available to child executions.
462
+ 8. Output schema propagation and final-result validation.
463
+ 9. Streaming and cancellation behavior.
464
+ 10. Metrics, token accounting, and debug representation.
465
+ 11. Package name and supported module formats.
package/demo/gemini.js CHANGED
@@ -12,9 +12,9 @@ const mmix = new ModelMix({
12
12
  }
13
13
  });
14
14
 
15
- // Using gemini3flash (Gemini 3 Flash) with built-in method
16
- console.log("\n" + '--------| gemini25flash() |--------');
17
- const flash = await mmix.gemini3flash()
15
+ // Using Gemini 3.7 Flash with the built-in method
16
+ console.log("\n" + '--------| gemini37flash() |--------');
17
+ const flash = await mmix.gemini37flash()
18
18
  .addText('Hi there! Do you like cats?')
19
19
  .message();
20
20
 
@@ -41,4 +41,3 @@ const customModel = mmix.new().attach('gemini-2.5-flash', new MixGoogle());
41
41
 
42
42
  const custom = await customModel.addText('Tell me a short joke about cats.').message();
43
43
  console.log(custom);
44
-
package/demo/short.js CHANGED
@@ -11,7 +11,7 @@ const setup = {
11
11
  const mmix = await ModelMix.new(setup)
12
12
  .sonnet46() // (main model) Anthropic claude-sonnet-4-6
13
13
  .gpt56luna() // (fallback 1) OpenAI gpt-5.6-luna
14
- .gemini36flash({ config: { temperature: 0 } }) // (fallback 2) Google gemini-3.6-flash
14
+ .gemini37flash() // (fallback 2) Google gemini-3.7-flash
15
15
  .gpt41nano() // (fallback 3) OpenAI gpt-4.1-nano
16
16
  .grok46() // (fallback 4) Grok grok-4.6
17
17
  .addText("What's your name?");