@hicaru/pi-rlm 0.3.21 → 0.3.22
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 +58 -46
- package/package.json +1 -1
- package/src/ui/theme-adapter.ts +85 -3
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<p align="center">
|
|
6
6
|
<a href="https://www.npmjs.com/package/@hicaru/pi-rlm"><img src="https://img.shields.io/npm/v/@hicaru/pi-rlm?color=cb3837&logo=npm" alt="npm version"></a>
|
|
7
7
|
<a href="https://github.com/openzebra/rlm.pi/blob/master/pi-plugin/rlm/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
|
|
8
|
-
<a href="https://github.com/earendil-works/pi"><img src="https://img.shields.io/badge/for-Pi-7c3aed" alt="Built for Pi"></a>
|
|
8
|
+
<a href="https://github.com/earendil-works/pi"><img src="https://img.shields.io/badge/for-Pi%20%C2%B7%20omp-7c3aed" alt="Built for Pi and oh-my-pi"></a>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
@@ -17,17 +17,17 @@
|
|
|
17
17
|
## Install
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
pi install npm:@hicaru/pi-rlm
|
|
20
|
+
pi install npm:@hicaru/pi-rlm # Pi
|
|
21
|
+
omp plugin install @hicaru/pi-rlm # oh-my-pi
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
Toggle with `Ctrl+Shift+R` or `/rlm`.
|
|
24
|
+
Then `/reload` (or restart) — `/rlm`, `/rlm-config`, `/rlm-stop` appear under
|
|
25
|
+
**[Extensions]**. Toggle with `Ctrl+Shift+R` or `/rlm`.
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
pi uninstall
|
|
30
|
-
```
|
|
27
|
+
| | install | upgrade | remove |
|
|
28
|
+
| ------------ | ----------------------------------- | ------------------------------------------- | ------------------------------------- |
|
|
29
|
+
| **Pi** | `pi install npm:@hicaru/pi-rlm` | `pi install npm:@hicaru/pi-rlm --force` | `pi uninstall npm:@hicaru/pi-rlm` |
|
|
30
|
+
| **oh-my-pi** | `omp plugin install @hicaru/pi-rlm` | `omp plugin install @hicaru/pi-rlm --force` | `omp plugin uninstall @hicaru/pi-rlm` |
|
|
31
31
|
|
|
32
32
|
<p align="center">
|
|
33
33
|
<img src="https://github.com/openzebra/rlm.pi/blob/master/assets/hero.png?raw=true" width="100%" alt="rlm.pi — OOLONG benchmark results">
|
|
@@ -46,14 +46,17 @@ models, recursively. Same Pi session, same tools, same keys: `/rlm` and go. Read
|
|
|
46
46
|
**OOLONG (oolong-synth)** — paper-tier long-context suite (the only suite); latest
|
|
47
47
|
journal per model, cost per task from real `costUsd`:
|
|
48
48
|
|
|
49
|
-
| Model
|
|
50
|
-
|
|
51
|
-
| `zai/glm-4.7`
|
|
52
|
-
| `qwen/qwen3.8-27b`
|
|
53
|
-
| `inception/mercury-2.5` | 38% | $0.0052
|
|
49
|
+
| Model | Score | Avg. tokens/task | Avg. cost/task |
|
|
50
|
+
| ---------------------------------- | ----------------- | ---------------- | -------------- |
|
|
51
|
+
| `zai/glm-4.7` | **91.7%** (22/24) | ~36k | $0.0000 * |
|
|
52
|
+
| `openrouter/qwen/qwen3.8-27b` | 49% | ~36k | $0.1038 |
|
|
53
|
+
| `openrouter/inception/mercury-2.5` | 38% | ~36k | $0.0052 |
|
|
54
54
|
|
|
55
55
|
\* glm-4.7 runs on Z.ai's coding-plan endpoint — subscription billing, `costUsd` stays $0.
|
|
56
56
|
|
|
57
|
+
In the [RLM paper](https://arxiv.org/abs/2512.24601), GPT-5-mini driven as an RLM outperforms
|
|
58
|
+
GPT-o3 on OOLONG — recursion beats raw context, at a fraction of the price.
|
|
59
|
+
|
|
57
60
|
Raw per-task rows (correct, recall, latency, tokens, cost) live in
|
|
58
61
|
`bench/runs/*.jsonl` — one JSONL row per task, committed as history.
|
|
59
62
|
|
|
@@ -99,17 +102,17 @@ One suite (`oolong`). Regenerate the hero chart:
|
|
|
99
102
|
|
|
100
103
|
Drop ANY of these into `add_context()` — they auto-convert to Markdown and land in `context`:
|
|
101
104
|
|
|
102
|
-
| Category
|
|
103
|
-
|
|
104
|
-
| **Word**
|
|
105
|
-
| **PDF**
|
|
106
|
-
| **PowerPoint**
|
|
107
|
-
| **Excel**
|
|
108
|
-
| **EPUB**
|
|
109
|
-
| **Rich Text**
|
|
110
|
-
| **OpenDocument**
|
|
111
|
-
| **CSV / TSV**
|
|
112
|
-
| **HTML / XML**
|
|
105
|
+
| Category | Formats |
|
|
106
|
+
| --------------------- | -------------------------------------------------------------------------- |
|
|
107
|
+
| **Word** | `.docx` |
|
|
108
|
+
| **PDF** | `.pdf` |
|
|
109
|
+
| **PowerPoint** | `.pptx` |
|
|
110
|
+
| **Excel** | `.xlsx` |
|
|
111
|
+
| **EPUB** | `.epub` |
|
|
112
|
+
| **Rich Text** | `.rtf` |
|
|
113
|
+
| **OpenDocument** | `.odt` |
|
|
114
|
+
| **CSV / TSV** | `.csv` `.tsv` |
|
|
115
|
+
| **HTML / XML** | `.html` `.htm` `.xml` `.rss` `.atom` |
|
|
113
116
|
| **+ Pandoc fallback** | `.doc` `.ppt` `.xls` `.pptm` `.xlsm` `.xlsb` `.ppsm` `.docm` `.odp` `.ods` |
|
|
114
117
|
|
|
115
118
|
```python
|
|
@@ -126,24 +129,33 @@ A **Recursive Language Model (RLM)** replaces `llm.completion(prompt)` with
|
|
|
126
129
|
launch sub-LLM and sub-RLM calls as ordinary Python functions — decomposing,
|
|
127
130
|
delegating, and synthesizing across a tree of models, not a single context window.
|
|
128
131
|
|
|
132
|
+
Each step of a run is one validated state transition (paper §3.2, §5.7):
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
A_t = (P, Σ_t, O_t) fixed prompt + execution state Σ_t + tool surface
|
|
136
|
+
ΔΣ_t = μ(A_t) the model emits a repl() patch, not prose
|
|
137
|
+
V(ΔΣ_t, Σ_t) deterministic validator — no crash paths
|
|
138
|
+
Σ_{t+1} = Σ_t ⊕ ΔΣ_t deep merge, null = delete
|
|
139
|
+
```
|
|
140
|
+
|
|
129
141
|
## Commands
|
|
130
142
|
|
|
131
|
-
| Command
|
|
132
|
-
|
|
133
|
-
| `/rlm`
|
|
134
|
-
| `/rlm-stop`
|
|
135
|
-
| `/rlm-config` |
|
|
143
|
+
| Command | Shortcut | What it does |
|
|
144
|
+
| ------------- | -------------- | ------------------------ |
|
|
145
|
+
| `/rlm` | `Ctrl+Shift+R` | Toggle RLM mode on/off |
|
|
146
|
+
| `/rlm-stop` | | Abort current run |
|
|
147
|
+
| `/rlm-config` | | Pick models, tune limits |
|
|
136
148
|
|
|
137
149
|
## Settings (`/rlm-config`)
|
|
138
150
|
|
|
139
|
-
| Setting
|
|
140
|
-
|
|
141
|
-
| Smart model
|
|
142
|
-
| Worker model
|
|
143
|
-
| Max recursion depth | `4`
|
|
144
|
-
| Max iterations
|
|
145
|
-
| REPL timeout
|
|
146
|
-
| Max concurrent subs | `16`
|
|
151
|
+
| Setting | Default | Why you'd change it |
|
|
152
|
+
| ------------------- | ------------------ | ------------------------------------------- |
|
|
153
|
+
| Smart model | Pi's active | Use your best model as orchestrator |
|
|
154
|
+
| Worker model | cheapest available | Free/cheap model for leaf `llm_query` calls |
|
|
155
|
+
| Max recursion depth | `4` | Deeper trees for harder problems |
|
|
156
|
+
| Max iterations | `30` | Longer runs for complex tasks |
|
|
157
|
+
| REPL timeout | `120`s | Bump for slow computations |
|
|
158
|
+
| Max concurrent subs | `16` | More parallelism (costs RAM) |
|
|
147
159
|
|
|
148
160
|
## Sampling & reproducibility
|
|
149
161
|
|
|
@@ -152,14 +164,14 @@ temperature 0 took OOLONG from 71% pooled / 40% flips to 91.7–100% all-stable
|
|
|
152
164
|
total. Those knobs are first-class in `rlm.json` (`~/.pi/agent/rlm.json`) and on the
|
|
153
165
|
`/rlm-config` panel:
|
|
154
166
|
|
|
155
|
-
| Field
|
|
156
|
-
|
|
157
|
-
| `rootSampling.maxTokens`
|
|
158
|
-
| `rootSampling.temperature` | rlm.json, panel | provider default | Root + finalize sampling temperature; `0` = deterministic
|
|
159
|
-
| `smartReasoning`
|
|
160
|
-
| `subSampling.maxTokens`
|
|
161
|
-
| `subSampling.temperature`
|
|
162
|
-
| `enableVerificationNudge`
|
|
167
|
+
| Field | Where | Default | What it governs |
|
|
168
|
+
| -------------------------- | --------------- | ---------------- | ------------------------------------------------------------------------------- |
|
|
169
|
+
| `rootSampling.maxTokens` | rlm.json, panel | `16384` | Output cap per root-model turn (finalize included) |
|
|
170
|
+
| `rootSampling.temperature` | rlm.json, panel | provider default | Root + finalize sampling temperature; `0` = deterministic |
|
|
171
|
+
| `smartReasoning` | rlm.json, panel | none | Thinking effort for the root model |
|
|
172
|
+
| `subSampling.maxTokens` | rlm.json, panel | `8192` | Output cap per leaf sub-call (`llm_query`, `llm_batch`, `map_files`) |
|
|
173
|
+
| `subSampling.temperature` | rlm.json, panel | provider default | Leaf sampling temperature |
|
|
174
|
+
| `enableVerificationNudge` | rlm.json | off | One coached redo when the root finalizes early with a bare number / short label |
|
|
163
175
|
|
|
164
176
|
**Reproducibility recipe (validated by r3):**
|
|
165
177
|
|
package/package.json
CHANGED
package/src/ui/theme-adapter.ts
CHANGED
|
@@ -5,19 +5,87 @@
|
|
|
5
5
|
* loaded through jiti, which gives them a separate module cache, so that global can be
|
|
6
6
|
* `undefined` inside a plugin — pi documents this footgun on `DynamicBorder`. Every renderer
|
|
7
7
|
* pi calls hands us a live `Theme`, so we build the adapter from that instead of the global.
|
|
8
|
+
*
|
|
9
|
+
* Fork hosts (oh-my-pi) remap `@earendil-works/pi-tui` to their own bundled copy, whose
|
|
10
|
+
* `Markdown` requires a nested `symbols` record on the theme (`theme.symbols.table/hrChar/
|
|
11
|
+
* quoteBorder/colorSwatch`) in addition to the upstream render functions. Their `Theme.md`
|
|
12
|
+
* getter is NOT a MarkdownTheme — it is a flat symbol-string partial
|
|
13
|
+
* (`{ quoteBorder, hrChar, bullet, colorSwatch }`), so returning it raw amputates the render
|
|
14
|
+
* functions and the host crashes (`this.#r.heading is not a function`). Hence the merge
|
|
15
|
+
* below: base functions from the injected theme, function-valued host overrides on top,
|
|
16
|
+
* and the flat strings folded into a complete `symbols` record with safe defaults.
|
|
8
17
|
*/
|
|
9
18
|
|
|
10
19
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
11
20
|
import type { MarkdownTheme } from "@earendil-works/pi-tui";
|
|
12
21
|
|
|
22
|
+
/** The `symbols` record fork hosts read off a MarkdownTheme (upstream has no such key). */
|
|
23
|
+
interface MarkdownThemeSymbols {
|
|
24
|
+
readonly colorSwatch: string;
|
|
25
|
+
readonly hrChar: string;
|
|
26
|
+
readonly quoteBorder: string;
|
|
27
|
+
readonly bullet?: string;
|
|
28
|
+
readonly table: Readonly<Record<string, string>>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Safe defaults covering every symbol a fork's `Markdown` dereferences (omp: table/hrChar/quoteBorder/colorSwatch). */
|
|
32
|
+
const FALLBACK_SYMBOLS: MarkdownThemeSymbols = Object.freeze({
|
|
33
|
+
colorSwatch: "●",
|
|
34
|
+
hrChar: "─",
|
|
35
|
+
quoteBorder: "▌",
|
|
36
|
+
table: Object.freeze({
|
|
37
|
+
horizontal: "─",
|
|
38
|
+
vertical: "│",
|
|
39
|
+
topLeft: "┌",
|
|
40
|
+
topRight: "┐",
|
|
41
|
+
bottomLeft: "└",
|
|
42
|
+
bottomRight: "┘",
|
|
43
|
+
teeUp: "┴",
|
|
44
|
+
teeDown: "┬",
|
|
45
|
+
teeLeft: "├",
|
|
46
|
+
teeRight: "┤",
|
|
47
|
+
cross: "┼",
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
type MarkdownThemeWithSymbols = MarkdownTheme & { symbols?: MarkdownThemeSymbols };
|
|
52
|
+
|
|
53
|
+
const isRecord = (v: unknown): v is Record<string, unknown> =>
|
|
54
|
+
typeof v === "object" && v !== null;
|
|
55
|
+
const asNonEmptyString = (v: unknown): string | undefined =>
|
|
56
|
+
typeof v === "string" && v.length > 0 ? v : undefined;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Fold the host's symbol surface into one complete `symbols` record. Forks expose the same
|
|
60
|
+
* chars in two shapes — nested (`theme.md.symbols.colorSwatch`) or flat
|
|
61
|
+
* (`theme.md.colorSwatch`) — and the host's values win over our safe defaults. `host.md`
|
|
62
|
+
* flat strings are also NOT spread over the base: `quoteBorder`/`hrChar` exist on the base
|
|
63
|
+
* as FUNCTIONS, so a naive spread would corrupt them.
|
|
64
|
+
*/
|
|
65
|
+
function hostSymbols(md: Record<string, unknown>): MarkdownThemeSymbols {
|
|
66
|
+
const nested = isRecord(md.symbols) ? md.symbols : {};
|
|
67
|
+
const fallback = FALLBACK_SYMBOLS as unknown as Record<string, unknown>;
|
|
68
|
+
const fallbackString = (k: string): string => (typeof fallback[k] === "string" ? fallback[k] : "");
|
|
69
|
+
const pick = (k: string): string => asNonEmptyString(nested[k]) ?? asNonEmptyString(md[k]) ?? fallbackString(k);
|
|
70
|
+
const hostTable = isRecord(nested.table) ? nested.table : {};
|
|
71
|
+
const table: Record<string, string> = { ...FALLBACK_SYMBOLS.table };
|
|
72
|
+
for (const k of Object.keys(table)) {
|
|
73
|
+
const v = asNonEmptyString(hostTable[k]);
|
|
74
|
+
if (v !== undefined) table[k] = v;
|
|
75
|
+
}
|
|
76
|
+
const bullet = asNonEmptyString(nested.bullet) ?? asNonEmptyString(md.bullet);
|
|
77
|
+
return Object.freeze({ colorSwatch: pick("colorSwatch"), hrChar: pick("hrChar"), quoteBorder: pick("quoteBorder"), bullet, table: Object.freeze(table) });
|
|
78
|
+
}
|
|
79
|
+
|
|
13
80
|
/**
|
|
14
81
|
* A `MarkdownTheme` derived from the theme pi passed to this render pass.
|
|
15
82
|
*
|
|
16
|
-
* `highlightCode`
|
|
17
|
-
*
|
|
83
|
+
* `highlightCode`/`resolveMermaidAscii` come through only if the host itself provides them
|
|
84
|
+
* as functions on `theme.md` — pi's own global-based implementation reads the broken
|
|
85
|
+
* module global and is deliberately never reconstructed here.
|
|
18
86
|
*/
|
|
19
87
|
export function markdownTheme(theme: Theme): MarkdownTheme {
|
|
20
|
-
|
|
88
|
+
const base: MarkdownTheme = {
|
|
21
89
|
heading: (text) => theme.fg("mdHeading", text),
|
|
22
90
|
link: (text) => theme.fg("mdLink", text),
|
|
23
91
|
linkUrl: (text) => theme.fg("mdLinkUrl", text),
|
|
@@ -33,4 +101,18 @@ export function markdownTheme(theme: Theme): MarkdownTheme {
|
|
|
33
101
|
underline: (text) => theme.underline(text),
|
|
34
102
|
strikethrough: (text) => theme.strikethrough(text),
|
|
35
103
|
};
|
|
104
|
+
const md: unknown = (theme as Theme & { md?: unknown }).md;
|
|
105
|
+
if (!isRecord(md)) {
|
|
106
|
+
const bare: MarkdownThemeWithSymbols = { ...base, symbols: hostSymbols({}) };
|
|
107
|
+
return bare;
|
|
108
|
+
}
|
|
109
|
+
// Function-valued host fields override the base (a real host MarkdownTheme wins);
|
|
110
|
+
// flat symbol strings stay out of the function surface and fold into `symbols`.
|
|
111
|
+
const hostFns: Record<string, unknown> = {};
|
|
112
|
+
for (const [k, v] of Object.entries(md)) {
|
|
113
|
+
if (typeof v === "function") hostFns[k] = v;
|
|
114
|
+
}
|
|
115
|
+
const merged = { ...base, ...hostFns } as MarkdownTheme & { symbols?: MarkdownThemeSymbols };
|
|
116
|
+
merged.symbols = hostSymbols(md);
|
|
117
|
+
return merged;
|
|
36
118
|
}
|