@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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The first-run screen: one plain terminal screen (at most 16 lines of at most 78
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 = 16;
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: the best finished chain45 run with its error margin (operator
38
- * 2026-10-01, until something better): S43 C2 total bill -19.5% [-23.9, -14.9], Opus 5.5,
39
- * n=23, 95% CI rounded to 15-25. The d240p1 ~45% figure is NOT used (its re-bill
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 tests.";
43
- const FORWARD_SOURCE = "operator 2026-10-01: S43 C2 total bill -19.5% [-23.9, -14.9] (Opus 5.5, n=23), 95% CI " +
44
- "rounded to 15 to 25; measured vs stock at the 1M default";
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 to a multiple of 5. */
77
- export function pct5(part, whole) {
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 / 5 + 1e-9) * 5;
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 = pct5(sub.saved, sub.actual);
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 rounds DOWN below 5%" });
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, rounded DOWN to 5s",
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 = pct5(saved, actual);
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", { shown: p, raw: (100 * saved) / actual, method: "rounded DOWN to 5s" });
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
- L.push(...subLines(inp.sub, A));
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
- L.push(FORWARD_LINE);
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: [-23.9, -19.5, -14.9],
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 { ExperimentalId } from "./experimental.js";
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` while `compaction-intercept` is off, and `goal_mode = implicit` while `implicit` is off (`goal_mode = off` is kept).
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 { CONTEXT_PROFILES, isPositiveInt, isPositiveNumber, parseContextProfile, } from "./agent.js";
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` while `compaction-intercept` is off, and `goal_mode = implicit` while `implicit` is off (`goal_mode = off` is kept).
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mnemom/mnemom",
3
- "version": "0.17.6-next.0",
3
+ "version": "0.17.6-next.2",
4
4
  "description": "Transparent AI agent tracing",
5
5
  "type": "module",
6
6
  "bin": {