@hicaru/pi-rlm 0.3.19 → 0.3.21
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 +8 -5
- package/package.json +1 -1
- package/src/bridge/handlers/completion.ts +3 -0
- package/src/bridge/handlers/emitting.ts +0 -4
- package/src/bridge/handlers/rlm-query.ts +3 -3
- package/src/bridge/handlers/task-registry.ts +46 -19
- package/src/bridge/handlers/types.ts +6 -3
- package/src/bridge/model.ts +4 -0
- package/src/commands/rlm.ts +14 -7
- package/src/config/defaults.ts +41 -13
- package/src/config/settings.ts +7 -3
- package/src/config/skillstate.ts +236 -44
- package/src/context/merge.ts +10 -3
- package/src/context/namespace.ts +6 -2
- package/src/context/refresh.ts +32 -11
- package/src/core/answer.ts +15 -0
- package/src/core/budget.ts +39 -17
- package/src/core/compaction.ts +85 -9
- package/src/core/engine.ts +117 -32
- package/src/core/iteration.ts +4 -0
- package/src/core/limits.ts +10 -14
- package/src/core/root-context.ts +83 -19
- 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 +13 -2
- package/src/index.ts +142 -12
- package/src/mode/rlm-mode.ts +2 -2
- package/src/prompts/glossary.ts +36 -5
- package/src/prompts/native.ts +8 -2
- package/src/prompts/user.ts +6 -4
- 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/sandbox/sandbox.ts +13 -1
- package/src/text/parsing.ts +133 -2
- package/src/text/tokens.ts +39 -4
- package/src/tool/repl-details.ts +2 -2
- package/src/tool/repl-render.ts +38 -2
- package/src/tool/repl-tool.ts +37 -23
- package/src/tool/rlm-aggregator.ts +1 -1
- package/src/tool/rlm-details.ts +1 -2
- package/src/tool/rlm-events.ts +3 -6
- package/src/tool/rlm-tool.ts +1 -1
- package/src/tool/subcall-render.ts +7 -4
- package/src/tool/subcall-store.ts +5 -18
- package/src/ui/config-panel.ts +4 -19
- package/src/ui/intro.ts +1 -1
- package/src/ui/panel/run-registry.ts +2 -2
- package/src/ui/python-highlight.ts +49 -0
- package/src/ui/stage-cards.ts +192 -0
- 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
|
@@ -48,9 +48,11 @@ journal per model, cost per task from real `costUsd`:
|
|
|
48
48
|
|
|
49
49
|
| Model | Score | Avg. cost/task |
|
|
50
50
|
|-------|-------|----------------|
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `inception/mercury-2.5` |
|
|
51
|
+
| `zai/glm-4.7` | **83%** | $0.0000 * |
|
|
52
|
+
| `qwen/qwen3.8-27b` | 49% | $0.1038 |
|
|
53
|
+
| `inception/mercury-2.5` | 38% | $0.0052 |
|
|
54
|
+
|
|
55
|
+
\* glm-4.7 runs on Z.ai's coding-plan endpoint — subscription billing, `costUsd` stays $0.
|
|
54
56
|
|
|
55
57
|
Raw per-task rows (correct, recall, latency, tokens, cost) live in
|
|
56
58
|
`bench/runs/*.jsonl` — one JSONL row per task, committed as history.
|
|
@@ -58,10 +60,11 @@ Raw per-task rows (correct, recall, latency, tokens, cost) live in
|
|
|
58
60
|
### Run the benchmarks
|
|
59
61
|
|
|
60
62
|
```bash
|
|
61
|
-
export OPENROUTER_API_KEY=sk-or-... # required
|
|
63
|
+
export OPENROUTER_API_KEY=sk-or-... # required for openrouter/* models
|
|
64
|
+
export ZAI_API_KEY=... # required for zai/* models (coding endpoint)
|
|
62
65
|
|
|
63
66
|
bun run bench # oolong suite, default model (qwen3.8-27b)
|
|
64
|
-
bun run bench --model
|
|
67
|
+
bun run bench --model zai/glm-4.7
|
|
65
68
|
bun run bench --model openrouter/inception/mercury-2.5
|
|
66
69
|
bun run bench --list # print tasks, no engine / no key
|
|
67
70
|
```
|
package/package.json
CHANGED
|
@@ -83,6 +83,9 @@ export async function complete1(
|
|
|
83
83
|
onThrottlePark: hooks?.onThrottlePark,
|
|
84
84
|
onThrottleRelease: hooks?.onThrottleRelease,
|
|
85
85
|
signal: deps.signal,
|
|
86
|
+
// Long-context providers (zai bigmodel TTFB ~1 min per 10k ctx chars) need the
|
|
87
|
+
// per-request wall cap raised from the pi-ai default.
|
|
88
|
+
timeoutMs: config.requestTimeoutMs,
|
|
86
89
|
}),
|
|
87
90
|
);
|
|
88
91
|
inv.limits.addUsage(res.usage);
|
|
@@ -54,12 +54,10 @@ export async function emitting<T>(
|
|
|
54
54
|
inv.emitter.emitSubcallUpdated({ id, ...u });
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
let costUsd = 0;
|
|
58
57
|
let tokens = 0;
|
|
59
58
|
let tokensIn = 0;
|
|
60
59
|
let tokensOut = 0;
|
|
61
60
|
const track = (u: Usage): void => {
|
|
62
|
-
costUsd += u.cost.total;
|
|
63
61
|
tokens += u.totalTokens;
|
|
64
62
|
tokensIn += u.input;
|
|
65
63
|
tokensOut += u.output;
|
|
@@ -72,7 +70,6 @@ export async function emitting<T>(
|
|
|
72
70
|
id,
|
|
73
71
|
status: summary.error !== undefined ? "error" : "done",
|
|
74
72
|
resultPreview: summary.preview,
|
|
75
|
-
costUsd,
|
|
76
73
|
tokens,
|
|
77
74
|
tokensIn,
|
|
78
75
|
tokensOut,
|
|
@@ -87,7 +84,6 @@ export async function emitting<T>(
|
|
|
87
84
|
id,
|
|
88
85
|
status: "error",
|
|
89
86
|
resultPreview: msg,
|
|
90
|
-
costUsd,
|
|
91
87
|
tokens,
|
|
92
88
|
tokensIn,
|
|
93
89
|
tokensOut,
|
|
@@ -24,10 +24,10 @@ function emptyResult(answer: string): RlmResult {
|
|
|
24
24
|
return {
|
|
25
25
|
answer,
|
|
26
26
|
iterations: 0,
|
|
27
|
-
costUsd: 0,
|
|
28
27
|
inputTokens: 0,
|
|
29
28
|
outputTokens: 0,
|
|
30
29
|
durationMs: 0,
|
|
30
|
+
lastStdout: "",
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -149,8 +149,8 @@ async function childRun(
|
|
|
149
149
|
|
|
150
150
|
try {
|
|
151
151
|
const res = await deps.gates.rlm.at(childDepth).run(() => run(input, inv));
|
|
152
|
-
inv.limits.addRaw(res.
|
|
153
|
-
deps.onChildUsage?.(res.
|
|
152
|
+
inv.limits.addRaw(res.inputTokens, res.outputTokens);
|
|
153
|
+
deps.onChildUsage?.(res.inputTokens, res.outputTokens);
|
|
154
154
|
if (ledger !== undefined && claimKey !== undefined) ledger.finish(claimKey, res.answer);
|
|
155
155
|
inv.emitter.emitSubcallUpdated({
|
|
156
156
|
id: subId,
|
|
@@ -54,17 +54,19 @@ interface Waiter {
|
|
|
54
54
|
timer?: ReturnType<typeof setTimeout>;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
function notify(waiters: Map<string, Waiter>, taskId: string, entry: TaskEntry): void {
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
60
|
-
if (w.timer !== undefined) clearTimeout(w.timer);
|
|
61
|
-
w.resolve(entry);
|
|
57
|
+
function notify(waiters: Map<string, Waiter[]>, taskId: string, entry: TaskEntry): void {
|
|
58
|
+
const list = waiters.get(taskId);
|
|
59
|
+
if (list === undefined) return;
|
|
62
60
|
waiters.delete(taskId);
|
|
61
|
+
for (const w of list) {
|
|
62
|
+
if (w.timer !== undefined) clearTimeout(w.timer);
|
|
63
|
+
w.resolve(entry);
|
|
64
|
+
}
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
export function createTaskRegistry(): TaskRegistry {
|
|
66
68
|
const tasks = new Map<string, TaskEntry>();
|
|
67
|
-
const waiters = new Map<string, Waiter>();
|
|
69
|
+
const waiters = new Map<string, Waiter[]>();
|
|
68
70
|
let counter = 0;
|
|
69
71
|
|
|
70
72
|
const spawnDeps: SpawnDeps = {
|
|
@@ -92,7 +94,9 @@ export function createTaskRegistry(): TaskRegistry {
|
|
|
92
94
|
},
|
|
93
95
|
resolve(taskId, result) {
|
|
94
96
|
const entry = tasks.get(taskId);
|
|
95
|
-
|
|
97
|
+
// Settle-once: a late resolve after a timeout/reject must not flip status or
|
|
98
|
+
// double-notify; the entry is terminal the moment it leaves "pending".
|
|
99
|
+
if (entry === undefined || entry.status !== "pending") return;
|
|
96
100
|
entry.status = "done";
|
|
97
101
|
if (typeof result === "string") {
|
|
98
102
|
entry.result = result;
|
|
@@ -103,14 +107,17 @@ export function createTaskRegistry(): TaskRegistry {
|
|
|
103
107
|
},
|
|
104
108
|
reject(taskId, error) {
|
|
105
109
|
const entry = tasks.get(taskId);
|
|
106
|
-
|
|
110
|
+
// Settle-once (mirror of resolve): reject after resolve/timeout is a no-op.
|
|
111
|
+
if (entry === undefined || entry.status !== "pending") return;
|
|
107
112
|
entry.status = "error";
|
|
108
113
|
entry.error = error;
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
111
|
-
if (w.timer !== undefined) clearTimeout(w.timer);
|
|
112
|
-
w.reject(new Error(error));
|
|
114
|
+
const list = waiters.get(taskId);
|
|
115
|
+
if (list !== undefined) {
|
|
113
116
|
waiters.delete(taskId);
|
|
117
|
+
for (const w of list) {
|
|
118
|
+
if (w.timer !== undefined) clearTimeout(w.timer);
|
|
119
|
+
w.reject(new Error(error));
|
|
120
|
+
}
|
|
114
121
|
}
|
|
115
122
|
},
|
|
116
123
|
};
|
|
@@ -124,19 +131,39 @@ export function createTaskRegistry(): TaskRegistry {
|
|
|
124
131
|
resolve(entry);
|
|
125
132
|
return;
|
|
126
133
|
}
|
|
127
|
-
const
|
|
134
|
+
const w: Waiter = { resolve, reject };
|
|
135
|
+
w.timer =
|
|
128
136
|
timeoutMs !== undefined
|
|
129
137
|
? setTimeout(() => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
w.timer = undefined;
|
|
139
|
+
// Remove only THIS waiter — a sibling wait() on the same task stays parked.
|
|
140
|
+
const list = waiters.get(taskId);
|
|
141
|
+
let lastWaiter = true;
|
|
142
|
+
if (list !== undefined) {
|
|
143
|
+
const i = list.indexOf(w);
|
|
144
|
+
if (i >= 0) {
|
|
145
|
+
list.splice(i, 1);
|
|
146
|
+
lastWaiter = list.length === 0;
|
|
147
|
+
if (lastWaiter) waiters.delete(taskId);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Timeout is a per-waiter event, not a task property: only when the LAST
|
|
151
|
+
// waiter gives up does the shared entry record "timeout" (settle-once then
|
|
152
|
+
// keeps a late resolve from resurrecting it). While a sibling stays parked,
|
|
153
|
+
// the entry remains pending and resolve() still wakes it.
|
|
154
|
+
if (lastWaiter) {
|
|
155
|
+
const e = tasks.get(taskId);
|
|
156
|
+
if (e !== undefined && e.status === "pending") {
|
|
157
|
+
e.status = "timeout";
|
|
158
|
+
e.error = `Timeout after ${timeoutMs}ms`;
|
|
159
|
+
}
|
|
135
160
|
}
|
|
136
161
|
reject(new Error(`Timeout waiting for task ${taskId}`));
|
|
137
162
|
}, timeoutMs)
|
|
138
163
|
: undefined;
|
|
139
|
-
waiters.
|
|
164
|
+
const list = waiters.get(taskId);
|
|
165
|
+
if (list === undefined) waiters.set(taskId, [w]);
|
|
166
|
+
else list.push(w);
|
|
140
167
|
});
|
|
141
168
|
},
|
|
142
169
|
unawaitedIds: () => {
|
|
@@ -57,7 +57,7 @@ export interface FinishResult {
|
|
|
57
57
|
export interface InvocationLimits {
|
|
58
58
|
remainingTimeoutMs(): number | undefined;
|
|
59
59
|
addUsage(usage: Usage): void;
|
|
60
|
-
addRaw(
|
|
60
|
+
addRaw(inputTokens: number, outputTokens: number): void;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export function limitsFromRemaining(
|
|
@@ -66,7 +66,7 @@ export function limitsFromRemaining(
|
|
|
66
66
|
return Object.freeze({
|
|
67
67
|
remainingTimeoutMs: () => remaining?.().timeoutMs,
|
|
68
68
|
addUsage: (_usage: Usage) => {},
|
|
69
|
-
addRaw: (
|
|
69
|
+
addRaw: (_inputTokens: number, _outputTokens: number) => {},
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -86,6 +86,9 @@ export interface SubcallConfig {
|
|
|
86
86
|
readonly maxDepth: number;
|
|
87
87
|
readonly subSampling?: Sampling;
|
|
88
88
|
readonly subSystemPrompt?: string;
|
|
89
|
+
/** Per-request wall cap (ms) for the llm tier — long-context providers (zai bigmodel TTFB
|
|
90
|
+
* ~1 min per 10k ctx chars) abort under the pi-ai default without it. */
|
|
91
|
+
readonly requestTimeoutMs: number;
|
|
89
92
|
/** v5 TaskLedger: claim/coalesce/echo gates (optional — unwired callers keep ledger off). */
|
|
90
93
|
readonly enableLedger?: boolean;
|
|
91
94
|
/** v5: real rlm spawns before demotion to llm (0 = never demote). */
|
|
@@ -114,7 +117,7 @@ export interface SubcallHandlerDeps {
|
|
|
114
117
|
readonly getChildContext?: () => unknown;
|
|
115
118
|
readonly getModel?: () => Model<Api>;
|
|
116
119
|
readonly degrade?: (prompt: string, depth: number) => Promise<string>;
|
|
117
|
-
readonly onChildUsage?: (
|
|
120
|
+
readonly onChildUsage?: (inputTokens: number, outputTokens: number) => void;
|
|
118
121
|
readonly trackDetached?: <T>(run: () => Promise<T>) => Promise<T>;
|
|
119
122
|
/** v5 TaskLedger blackboard shared across the whole run tree (claim/coalesce/echo/demote). */
|
|
120
123
|
readonly ledger?: TaskLedger;
|
package/src/bridge/model.ts
CHANGED
|
@@ -28,6 +28,9 @@ export interface CompleteOptions {
|
|
|
28
28
|
readonly temperature?: number;
|
|
29
29
|
readonly reasoning?: ThinkingLevel;
|
|
30
30
|
readonly signal?: AbortSignal;
|
|
31
|
+
/** Wall-clock cap for ONE provider request (ms); omitted = pi-ai default. Long-context
|
|
32
|
+
* providers (zai bigmodel: ~1 min per 10k ctx chars TTFB) need this raised per config. */
|
|
33
|
+
readonly timeoutMs?: number;
|
|
31
34
|
/** Retry + adaptive throttle for transient 429/5xx; defaults apply when omitted. */
|
|
32
35
|
readonly retry?: RetryPolicy;
|
|
33
36
|
/** v5.1 UX: fired while parked on the rate-limit cooldown ("queued") / when released. */
|
|
@@ -116,6 +119,7 @@ export async function modelComplete(messages: readonly ChatMsg[], opts: Complete
|
|
|
116
119
|
temperature: opts.temperature,
|
|
117
120
|
reasoning: effectiveReasoning(opts.model, opts.reasoning),
|
|
118
121
|
signal: opts.signal,
|
|
122
|
+
timeoutMs: opts.timeoutMs,
|
|
119
123
|
onResponse: (res) => { note(res.status, res.headers); },
|
|
120
124
|
},
|
|
121
125
|
);
|
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,
|
|
@@ -12,7 +20,10 @@ export const DEFAULT_CONFIG: Readonly<RlmConfig> = Object.freeze({
|
|
|
12
20
|
// Max-long runs: the engine may keep iterating until budget/compaction walls hit. The budget
|
|
13
21
|
// cascade and compactionThresholdPct are the real length controls; this ceiling only stops
|
|
14
22
|
// truly runaway loops. Was 30 — capped long tasks prematurely.
|
|
15
|
-
|
|
23
|
+
// Strongly oversized on purpose: runs end on FINAL answer / errors / wall-clock long
|
|
24
|
+
// before this bites. History: 30 capped long tasks; the bench's 16 all-failed oolong
|
|
25
|
+
// mid-retrieval. 1000 ≈ 5× the old interactive default, effectively a runaway backstop.
|
|
26
|
+
maxIterations: 1_000,
|
|
16
27
|
execTimeoutS: 120,
|
|
17
28
|
requestTimeoutMs: 15 * 60_000,
|
|
18
29
|
// Session-wide, not per-batch: spawn() puts many requests on the wire at once, so this is
|
|
@@ -53,15 +64,19 @@ export const DEFAULT_CONFIG: Readonly<RlmConfig> = Object.freeze({
|
|
|
53
64
|
enableTokenBudget: true,
|
|
54
65
|
budgetShare: 0.25,
|
|
55
66
|
budgetSoftFrac: 0.8,
|
|
56
|
-
budgetTaskCap:
|
|
67
|
+
budgetTaskCap: 1_000_000,
|
|
57
68
|
budgetMaxContinuations: 2,
|
|
58
|
-
|
|
69
|
+
// 24K: the handoff must carry Σ + findings + query verbatim — a 4K skeleton is what made
|
|
70
|
+
// long research runs "lose context" on hard-budget continuation (amputated, not lost).
|
|
71
|
+
budgetHandoffChars: 24_000,
|
|
59
72
|
// v5 TaskLedger blackboard
|
|
60
73
|
enableLedger: true,
|
|
61
74
|
rlmBudget: 8,
|
|
62
|
-
// Verification-discipline nudge —
|
|
63
|
-
//
|
|
64
|
-
|
|
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,
|
|
65
80
|
// SKILL.state integration: Σ_t execution state + cross-session distilled knowledge.
|
|
66
81
|
// Paradigm flags are ENFORCED (R0, /tmp/ROOT_FULL_SKILLSTATE_PLAN.md) — validateEnforcedOn
|
|
67
82
|
// forces true whatever rlm.json carries; only calibrations are tunable.
|
|
@@ -73,7 +88,12 @@ export const DEFAULT_CONFIG: Readonly<RlmConfig> = Object.freeze({
|
|
|
73
88
|
enableSkillStateDistill: true, // ENFORCED — see /tmp/ROOT_FULL_SKILLSTATE_PLAN.md R0
|
|
74
89
|
skillStateMaxTokens: 1_200,
|
|
75
90
|
skillStateLeafTokens: 200,
|
|
76
|
-
|
|
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,
|
|
77
97
|
skillStateNotesPerProject: 128,
|
|
78
98
|
// Root Σ integration (WS-2..WS-4): every LLM call assembles A_t = (P, Σ_t, O_t) — discard
|
|
79
99
|
// semantics on stale payloads + exactly one Σ snapshot splice, and model-proposed ΔΣ_t
|
|
@@ -84,8 +104,16 @@ export const DEFAULT_CONFIG: Readonly<RlmConfig> = Object.freeze({
|
|
|
84
104
|
rootDigestKeepRecentChars: 12_000,
|
|
85
105
|
rootDigestMaxChars: 8_000,
|
|
86
106
|
enableRootContextTransform: true, // ENFORCED — see /tmp/ROOT_FULL_SKILLSTATE_PLAN.md R0 (was soak-OFF pre-v2)
|
|
87
|
-
|
|
88
|
-
|
|
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,
|
|
89
117
|
rootContextSnapshot: true,
|
|
90
118
|
enableRootStateFences: true, // ENFORCED — see /tmp/ROOT_FULL_SKILLSTATE_PLAN.md R0 (was soak-OFF pre-v2)
|
|
91
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");
|
|
@@ -197,11 +203,9 @@ export function validateConfig(raw: unknown): Partial<RlmConfig> {
|
|
|
197
203
|
}
|
|
198
204
|
if (typeof r.rootSampling === "object" && r.rootSampling !== null) {
|
|
199
205
|
const rs = r.rootSampling as Record<string, unknown>;
|
|
200
|
-
const rootSampling: { maxTokens?: number;
|
|
206
|
+
const rootSampling: { maxTokens?: number; reasoning?: ThinkingLevel } = {};
|
|
201
207
|
const rsMaxTokens = validateNumber(rs.maxTokens, 1);
|
|
202
208
|
if (rsMaxTokens !== undefined) rootSampling.maxTokens = rsMaxTokens;
|
|
203
|
-
const rsTemperature = validateNumber(rs.temperature, 0);
|
|
204
|
-
if (rsTemperature !== undefined) rootSampling.temperature = rsTemperature;
|
|
205
209
|
const rsReasoning = validateThinkingLevel(rs.reasoning);
|
|
206
210
|
if (rsReasoning !== undefined) rootSampling.reasoning = rsReasoning;
|
|
207
211
|
out.rootSampling = Object.freeze(rootSampling);
|