@hicaru/pi-rlm 0.3.20 → 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/commands/rlm.ts +14 -7
- package/src/config/defaults.ts +33 -10
- package/src/config/settings.ts +6 -0
- package/src/config/skillstate.ts +236 -44
- package/src/core/budget.ts +7 -3
- package/src/core/compaction.ts +2 -2
- package/src/core/engine.ts +87 -19
- package/src/core/root-context.ts +74 -21
- package/src/core/root-digest.ts +48 -11
- package/src/core/root-state.ts +39 -12
- package/src/core/run-state.ts +86 -14
- package/src/core/session-archive.ts +174 -0
- package/src/core/types.ts +6 -0
- package/src/index.ts +142 -12
- package/src/mode/rlm-mode.ts +2 -2
- package/src/prompts/glossary.ts +34 -5
- package/src/prompts/native.ts +8 -2
- package/src/prompts/user.ts +4 -3
- package/src/sandbox/py/__pycache__/retrieval.cpython-314.pyc +0 -0
- package/src/sandbox/py/__pycache__/scaffold.cpython-314.pyc +0 -0
- package/src/sandbox/py/retrieval.py +202 -36
- package/src/sandbox/py/scaffold.py +20 -5
- package/src/sandbox/py/worker.py +1 -1
- package/src/sandbox/sandbox-manager.ts +19 -0
- package/src/text/parsing.ts +133 -2
- package/src/text/tokens.ts +39 -4
- package/src/tool/repl-render.ts +38 -2
- package/src/tool/repl-tool.ts +34 -18
- package/src/tool/subcall-render.ts +7 -4
- package/src/ui/config-panel.ts +2 -2
- package/src/ui/intro.ts +1 -1
- package/src/ui/python-highlight.ts +49 -0
- package/src/ui/stage-cards.ts +192 -0
- package/src/ui/theme-adapter.ts +85 -3
- package/src/ui/tree/tree-model.ts +69 -19
- package/src/ui/tree/tree-rows.ts +2 -1
- package/src/util/abort.ts +34 -0
- package/src/util/bm25.ts +170 -21
- package/src/util/errors.ts +1 -1
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/commands/rlm.ts
CHANGED
|
@@ -4,7 +4,12 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
4
4
|
import type { RlmController } from "../mode/rlm-mode.ts";
|
|
5
5
|
import { setRlmModeStatus } from "../ui/status.ts";
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* `stopNative` aborts the native-mode session work (repl cells, child engines, detached
|
|
9
|
+
* spawn() tasks) and reports whether any was in flight. Optional so tests can register the
|
|
10
|
+
* bare command; production wires the closure accessors from src/index.ts.
|
|
11
|
+
*/
|
|
12
|
+
export function registerRlmCommand(pi: ExtensionAPI, controller: RlmController, stopNative?: () => boolean): void {
|
|
8
13
|
pi.registerCommand("rlm", {
|
|
9
14
|
description: "Toggle persistent RLM mode (route plain prompts through the RLM engine).",
|
|
10
15
|
handler: async (_args, ctx) => {
|
|
@@ -15,14 +20,16 @@ export function registerRlmCommand(pi: ExtensionAPI, controller: RlmController):
|
|
|
15
20
|
});
|
|
16
21
|
|
|
17
22
|
pi.registerCommand("rlm-stop", {
|
|
18
|
-
description: "Abort
|
|
23
|
+
description: "Abort in-progress RLM work: RLM runs, native repl cells, background tasks.",
|
|
19
24
|
handler: async (_args, ctx) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
const rlmBusy = controller.isBusy();
|
|
26
|
+
if (rlmBusy) controller.abort();
|
|
27
|
+
const nativeBusy = stopNative?.() ?? false;
|
|
28
|
+
if (rlmBusy || nativeBusy) {
|
|
29
|
+
ctx.ui.notify("RLM work aborted — runs, repl cells and background tasks stopped.", "info");
|
|
30
|
+
} else {
|
|
31
|
+
ctx.ui.notify("No RLM work in progress.", "info");
|
|
23
32
|
}
|
|
24
|
-
controller.abort();
|
|
25
|
-
ctx.ui.notify("RLM run aborted.", "info");
|
|
26
33
|
},
|
|
27
34
|
});
|
|
28
35
|
|
package/src/config/defaults.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import type { RlmConfig } from "../core/types.ts";
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Frozen default sub-LLM system prompt — avoids re-allocation on every llm_query call.
|
|
5
|
+
* Anthropic prompting canon applied: a role ("precise extraction assistant") and the
|
|
6
|
+
* hallucination out ("reply exactly NOT_FOUND when the material lacks the answer") — the
|
|
7
|
+
* convention the glossary documents so roots can branch on a leaf's NOT_FOUND instead of
|
|
8
|
+
* retrying identical prompts against slices that cannot answer.
|
|
9
|
+
*/
|
|
4
10
|
const DEFAULT_SUB_SYSTEM_PROMPT =
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
11
|
+
"You are a precise extraction and analysis assistant. " +
|
|
12
|
+
"Answer directly and concisely from the material provided in the prompt. " +
|
|
13
|
+
"Return only the requested information — no preamble, no meta-commentary, no explanation of your approach. " +
|
|
14
|
+
"If listing items, use compact bullet form. " +
|
|
15
|
+
"If the material does not contain the answer, reply exactly: NOT_FOUND";
|
|
8
16
|
|
|
9
17
|
export const DEFAULT_CONFIG: Readonly<RlmConfig> = Object.freeze({
|
|
10
18
|
enabled: true,
|
|
@@ -64,9 +72,11 @@ export const DEFAULT_CONFIG: Readonly<RlmConfig> = Object.freeze({
|
|
|
64
72
|
// v5 TaskLedger blackboard
|
|
65
73
|
enableLedger: true,
|
|
66
74
|
rlmBudget: 8,
|
|
67
|
-
// Verification-discipline nudge —
|
|
68
|
-
//
|
|
69
|
-
|
|
75
|
+
// Verification-discipline nudge — default ON (knowlange rec: 28/33 bench failures were
|
|
76
|
+
// early confident wrong answers; the RLM paper ships the same discipline). An early bare
|
|
77
|
+
// finalize — or one from a run that never inspected its context — gets ONE coached redo
|
|
78
|
+
// before the answer is accepted. Opt out via rlm.json (`"enableVerificationNudge": false`).
|
|
79
|
+
enableVerificationNudge: true,
|
|
70
80
|
// SKILL.state integration: Σ_t execution state + cross-session distilled knowledge.
|
|
71
81
|
// Paradigm flags are ENFORCED (R0, /tmp/ROOT_FULL_SKILLSTATE_PLAN.md) — validateEnforcedOn
|
|
72
82
|
// forces true whatever rlm.json carries; only calibrations are tunable.
|
|
@@ -78,7 +88,12 @@ export const DEFAULT_CONFIG: Readonly<RlmConfig> = Object.freeze({
|
|
|
78
88
|
enableSkillStateDistill: true, // ENFORCED — see /tmp/ROOT_FULL_SKILLSTATE_PLAN.md R0
|
|
79
89
|
skillStateMaxTokens: 1_200,
|
|
80
90
|
skillStateLeafTokens: 200,
|
|
81
|
-
|
|
91
|
+
// 2.5 (was 4.0 — recall W3): at 4.0 grounding silently no-oped on most prompts; 2.5 keeps
|
|
92
|
+
// the byte-identical-below-threshold contract while letting genuinely-relevant facts land.
|
|
93
|
+
skillStateMinScore: 2.5,
|
|
94
|
+
// Ξ block floor (recall W3): was Number.MIN_VALUE — every positively-scored stale note
|
|
95
|
+
// rode every root prompt. 2.0 admits relevant notes without the cross-session noise.
|
|
96
|
+
skillStateXiMinScore: 2.0,
|
|
82
97
|
skillStateNotesPerProject: 128,
|
|
83
98
|
// Root Σ integration (WS-2..WS-4): every LLM call assembles A_t = (P, Σ_t, O_t) — discard
|
|
84
99
|
// semantics on stale payloads + exactly one Σ snapshot splice, and model-proposed ΔΣ_t
|
|
@@ -89,8 +104,16 @@ export const DEFAULT_CONFIG: Readonly<RlmConfig> = Object.freeze({
|
|
|
89
104
|
rootDigestKeepRecentChars: 12_000,
|
|
90
105
|
rootDigestMaxChars: 8_000,
|
|
91
106
|
enableRootContextTransform: true, // ENFORCED — see /tmp/ROOT_FULL_SKILLSTATE_PLAN.md R0 (was soak-OFF pre-v2)
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
// Recall W4 calibration: 4 verbatim turns (was 2). SKILL.state's budget-matched ablation
|
|
108
|
+
// (Table 5/11) shows truncated windows collapse recall (0.18 vs structured 0.94); a 2-turn
|
|
109
|
+
// window sat dangerously close to that shape. Still O(1) per call; the session archive
|
|
110
|
+
// (rootArchiveMaxChars) makes anything older dereferenceable instead of gone.
|
|
111
|
+
rootContextKeepTurns: 4,
|
|
112
|
+
rootContextElideChars: 3_000,
|
|
113
|
+
// Recall W1: elided turns archive into the sandbox (ctx/session-log/*) so search()/
|
|
114
|
+
// grep_context() recall them — elision becomes dereferenceable, and the stubs stay honest.
|
|
115
|
+
// 0 disables the archive (stubs degrade to the plain Σ line).
|
|
116
|
+
rootArchiveMaxChars: 2_000_000,
|
|
94
117
|
rootContextSnapshot: true,
|
|
95
118
|
enableRootStateFences: true, // ENFORCED — see /tmp/ROOT_FULL_SKILLSTATE_PLAN.md R0 (was soak-OFF pre-v2)
|
|
96
119
|
});
|
package/src/config/settings.ts
CHANGED
|
@@ -167,6 +167,8 @@ export function validateConfig(raw: unknown): Partial<RlmConfig> {
|
|
|
167
167
|
if (skillStateLeafTokens !== undefined) out.skillStateLeafTokens = skillStateLeafTokens;
|
|
168
168
|
const skillStateMinScore = validateNumber(r.skillStateMinScore, 0);
|
|
169
169
|
if (skillStateMinScore !== undefined) out.skillStateMinScore = skillStateMinScore;
|
|
170
|
+
const skillStateXiMinScore = validateNumber(r.skillStateXiMinScore, 0);
|
|
171
|
+
if (skillStateXiMinScore !== undefined) out.skillStateXiMinScore = skillStateXiMinScore;
|
|
170
172
|
const skillStateNotesPerProject = validateNumber(r.skillStateNotesPerProject, 1);
|
|
171
173
|
if (skillStateNotesPerProject !== undefined) out.skillStateNotesPerProject = skillStateNotesPerProject;
|
|
172
174
|
// Root Σ integration (WS-2..WS-4) — paradigm flags ENFORCED (R0); the window/byte knobs
|
|
@@ -181,6 +183,10 @@ export function validateConfig(raw: unknown): Partial<RlmConfig> {
|
|
|
181
183
|
if (rootContextKeepTurns !== undefined) out.rootContextKeepTurns = rootContextKeepTurns;
|
|
182
184
|
const rootContextElideChars = validateNumber(r.rootContextElideChars, 100);
|
|
183
185
|
if (rootContextElideChars !== undefined) out.rootContextElideChars = rootContextElideChars;
|
|
186
|
+
// Recall W1 archive calibration: 0 legitimately disables the archive (plain stubs), so the
|
|
187
|
+
// floor differs from the other char knobs.
|
|
188
|
+
const rootArchiveMaxChars = validateNumber(r.rootArchiveMaxChars, 0);
|
|
189
|
+
if (rootArchiveMaxChars !== undefined) out.rootArchiveMaxChars = rootArchiveMaxChars;
|
|
184
190
|
const rootContextSnapshot = validateBoolean(r.rootContextSnapshot);
|
|
185
191
|
if (rootContextSnapshot !== undefined) out.rootContextSnapshot = rootContextSnapshot;
|
|
186
192
|
out.enableRootStateFences = validateEnforcedOn(r.enableRootStateFences, "enableRootStateFences");
|