@mnemom/mnemom 0.17.6-next.0 → 0.17.6-next.2
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 +41 -0
- package/dist/commands/agent-config.js +3 -0
- package/dist/commands/agent.d.ts +12 -0
- package/dist/commands/agent.js +50 -12
- package/dist/index.js +55 -2
- package/dist/lib/agent-ledger.d.ts +8 -0
- package/dist/lib/agent.d.ts +94 -6
- package/dist/lib/agent.js +184 -7
- package/dist/lib/experimental.d.ts +11 -2
- package/dist/lib/experimental.js +21 -2
- package/dist/lib/first-run/backtest.d.ts +16 -33
- package/dist/lib/first-run/backtest.js +19 -54
- package/dist/lib/first-run/index.js +1 -1
- package/dist/lib/first-run/screen.d.ts +9 -9
- package/dist/lib/first-run/screen.js +31 -20
- package/dist/lib/launcher-config.d.ts +11 -2
- package/dist/lib/launcher-config.js +32 -3
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The first-run screen: one plain terminal screen (at most
|
|
2
|
+
* The first-run screen: one plain terminal screen (at most 22 lines of at most 78
|
|
3
3
|
* columns, ASCII) built from the backtest numbers. Pure — no I/O — so every layout is
|
|
4
4
|
* unit-tested. Every number on it is recorded in the audit with its raw value and
|
|
5
5
|
* method.
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import { ESTIMATOR_METHOD } from "./backtest.js";
|
|
28
28
|
export const WIDTH = 78;
|
|
29
|
-
export const MAX_LINES =
|
|
29
|
+
export const MAX_LINES = 22;
|
|
30
30
|
/** Less history than this (API prices, in the window) is too little to backtest. */
|
|
31
31
|
export const MIN_HISTORY_USD = 5;
|
|
32
32
|
/** Variation A iff the shown saving is at least this many dollars and this many percent. */
|
|
@@ -34,14 +34,14 @@ export const STRONG_MIN_USD = 5;
|
|
|
34
34
|
export const STRONG_MIN_PCT = 5;
|
|
35
35
|
export const DAYS = 30;
|
|
36
36
|
/**
|
|
37
|
-
* The forward line:
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* undercounts output).
|
|
37
|
+
* The forward line: chain45 S46 C2n (context runtime, aggressive profile) total bill vs
|
|
38
|
+
* stock, -20.9% [-25.6, -15.9], Opus 5.5, n=13 (r15 readout; operator 2026-10-02: quote
|
|
39
|
+
* S46, not S43), 95% CI rounded to fives, 15-25. The d240p1 ~45% figure is NOT used
|
|
40
|
+
* (its re-bill undercounts output).
|
|
41
41
|
*/
|
|
42
|
-
export const FORWARD_LINE = "Long sessions are where Mnemom saves the most: about 15 to 25% in our
|
|
43
|
-
const FORWARD_SOURCE = "operator 2026-10-
|
|
44
|
-
"rounded to 15 to 25; measured vs stock
|
|
42
|
+
export const FORWARD_LINE = "Long sessions are where Mnemom saves the most: about 15 to 25% in our datasets.";
|
|
43
|
+
const FORWARD_SOURCE = "operator 2026-10-02: S46 C2n total bill -20.9% [-25.6, -15.9] (Opus 5.5, n=13), 95% CI " +
|
|
44
|
+
"rounded to fives, 15 to 25; measured vs stock";
|
|
45
45
|
/** Prices, promotions and plan allocations move; the saving is in usage (operator 2026-10-01). */
|
|
46
46
|
export const PRICE_LINE = "Prices and plan limits change. Mnemom cuts what you use, so you stay ahead.";
|
|
47
47
|
/** What `mnemom agent` does today: routes through the gateway, records, keeps context lean. */
|
|
@@ -73,11 +73,11 @@ export function pair(total, saved) {
|
|
|
73
73
|
const tot = sig3(total);
|
|
74
74
|
return [tot, Math.min(sv, tot), Math.max(tot - sv, 0)];
|
|
75
75
|
}
|
|
76
|
-
/** part / whole as a percentage rounded DOWN
|
|
77
|
-
export function
|
|
76
|
+
/** part / whole as a whole-number percentage, decimals dropped (rounded DOWN). */
|
|
77
|
+
export function pctDown(part, whole) {
|
|
78
78
|
if (whole <= 0 || part <= 0)
|
|
79
79
|
return 0;
|
|
80
|
-
return Math.floor((100 * part) / whole
|
|
80
|
+
return Math.floor((100 * part) / whole + 1e-9);
|
|
81
81
|
}
|
|
82
82
|
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
83
83
|
export function day(t) {
|
|
@@ -126,17 +126,17 @@ const SAVED_METHOD = `backtest.estimate(): ${ESTIMATOR_METHOD}; 2 sig figs DOWN`
|
|
|
126
126
|
function subLines(sub, A) {
|
|
127
127
|
if (!sub)
|
|
128
128
|
return [];
|
|
129
|
-
const p =
|
|
129
|
+
const p = pctDown(sub.saved, sub.actual);
|
|
130
130
|
const raw = sub.actual > 0 ? (100 * sub.saved) / sub.actual : 0;
|
|
131
131
|
if (p < 5) {
|
|
132
|
-
A("sub_line_omitted", { shown: true, raw, method: "usage cut
|
|
132
|
+
A("sub_line_omitted", { shown: true, raw, method: "usage cut below 5% (decimals dropped)" });
|
|
133
133
|
return [];
|
|
134
134
|
}
|
|
135
135
|
A("sub_usage_pct", {
|
|
136
136
|
shown: p,
|
|
137
137
|
raw,
|
|
138
138
|
method: "saving / API-price cost over the subscription sessions in the window (all sessions if " +
|
|
139
|
-
"none is tagged); cost-weighted,
|
|
139
|
+
"none is tagged); cost-weighted, decimals dropped",
|
|
140
140
|
});
|
|
141
141
|
return wrap(`On your subscription that is about ${p}% less usage: fewer limit hits, and less ` +
|
|
142
142
|
"overage if you use extra usage.");
|
|
@@ -151,7 +151,7 @@ export function buildScreen(inp) {
|
|
|
151
151
|
const actual = ses.reduce((t, r) => t + r.actual, 0);
|
|
152
152
|
const saved = ses.reduce((t, r) => t + r.saved, 0);
|
|
153
153
|
const [a, s, e] = pair(actual, saved);
|
|
154
|
-
const p =
|
|
154
|
+
const p = pctDown(saved, actual);
|
|
155
155
|
const enough = actual >= MIN_HISTORY_USD;
|
|
156
156
|
const variation = enough && s >= STRONG_MIN_USD && p >= STRONG_MIN_PCT ? "A" : enough ? "B" : "C";
|
|
157
157
|
A("variation", { shown: variation, raw: { actual, saved }, method: "see screen.ts header" });
|
|
@@ -162,7 +162,11 @@ export function buildScreen(inp) {
|
|
|
162
162
|
L.push("");
|
|
163
163
|
if (variation === "A") {
|
|
164
164
|
A("spent_usd", { shown: a, raw: actual, method: SPENT_METHOD });
|
|
165
|
-
A("saved_pct", {
|
|
165
|
+
A("saved_pct", {
|
|
166
|
+
shown: p,
|
|
167
|
+
raw: (100 * saved) / actual,
|
|
168
|
+
method: "decimals dropped (rounded DOWN)",
|
|
169
|
+
});
|
|
166
170
|
const viaRaw = ses.reduce((t, r) => t + (r.viaMnemom ? r.saved : 0), 0);
|
|
167
171
|
const via = sig2(viaRaw);
|
|
168
172
|
const rest = sig2(saved - viaRaw);
|
|
@@ -170,12 +174,14 @@ export function buildScreen(inp) {
|
|
|
170
174
|
A("saved_usd", { shown: s, raw: saved, method: SAVED_METHOD });
|
|
171
175
|
A("with_mnemom_usd", { shown: e, raw: actual - saved, method: "spent shown - saved shown" });
|
|
172
176
|
L.push(...wrap(`At API prices that usage cost about ${usd(a)}. With Mnemom: about ${usd(e)}.`));
|
|
177
|
+
L.push("");
|
|
173
178
|
L.push(...wrap(`That's about ${usd(s)} less (${p}%), and you don't change how you work.`));
|
|
174
179
|
}
|
|
175
180
|
else {
|
|
176
181
|
const m = `${SAVED_METHOD}; sessions the launch ledger shows ran through Mnemom`;
|
|
177
182
|
A("saved_via_mnemom_usd", { shown: via, raw: viaRaw, method: m });
|
|
178
183
|
L.push(...wrap(`At API prices that usage comes to about ${usd(a)} without Mnemom.`));
|
|
184
|
+
L.push("");
|
|
179
185
|
if (rest < 1) {
|
|
180
186
|
L.push(...wrap(`Mnemom saved you about ${usd(via)} of it (${p}%), and you didn't change how you work.`));
|
|
181
187
|
}
|
|
@@ -202,11 +208,14 @@ export function buildScreen(inp) {
|
|
|
202
208
|
raw: big.actual - big.saved,
|
|
203
209
|
method: "its cost shown - its saving (2 sig figs DOWN)",
|
|
204
210
|
});
|
|
211
|
+
L.push("");
|
|
205
212
|
const where = big.label ? `${big.label} on ${day(big.end)}` : `on ${day(big.end)}`;
|
|
206
213
|
L.push(...wrap(big.viaMnemom
|
|
207
214
|
? `Your biggest session, ${where}: ${usd(ba)} without Mnemom, about ${usd(be)} with it.`
|
|
208
215
|
: `Your biggest session, ${where}: ${usd(ba)}, with Mnemom about ${usd(be)}.`));
|
|
209
|
-
|
|
216
|
+
const subL = subLines(inp.sub, A);
|
|
217
|
+
if (subL.length)
|
|
218
|
+
L.push("", ...subL);
|
|
210
219
|
}
|
|
211
220
|
else {
|
|
212
221
|
if (variation === "B") {
|
|
@@ -219,10 +228,12 @@ export function buildScreen(inp) {
|
|
|
219
228
|
L.push(...wrap(`Your sessions used about ${usd(a)} at API prices. They stayed small, so Mnemom ` +
|
|
220
229
|
"wouldn't have changed that much."));
|
|
221
230
|
}
|
|
222
|
-
|
|
231
|
+
if (variation === "B")
|
|
232
|
+
L.push("");
|
|
233
|
+
L.push(...wrap(FORWARD_LINE));
|
|
223
234
|
A("forward_range_pct", {
|
|
224
235
|
shown: "15 to 25",
|
|
225
|
-
raw: [-
|
|
236
|
+
raw: [-25.6, -20.9, -15.9],
|
|
226
237
|
method: FORWARD_SOURCE,
|
|
227
238
|
});
|
|
228
239
|
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* the caller folds the plain-value settings under the CLI flags.
|
|
17
17
|
*/
|
|
18
18
|
import { type AgentAuth } from "./subscription-login.js";
|
|
19
|
-
import type
|
|
19
|
+
import { type ExperimentalId } from "./experimental.js";
|
|
20
20
|
import { type ContextProfile, type GoalModeDefault, type LaunchShape } from "./agent.js";
|
|
21
21
|
/** The human-editable settings file. Secret key lives elsewhere (launcher.json). */
|
|
22
22
|
export declare const LAUNCHER_CONFIG_PATH: string;
|
|
@@ -61,6 +61,12 @@ export interface LauncherConfig {
|
|
|
61
61
|
* folds earlier with a tighter working set. `--context-profile` overrides it per launch.
|
|
62
62
|
*/
|
|
63
63
|
contextProfile?: ContextProfile;
|
|
64
|
+
/**
|
|
65
|
+
* The gateway compaction target (`--compact-at`; unset means no target — Claude Code's own
|
|
66
|
+
* 1M window, nothing new sent). Stored as the validated raw string (e.g. "200k", "1m",
|
|
67
|
+
* "off", "350000"), parsed with `parseCompactAt` at the point of use.
|
|
68
|
+
*/
|
|
69
|
+
compactAt?: string;
|
|
64
70
|
guardrails?: {
|
|
65
71
|
maxTurns?: number;
|
|
66
72
|
budgetUsd?: number;
|
|
@@ -113,6 +119,9 @@ export declare function launcherEntries(cfg: LauncherConfig): Array<[string, str
|
|
|
113
119
|
/**
|
|
114
120
|
* `cfg` without the settings of experimental features that are off (a copy; `cfg` is
|
|
115
121
|
* untouched): `slack_*` while `slack` is off, `context_handoff` while `context-handoff` is
|
|
116
|
-
* off, `compaction_intercept`
|
|
122
|
+
* off, `compaction_intercept` (and an ARMED `compact_at` — it arms the same two gateway keys,
|
|
123
|
+
* so it needs the same opt-in; `compact_at = "off"`/`"1m"` carries no gated behaviour and is
|
|
124
|
+
* kept) while `compaction-intercept` is off, and `goal_mode = implicit` while `implicit` is
|
|
125
|
+
* off (`goal_mode = off` is kept).
|
|
117
126
|
*/
|
|
118
127
|
export declare function withoutDisabledExperimental(cfg: LauncherConfig, enabled: readonly ExperimentalId[]): LauncherConfig;
|
|
@@ -20,7 +20,8 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "
|
|
|
20
20
|
import { dirname, join } from "node:path";
|
|
21
21
|
import { parse as parseToml, stringify as stringifyToml, TomlError } from "smol-toml";
|
|
22
22
|
import { MNEMOM_DIR } from "./config.js";
|
|
23
|
-
import {
|
|
23
|
+
import { compactAtArmed } from "./experimental.js";
|
|
24
|
+
import { CONTEXT_PROFILES, isPositiveInt, isPositiveNumber, parseCompactAt, parseContextProfile, } from "./agent.js";
|
|
24
25
|
/** The human-editable settings file. Secret key lives elsewhere (launcher.json). */
|
|
25
26
|
export const LAUNCHER_CONFIG_PATH = join(MNEMOM_DIR, "launcher.toml");
|
|
26
27
|
const LAUNCH_SHAPES = ["terminal", "remote-control", "server"];
|
|
@@ -67,6 +68,13 @@ function coerceNonEmpty(name, raw) {
|
|
|
67
68
|
throw new Error(`${name} must be a non-empty string.`);
|
|
68
69
|
return v;
|
|
69
70
|
}
|
|
71
|
+
function coerceCompactAt(raw) {
|
|
72
|
+
const v = raw.trim().toLowerCase();
|
|
73
|
+
// Validate against the same parser the flag/env use, but keep the human-chosen spelling
|
|
74
|
+
// ("200k" vs "200000") — resolveCompactAt re-parses it at launch time anyway.
|
|
75
|
+
parseCompactAt(v, "compact_at");
|
|
76
|
+
return v;
|
|
77
|
+
}
|
|
70
78
|
function coerceMemberIds(raw) {
|
|
71
79
|
const ids = raw
|
|
72
80
|
.split(/[\s,]+/)
|
|
@@ -213,6 +221,14 @@ export const CONFIG_KEYS = [
|
|
|
213
221
|
c.contextProfile = parseContextProfile(v, "context_profile");
|
|
214
222
|
},
|
|
215
223
|
},
|
|
224
|
+
{
|
|
225
|
+
key: "compact_at",
|
|
226
|
+
describe: "Gateway compaction target: a token count (k/m suffix OK), '1m' or 'off' for none (default: none — Claude Code's own 1M window)",
|
|
227
|
+
get: (c) => c.compactAt,
|
|
228
|
+
set: (c, v) => {
|
|
229
|
+
c.compactAt = coerceCompactAt(v);
|
|
230
|
+
},
|
|
231
|
+
},
|
|
216
232
|
{
|
|
217
233
|
key: "guardrails.max_turns",
|
|
218
234
|
describe: "Contract guardrail: turn ceiling (positive integer)",
|
|
@@ -297,6 +313,8 @@ export function parseLauncherConfig(text) {
|
|
|
297
313
|
cfg.compactionIntercept = coerceBool("compaction_intercept", str(raw.compaction_intercept) ?? "");
|
|
298
314
|
if (raw.context_profile !== undefined)
|
|
299
315
|
cfg.contextProfile = parseContextProfile(str(raw.context_profile) ?? "", "context_profile");
|
|
316
|
+
if (raw.compact_at !== undefined)
|
|
317
|
+
cfg.compactAt = coerceCompactAt(str(raw.compact_at) ?? "");
|
|
300
318
|
const g = raw.guardrails;
|
|
301
319
|
if (g !== undefined) {
|
|
302
320
|
if (typeof g !== "object" || g === null || Array.isArray(g)) {
|
|
@@ -371,6 +389,8 @@ export function serializeLauncherConfig(cfg) {
|
|
|
371
389
|
doc.compaction_intercept = cfg.compactionIntercept;
|
|
372
390
|
if (cfg.contextProfile !== undefined)
|
|
373
391
|
doc.context_profile = cfg.contextProfile;
|
|
392
|
+
if (cfg.compactAt !== undefined)
|
|
393
|
+
doc.compact_at = cfg.compactAt;
|
|
374
394
|
const g = cfg.guardrails;
|
|
375
395
|
if (g && (g.maxTurns !== undefined || g.budgetUsd !== undefined || g.stallTurns !== undefined)) {
|
|
376
396
|
const gr = {};
|
|
@@ -433,6 +453,9 @@ export function applyConfigToEnv(cfg, env = process.env) {
|
|
|
433
453
|
if (cfg.contextProfile !== undefined && !(env.MNEMOM_AGENT_CONTEXT_PROFILE ?? "").trim()) {
|
|
434
454
|
env.MNEMOM_AGENT_CONTEXT_PROFILE = cfg.contextProfile;
|
|
435
455
|
}
|
|
456
|
+
if (cfg.compactAt !== undefined && !(env.MNEMOM_COMPACT_AT ?? "").trim()) {
|
|
457
|
+
env.MNEMOM_COMPACT_AT = cfg.compactAt;
|
|
458
|
+
}
|
|
436
459
|
}
|
|
437
460
|
/** The set keys of `cfg` as `[key, value]` pairs (CONFIG_KEYS order, values stringified). */
|
|
438
461
|
export function launcherEntries(cfg) {
|
|
@@ -444,7 +467,10 @@ export function launcherEntries(cfg) {
|
|
|
444
467
|
/**
|
|
445
468
|
* `cfg` without the settings of experimental features that are off (a copy; `cfg` is
|
|
446
469
|
* untouched): `slack_*` while `slack` is off, `context_handoff` while `context-handoff` is
|
|
447
|
-
* off, `compaction_intercept`
|
|
470
|
+
* off, `compaction_intercept` (and an ARMED `compact_at` — it arms the same two gateway keys,
|
|
471
|
+
* so it needs the same opt-in; `compact_at = "off"`/`"1m"` carries no gated behaviour and is
|
|
472
|
+
* kept) while `compaction-intercept` is off, and `goal_mode = implicit` while `implicit` is
|
|
473
|
+
* off (`goal_mode = off` is kept).
|
|
448
474
|
*/
|
|
449
475
|
export function withoutDisabledExperimental(cfg, enabled) {
|
|
450
476
|
const out = { ...cfg };
|
|
@@ -455,8 +481,11 @@ export function withoutDisabledExperimental(cfg, enabled) {
|
|
|
455
481
|
}
|
|
456
482
|
if (!enabled.includes("context-handoff"))
|
|
457
483
|
delete out.contextHandoff;
|
|
458
|
-
if (!enabled.includes("compaction-intercept"))
|
|
484
|
+
if (!enabled.includes("compaction-intercept")) {
|
|
459
485
|
delete out.compactionIntercept;
|
|
486
|
+
if (compactAtArmed(out.compactAt))
|
|
487
|
+
delete out.compactAt;
|
|
488
|
+
}
|
|
460
489
|
if (!enabled.includes("implicit") && out.goalMode === "implicit")
|
|
461
490
|
delete out.goalMode;
|
|
462
491
|
return out;
|