@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
package/dist/lib/agent.js
CHANGED
|
@@ -489,10 +489,175 @@ export const CONTEXT_HANDOFF_KEY = "CONTEXT_HANDOFF_ENABLED";
|
|
|
489
489
|
* `CLAUDE_CODE_AUTO_COMPACT_WINDOW` / `autoCompactWindow`) fires as it would without the fold;
|
|
490
490
|
* - `GATEWAY_COMPACTION_INTERCEPT` — answer the client's compaction request at once from the
|
|
491
491
|
* gateway's own fold digest (no model call), so the compaction never blocks the user.
|
|
492
|
-
* Sent together, value `"on"`,
|
|
492
|
+
* Sent together, value `"on"`, when the `compaction_intercept` setting is on
|
|
493
|
+
* (`resolveCompactionIntercept`) OR when `--compact-at` arms a target below 1M (below) — a
|
|
494
|
+
* target cannot be honored without both.
|
|
493
495
|
*/
|
|
494
496
|
export const USAGE_REAL_SIZE_KEY = "GATEWAY_USAGE_REAL_SIZE";
|
|
495
497
|
export const COMPACTION_INTERCEPT_KEY = "GATEWAY_COMPACTION_INTERCEPT";
|
|
498
|
+
// ---------------------------------------------------------------------------
|
|
499
|
+
// Compaction target (`--compact-at`, launcher.toml `compact_at`, MNEMOM_COMPACT_AT)
|
|
500
|
+
// ---------------------------------------------------------------------------
|
|
501
|
+
//
|
|
502
|
+
// Lets an operator play with WHEN the gateway pulls this session's own client-visible
|
|
503
|
+
// compaction point forward (GATEWAY_COMPACTION_UPLOAD_TARGET_TOKENS, #2251), instead of
|
|
504
|
+
// leaving Claude Code to compact on its own 1M-window schedule. Unset / `1m` / `off` all
|
|
505
|
+
// mean the SAME thing — no target — so a launch with nothing else configured sends no new
|
|
506
|
+
// header keys at all and behaves exactly as it did before this setting existed: Claude
|
|
507
|
+
// Code's own 1M window, no gateway-triggered compaction. A value strictly below 1,000,000
|
|
508
|
+
// arms the compaction intercept (USAGE_REAL_SIZE_KEY + COMPACTION_INTERCEPT_KEY, above — the
|
|
509
|
+
// same two keys `--compaction-intercept` arms, #2216, both required for the upload pull to be
|
|
510
|
+
// honored instead of silently inert) plus the target itself. Refold (GATEWAY_COMPACTION_REFOLD)
|
|
511
|
+
// is a SEPARATE, costlier opt-in (+9.5pts measured) — this setting never touches it. This never
|
|
512
|
+
// sets a Claude Code autocompact env/window: Claude Code's own compaction math is untouched;
|
|
513
|
+
// only what the GATEWAY reports back changes.
|
|
514
|
+
export const GATEWAY_COMPACTION_UPLOAD_TARGET_KEY = "GATEWAY_COMPACTION_UPLOAD_TARGET_TOKENS";
|
|
515
|
+
/** The lowest token count `--compact-at` / `compact_at` / `MNEMOM_COMPACT_AT` will accept. */
|
|
516
|
+
export const COMPACT_AT_MIN_TOKENS = 50_000;
|
|
517
|
+
/**
|
|
518
|
+
* The parser's ceiling (exclusive): the largest window Claude Code runs. It is NOT where a target
|
|
519
|
+
* stops working — the gateway ignores a target at or above the compaction point of the window
|
|
520
|
+
* the session actually runs on (COMPACT_AT_TRIGGER_1M / COMPACT_AT_TRIGGER_200K, below), which
|
|
521
|
+
* `compactAtWindowWarning` tells the user about at launch.
|
|
522
|
+
*/
|
|
523
|
+
export const COMPACT_AT_MAX_TOKENS = 1_000_000;
|
|
524
|
+
/**
|
|
525
|
+
* Claude Code's own compaction point on a 1M and on a 200k window — the gateway's
|
|
526
|
+
* `ALIGN_T_1M` / `ALIGN_T_200K` (gateway/src/compaction-align.ts). The gateway honours an upload
|
|
527
|
+
* target only BELOW the point of the window the request runs on (`target_over_threshold`
|
|
528
|
+
* otherwise), and says nothing back to the client when it ignores one.
|
|
529
|
+
*/
|
|
530
|
+
export const COMPACT_AT_TRIGGER_1M = 967_000;
|
|
531
|
+
export const COMPACT_AT_TRIGGER_200K = 167_000;
|
|
532
|
+
/**
|
|
533
|
+
* `resolveWindowModel`'s answer as a window. `one-m` and a model already suffixed `[1m]` are
|
|
534
|
+
* 1M; the `CLAUDE_CODE_DISABLE_1M_CONTEXT` opt-out and a model with no 1M window are 200k (the
|
|
535
|
+
* request then carries no 1M beta, so the gateway uses its 200k point too). A passthrough
|
|
536
|
+
* `-- --model`, a resume, Claude Code's own default, any other suffix, and a non-Claude-Code CLI
|
|
537
|
+
* (`null`) are unknown.
|
|
538
|
+
*/
|
|
539
|
+
export function launchWindow(win) {
|
|
540
|
+
if (win === null)
|
|
541
|
+
return "unknown";
|
|
542
|
+
switch (win.reason) {
|
|
543
|
+
case "one-m":
|
|
544
|
+
return "1m";
|
|
545
|
+
case "suffixed":
|
|
546
|
+
return /\[1m\]\s*$/i.test(win.model ?? "") ? "1m" : "unknown";
|
|
547
|
+
case "opt-out":
|
|
548
|
+
case "unsupported":
|
|
549
|
+
return "200k";
|
|
550
|
+
default:
|
|
551
|
+
return "unknown";
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* The launch warning for a target the gateway will (or may) ignore because it is at or above
|
|
556
|
+
* the compaction point of the window this launch runs on; `null` when the target can take
|
|
557
|
+
* effect (or there is no target). On an unknown window a target in `[167k, 967k)` works only
|
|
558
|
+
* if the session lands on a 1M window, so the line says so instead of staying silent.
|
|
559
|
+
*/
|
|
560
|
+
export function compactAtWindowWarning(compactAt, window) {
|
|
561
|
+
if (compactAt === null)
|
|
562
|
+
return null;
|
|
563
|
+
const at = formatTokenCount(compactAt);
|
|
564
|
+
const t1m = formatTokenCount(COMPACT_AT_TRIGGER_1M);
|
|
565
|
+
const t200k = formatTokenCount(COMPACT_AT_TRIGGER_200K);
|
|
566
|
+
if (compactAt >= COMPACT_AT_TRIGGER_1M ||
|
|
567
|
+
(window === "200k" && compactAt >= COMPACT_AT_TRIGGER_200K)) {
|
|
568
|
+
const [w, t] = window === "200k" ? ["200k", t200k] : window === "1m" ? ["1M", t1m] : ["any", t1m];
|
|
569
|
+
return (`mnemom agent: warning: compaction target ${at} is at or above Claude Code's own compaction ` +
|
|
570
|
+
`point on ${w === "any" ? "any" : `this launch's ${w}`} window (${t}), so the gateway will ignore it. ` +
|
|
571
|
+
`Pick a value below ${t}.`);
|
|
572
|
+
}
|
|
573
|
+
if (window === "unknown" && compactAt >= COMPACT_AT_TRIGGER_200K) {
|
|
574
|
+
return (`mnemom agent: note: compaction target ${at} takes effect only on a 1M window (below ${t1m}); ` +
|
|
575
|
+
`if this session runs on a 200k window, the gateway's point is ${t200k} and it ignores the target.`);
|
|
576
|
+
}
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Parse a `--compact-at` / `compact_at` / `MNEMOM_COMPACT_AT` value. `source` names the
|
|
581
|
+
* surface in the error so a customer knows which setting to fix (same convention as
|
|
582
|
+
* `parseContextProfile`).
|
|
583
|
+
*
|
|
584
|
+
* - "" (unset), "1m", "off" (trimmed, case-insensitive) → `null`: no gateway target —
|
|
585
|
+
* Claude Code runs its normal 1M window and nothing new is sent.
|
|
586
|
+
* - a bare integer, or one with a `k`/`m` suffix (`200k` → 200000, `0.5m` → 500000) →
|
|
587
|
+
* that many tokens, when it is in `[COMPACT_AT_MIN_TOKENS, COMPACT_AT_MAX_TOKENS)`.
|
|
588
|
+
* - anything else, or a number outside that range (use `1m`/`off` for the ceiling and
|
|
589
|
+
* above, never a bare `1000000`) → throws with a human-readable message.
|
|
590
|
+
*/
|
|
591
|
+
export function parseCompactAt(raw, source) {
|
|
592
|
+
const v = raw.trim().toLowerCase();
|
|
593
|
+
if (v === "" || v === "1m" || v === "off")
|
|
594
|
+
return null;
|
|
595
|
+
const m = /^(\d+(?:\.\d+)?)(k|m)?$/.exec(v);
|
|
596
|
+
if (!m) {
|
|
597
|
+
throw new Error(`${source} must be a token count (optionally with a k/m suffix), '1m', or 'off' (got '${raw}').`);
|
|
598
|
+
}
|
|
599
|
+
const mult = m[2] === "k" ? 1_000 : m[2] === "m" ? 1_000_000 : 1;
|
|
600
|
+
const tokens = Math.round(Number(m[1]) * mult);
|
|
601
|
+
if (!Number.isFinite(tokens) ||
|
|
602
|
+
tokens < COMPACT_AT_MIN_TOKENS ||
|
|
603
|
+
tokens >= COMPACT_AT_MAX_TOKENS) {
|
|
604
|
+
throw new Error(`${source} must be between ${COMPACT_AT_MIN_TOKENS} and ${COMPACT_AT_MAX_TOKENS - 1} tokens` +
|
|
605
|
+
` (or '1m' / 'off' for no gateway compaction target); got '${raw}'.`);
|
|
606
|
+
}
|
|
607
|
+
return tokens;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Which compaction target this launch runs under. Precedence, highest wins:
|
|
611
|
+
* 1. `opts.compactAt` — an already-parsed `--compact-at` flag value (`undefined` when the
|
|
612
|
+
* flag was not given at all; `null` is a valid, explicit "no target" from `1m`/`off`).
|
|
613
|
+
* 2. `MNEMOM_COMPACT_AT` (which `applyConfigToEnv` sets from the `compact_at` launcher.toml
|
|
614
|
+
* key, UNDER any real env var — so an operator's own env always wins over the file).
|
|
615
|
+
* 3. `null` — the default: no target, nothing new sent.
|
|
616
|
+
*/
|
|
617
|
+
export function resolveCompactAt(opts) {
|
|
618
|
+
if (opts.compactAt !== undefined)
|
|
619
|
+
return opts.compactAt;
|
|
620
|
+
const raw = (opts.env?.MNEMOM_COMPACT_AT ?? "").trim();
|
|
621
|
+
if (raw === "")
|
|
622
|
+
return null;
|
|
623
|
+
return parseCompactAt(raw, "MNEMOM_COMPACT_AT");
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* `--no-compaction-intercept` is a deliberate, documented "keep the intercept off for this
|
|
627
|
+
* launch no matter what toml/env says" signal. A compaction target needs that SAME intercept
|
|
628
|
+
* to arm (`buildSessionConfig` ORs them), so pairing an explicit `--no-compaction-intercept`
|
|
629
|
+
* with an armed `--compact-at` is a contradiction — refuse rather than silently pick a side
|
|
630
|
+
* (either silently re-arming the intercept the user just turned off, or silently sending an
|
|
631
|
+
* inert upload-target the gateway can never act on). `compactionIntercept` here is the raw,
|
|
632
|
+
* pre-`resolveCompactionIntercept` flag value: `false` only when `--no-compaction-intercept`
|
|
633
|
+
* was explicitly passed (or the experimental gate forced it, which `compactAt` cannot reach
|
|
634
|
+
* past either — see index.ts's `assertEnabled`), never the mere absence of `--compaction-intercept`.
|
|
635
|
+
*/
|
|
636
|
+
export function assertCompactAtCompatible(opts) {
|
|
637
|
+
if (opts.compactionIntercept === false && opts.compactAt !== null) {
|
|
638
|
+
throw new Error("mnemom agent: --compact-at needs the compaction intercept to arm, but it is off for " +
|
|
639
|
+
"this launch (--no-compaction-intercept, or the compaction-intercept experimental " +
|
|
640
|
+
"feature is disabled). Drop --no-compaction-intercept (and run `mnemom experimental " +
|
|
641
|
+
"enable compaction-intercept`), or pass --compact-at off.");
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* `200000` → `"200k"`, anything not k-aligned → the bare digits. Every real caller only ever
|
|
646
|
+
* passes a `parseCompactAt`-validated value, which is bounded to
|
|
647
|
+
* `[COMPACT_AT_MIN_TOKENS, COMPACT_AT_MAX_TOKENS)` — i.e. `[50_000, 999_999]` — so there is no
|
|
648
|
+
* multiple of 1,000,000 to round to an `"Nm"` form; that case is intentionally not handled here.
|
|
649
|
+
*/
|
|
650
|
+
function formatTokenCount(tokens) {
|
|
651
|
+
if (tokens % 1_000 === 0)
|
|
652
|
+
return `${tokens / 1_000}k`;
|
|
653
|
+
return String(tokens);
|
|
654
|
+
}
|
|
655
|
+
/** The launch-banner line for the resolved compaction target (always printed). */
|
|
656
|
+
export function describeCompactAt(compactAt) {
|
|
657
|
+
return compactAt === null
|
|
658
|
+
? "Claude Code default (1M)"
|
|
659
|
+
: `gateway at ${formatTokenCount(compactAt)}`;
|
|
660
|
+
}
|
|
496
661
|
/**
|
|
497
662
|
* Claude Code's opt-in for its gateway hint headers (`x-claude-code-request-class: main |
|
|
498
663
|
* compaction`, `x-claude-code-compaction: auto | manual | reactive`,
|
|
@@ -768,9 +933,11 @@ export function resolveContextProfile(opts) {
|
|
|
768
933
|
* `x-mnemom-session-config` payload, in wire order: the profile's keys first
|
|
769
934
|
* (`CONTEXT_RUNTIME_ENABLED` for `off`; the three fold-tuning keys for `aggressive`; nothing
|
|
770
935
|
* for `balanced`), then `CONTEXT_HANDOFF_ENABLED: "true"` when context handoff is ON, then
|
|
771
|
-
* `GATEWAY_USAGE_REAL_SIZE: "on"` + `GATEWAY_COMPACTION_INTERCEPT: "on"` when the
|
|
772
|
-
*
|
|
773
|
-
*
|
|
936
|
+
* `GATEWAY_USAGE_REAL_SIZE: "on"` + `GATEWAY_COMPACTION_INTERCEPT: "on"` when EITHER the
|
|
937
|
+
* `compaction_intercept` setting is ON OR `--compact-at` arms a target below 1M (the target
|
|
938
|
+
* cannot be honored without the intercept), then `GATEWAY_COMPACTION_UPLOAD_TARGET_TOKENS: "<N>"`
|
|
939
|
+
* when a target is set. Empty for `balanced` with everything off — the common case, where no
|
|
940
|
+
* header is sent at all. Exported so `--dry-run` can list exactly what the header carries.
|
|
774
941
|
*/
|
|
775
942
|
export function buildSessionConfig(opts) {
|
|
776
943
|
const config = {};
|
|
@@ -778,10 +945,17 @@ export function buildSessionConfig(opts) {
|
|
|
778
945
|
config[k] = v;
|
|
779
946
|
if (opts.contextHandoff)
|
|
780
947
|
config[CONTEXT_HANDOFF_KEY] = "true";
|
|
781
|
-
|
|
948
|
+
const hasCompactionTarget = opts.compactAt !== undefined && opts.compactAt !== null;
|
|
949
|
+
// A target needs the compaction intercept + real-size rewrite to arm (#2216) — without them
|
|
950
|
+
// the upload-target key is silently inert (compaction-align.ts: "armed only inside the
|
|
951
|
+
// intercept"). Deliberately NOT GATEWAY_COMPACTION_REFOLD — a separate, costlier opt-in.
|
|
952
|
+
if (opts.compactionIntercept || hasCompactionTarget) {
|
|
782
953
|
config[USAGE_REAL_SIZE_KEY] = "on";
|
|
783
954
|
config[COMPACTION_INTERCEPT_KEY] = "on";
|
|
784
955
|
}
|
|
956
|
+
if (hasCompactionTarget) {
|
|
957
|
+
config[GATEWAY_COMPACTION_UPLOAD_TARGET_KEY] = String(opts.compactAt);
|
|
958
|
+
}
|
|
785
959
|
return config;
|
|
786
960
|
}
|
|
787
961
|
/**
|
|
@@ -800,12 +974,15 @@ export function buildSessionConfigPayload(opts) {
|
|
|
800
974
|
}
|
|
801
975
|
/**
|
|
802
976
|
* The `x-mnemom-session-config` value for this launch, or `undefined` when there is
|
|
803
|
-
* nothing to override (the gateway's own defaults stand — `balanced` with
|
|
804
|
-
* byte-identical to a launch before profiles existed).
|
|
977
|
+
* nothing to override (the gateway's own defaults stand — `balanced` with everything else off,
|
|
978
|
+
* byte-identical to a launch before profiles existed). Independent overlays:
|
|
805
979
|
* - the context profile (`buildSessionConfig`): `off` → `CONTEXT_RUNTIME_ENABLED: "false"`,
|
|
806
980
|
* `aggressive` → the three fold-tuning keys, `balanced` → nothing;
|
|
807
981
|
* - `compactionIntercept: true` → `GATEWAY_USAGE_REAL_SIZE` + `GATEWAY_COMPACTION_INTERCEPT`
|
|
808
982
|
* `"on"` (the `compaction_intercept` setting);
|
|
983
|
+
* - `compactAt: <N>` (`--compact-at`, below 1M) → the SAME two intercept keys plus
|
|
984
|
+
* `GATEWAY_COMPACTION_UPLOAD_TARGET_TOKENS: "<N>"`; unset, `1m` or `off` → nothing beyond
|
|
985
|
+
* what `compactionIntercept` already sent (Claude Code's own 1M window, untouched);
|
|
809
986
|
* - `contextHandoff: true` → `CONTEXT_HANDOFF_ENABLED: "true"` (the `context_handoff`
|
|
810
987
|
* setting) — the SAME header the `handoff` MCP tool's own call to
|
|
811
988
|
* `/mnemom/context/handoff` must also send, since the gateway resolves the flag
|
|
@@ -13,6 +13,15 @@
|
|
|
13
13
|
* modules are the thin drivers.
|
|
14
14
|
*/
|
|
15
15
|
export type ExperimentalId = "context-handoff" | "slack" | "implicit" | "compaction-intercept";
|
|
16
|
+
/**
|
|
17
|
+
* Is a `compact_at` / `MNEMOM_COMPACT_AT` value (trimmed, case-insensitive) an ARMED gateway
|
|
18
|
+
* target, as opposed to "no target" (`""`, `"1m"`, `"off"`)? Deliberately duplicates
|
|
19
|
+
* `parseCompactAt`'s no-target spellings rather than importing it, to keep this registry
|
|
20
|
+
* dependency-free (see the module doc); an unparseable value (bad k/m suffix, out of range)
|
|
21
|
+
* is treated as armed so the gate errs toward requiring the opt-in rather than toward silently
|
|
22
|
+
* admitting a value the real parser would reject anyway.
|
|
23
|
+
*/
|
|
24
|
+
export declare function compactAtArmed(raw: string | undefined): boolean;
|
|
16
25
|
export interface ExperimentalFeature {
|
|
17
26
|
id: ExperimentalId;
|
|
18
27
|
summary: string;
|
|
@@ -73,8 +82,8 @@ export declare function assertEnabled(uses: ReadonlyArray<{
|
|
|
73
82
|
export declare function experimentalIdForConfigKey(key: string, value: string): ExperimentalId | undefined;
|
|
74
83
|
/**
|
|
75
84
|
* The stderr lines for experimental settings a launch ignores because their feature
|
|
76
|
-
* is off: launcher.toml keys (`entries` as `[key, value]`), MNEMOM_AGENT_CONTEXT_HANDOFF
|
|
77
|
-
* MNEMOM_AGENT_COMPACTION_INTERCEPT.
|
|
85
|
+
* is off: launcher.toml keys (`entries` as `[key, value]`), MNEMOM_AGENT_CONTEXT_HANDOFF,
|
|
86
|
+
* MNEMOM_AGENT_COMPACTION_INTERCEPT and MNEMOM_COMPACT_AT (an armed target only).
|
|
78
87
|
*/
|
|
79
88
|
export declare function ignoredSettingLines(opts: {
|
|
80
89
|
entries: ReadonlyArray<[string, string]>;
|
package/dist/lib/experimental.js
CHANGED
|
@@ -12,6 +12,18 @@
|
|
|
12
12
|
* Everything here is pure or dependency-injected; index.ts and the command
|
|
13
13
|
* modules are the thin drivers.
|
|
14
14
|
*/
|
|
15
|
+
/**
|
|
16
|
+
* Is a `compact_at` / `MNEMOM_COMPACT_AT` value (trimmed, case-insensitive) an ARMED gateway
|
|
17
|
+
* target, as opposed to "no target" (`""`, `"1m"`, `"off"`)? Deliberately duplicates
|
|
18
|
+
* `parseCompactAt`'s no-target spellings rather than importing it, to keep this registry
|
|
19
|
+
* dependency-free (see the module doc); an unparseable value (bad k/m suffix, out of range)
|
|
20
|
+
* is treated as armed so the gate errs toward requiring the opt-in rather than toward silently
|
|
21
|
+
* admitting a value the real parser would reject anyway.
|
|
22
|
+
*/
|
|
23
|
+
export function compactAtArmed(raw) {
|
|
24
|
+
const v = (raw ?? "").trim().toLowerCase();
|
|
25
|
+
return v !== "" && v !== "1m" && v !== "off";
|
|
26
|
+
}
|
|
15
27
|
export const EXPERIMENTAL_FEATURES = [
|
|
16
28
|
{
|
|
17
29
|
id: "context-handoff",
|
|
@@ -140,6 +152,9 @@ export function experimentalIdForConfigKey(key, value) {
|
|
|
140
152
|
return value === "true" ? "context-handoff" : undefined;
|
|
141
153
|
case "compaction_intercept":
|
|
142
154
|
return value === "true" ? "compaction-intercept" : undefined;
|
|
155
|
+
case "compact_at":
|
|
156
|
+
// Arms the SAME gateway keys compaction_intercept does — same gate.
|
|
157
|
+
return compactAtArmed(value) ? "compaction-intercept" : undefined;
|
|
143
158
|
case "goal_mode":
|
|
144
159
|
return value === "implicit" ? "implicit" : undefined;
|
|
145
160
|
default:
|
|
@@ -152,8 +167,8 @@ function truthy(raw) {
|
|
|
152
167
|
}
|
|
153
168
|
/**
|
|
154
169
|
* The stderr lines for experimental settings a launch ignores because their feature
|
|
155
|
-
* is off: launcher.toml keys (`entries` as `[key, value]`), MNEMOM_AGENT_CONTEXT_HANDOFF
|
|
156
|
-
* MNEMOM_AGENT_COMPACTION_INTERCEPT.
|
|
170
|
+
* is off: launcher.toml keys (`entries` as `[key, value]`), MNEMOM_AGENT_CONTEXT_HANDOFF,
|
|
171
|
+
* MNEMOM_AGENT_COMPACTION_INTERCEPT and MNEMOM_COMPACT_AT (an armed target only).
|
|
157
172
|
*/
|
|
158
173
|
export function ignoredSettingLines(opts) {
|
|
159
174
|
const lines = [];
|
|
@@ -170,6 +185,10 @@ export function ignoredSettingLines(opts) {
|
|
|
170
185
|
!opts.enabled.includes("compaction-intercept")) {
|
|
171
186
|
lines.push(`mnemom agent: ignoring ${gatedMessage("MNEMOM_AGENT_COMPACTION_INTERCEPT", "compaction-intercept")}`);
|
|
172
187
|
}
|
|
188
|
+
if (compactAtArmed(opts.env.MNEMOM_COMPACT_AT) &&
|
|
189
|
+
!opts.enabled.includes("compaction-intercept")) {
|
|
190
|
+
lines.push(`mnemom agent: ignoring ${gatedMessage("MNEMOM_COMPACT_AT", "compaction-intercept")}`);
|
|
191
|
+
}
|
|
173
192
|
return lines;
|
|
174
193
|
}
|
|
175
194
|
/** The features an existing launcher.toml already uses (the one-time migration). */
|
|
@@ -6,17 +6,14 @@
|
|
|
6
6
|
* Read-only; no network. Only token counts, model ids and timestamps are read from a
|
|
7
7
|
* transcript line — never message content.
|
|
8
8
|
*
|
|
9
|
-
* The
|
|
10
|
-
*
|
|
11
|
-
* mnemom-adw scratchpad/
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* 700k – 1M aggressive → the 400–700k bound, EXTRAPOLATED (no measured pair)
|
|
18
|
-
* ≥ 1M balanced → 0 (live sessions only; a stock history cannot get
|
|
19
|
-
* there because Claude Code compacts first — counted)
|
|
9
|
+
* The rate is ONE flat number (operator policy 2026-10-02): the median paired total-bill
|
|
10
|
+
* saving of the S46 chain45 C2n arm (context runtime, aggressive profile, no goal) against
|
|
11
|
+
* stock, 23.8% over n = 13 counted rounds (mnemom-adw scratchpad/trustcode-mvp/runs/
|
|
12
|
+
* s46-status.json; early exits and void arms excluded). It applies to every request whose
|
|
13
|
+
* original context is at or above the aggressive profile's fold point (its 60k tail
|
|
14
|
+
* budget); smaller requests never fold, so they save 0. A flat rate over-counts mid-size
|
|
15
|
+
* contexts and under-counts large ones; in aggregate that evens out. Per-size rates come
|
|
16
|
+
* back once there are enough measured pairs.
|
|
20
17
|
*/
|
|
21
18
|
/** One billed request, deduped by message id. `t` is epoch seconds. */
|
|
22
19
|
export interface Row {
|
|
@@ -59,28 +56,14 @@ export declare function parseUsageLine(line: Buffer, subagent: boolean): {
|
|
|
59
56
|
* session copies messages) is skipped, so each request is billed once, to its origin.
|
|
60
57
|
*/
|
|
61
58
|
export declare function loadTranscript(path: string, seen?: Set<string>): Row[];
|
|
62
|
-
/**
|
|
63
|
-
export declare const
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
* aggressive: no measured pair below 100k or at/above 700k.
|
|
67
|
-
*/
|
|
68
|
-
export declare const CURVE_LOWER: Readonly<Record<"aggressive" | "balanced", Record<number, number>>>;
|
|
69
|
-
export declare const NO_FOLD_BELOW = 200000;
|
|
70
|
-
export declare const EXTRAPOLATE_FROM = 700000;
|
|
71
|
-
export declare const BALANCED_FROM = 1000000;
|
|
72
|
-
export declare const POLICY = "none <200k / aggressive 200k-1M / balanced 1M+ (1M+ applies only to live sessions)";
|
|
59
|
+
/** S46 C2n vs stock, median paired total-bill saving (n = 13). */
|
|
60
|
+
export declare const S46_MEDIAN_SAVING = 0.238;
|
|
61
|
+
/** The aggressive profile's tail budget (cli/src/lib/agent.ts): below it nothing folds. */
|
|
62
|
+
export declare const AGGRESSIVE_FOLD_FROM = 60000;
|
|
73
63
|
export declare const ESTIMATOR_METHOD: string;
|
|
74
|
-
export type Strategy = "none" | "aggressive"
|
|
75
|
-
export type RateNote = "
|
|
76
|
-
/** The
|
|
77
|
-
export declare function binOf(x: number): number;
|
|
78
|
-
/** The screen never claims a negative saving: a negative measured rate shows as 0. */
|
|
79
|
-
export declare function floorRate(raw: number): {
|
|
80
|
-
screen: number;
|
|
81
|
-
note: "floored_at_0" | null;
|
|
82
|
-
};
|
|
83
|
-
/** The per-request rate the screen uses (`screen`) and the honest measured one (`raw`). */
|
|
64
|
+
export type Strategy = "none" | "aggressive";
|
|
65
|
+
export type RateNote = "below_aggressive" | null;
|
|
66
|
+
/** The per-request rate the screen uses. `raw` equals `screen`: no floor, no curve. */
|
|
84
67
|
export declare function ruleRate(x: number): {
|
|
85
68
|
strategy: Strategy;
|
|
86
69
|
raw: number;
|
|
@@ -90,7 +73,7 @@ export declare function ruleRate(x: number): {
|
|
|
90
73
|
export interface Estimate {
|
|
91
74
|
actual: number;
|
|
92
75
|
saved: number;
|
|
93
|
-
/**
|
|
76
|
+
/** Same as `saved` now that no rate is floored (kept for the audit record). */
|
|
94
77
|
rawSaved: number;
|
|
95
78
|
peak: number;
|
|
96
79
|
requests: number;
|
|
@@ -6,17 +6,14 @@
|
|
|
6
6
|
* Read-only; no network. Only token counts, model ids and timestamps are read from a
|
|
7
7
|
* transcript line — never message content.
|
|
8
8
|
*
|
|
9
|
-
* The
|
|
10
|
-
*
|
|
11
|
-
* mnemom-adw scratchpad/
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* 700k – 1M aggressive → the 400–700k bound, EXTRAPOLATED (no measured pair)
|
|
18
|
-
* ≥ 1M balanced → 0 (live sessions only; a stock history cannot get
|
|
19
|
-
* there because Claude Code compacts first — counted)
|
|
9
|
+
* The rate is ONE flat number (operator policy 2026-10-02): the median paired total-bill
|
|
10
|
+
* saving of the S46 chain45 C2n arm (context runtime, aggressive profile, no goal) against
|
|
11
|
+
* stock, 23.8% over n = 13 counted rounds (mnemom-adw scratchpad/trustcode-mvp/runs/
|
|
12
|
+
* s46-status.json; early exits and void arms excluded). It applies to every request whose
|
|
13
|
+
* original context is at or above the aggressive profile's fold point (its 60k tail
|
|
14
|
+
* budget); smaller requests never fold, so they save 0. A flat rate over-counts mid-size
|
|
15
|
+
* contexts and under-counts large ones; in aggregate that evens out. Per-size rates come
|
|
16
|
+
* back once there are enough measured pairs.
|
|
20
17
|
*/
|
|
21
18
|
import { forEachLine, isoSeconds, parseJson } from "./lines.js";
|
|
22
19
|
/**
|
|
@@ -137,51 +134,19 @@ export function loadTranscript(path, seen) {
|
|
|
137
134
|
}
|
|
138
135
|
return out;
|
|
139
136
|
}
|
|
140
|
-
// ── the
|
|
141
|
-
/**
|
|
142
|
-
export const
|
|
143
|
-
/**
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// bin 1 (100-200k) is measured at -0.059 but never used: < 200k runs no fold.
|
|
149
|
-
aggressive: { 2: 0.137, 3: 0.282, 4: 0.342 },
|
|
150
|
-
balanced: { 0: -0.222, 1: -0.028, 2: 0.055, 3: 0.081, 4: 0.163, 5: 0.43 },
|
|
151
|
-
};
|
|
152
|
-
export const NO_FOLD_BELOW = 200_000;
|
|
153
|
-
export const EXTRAPOLATE_FROM = 700_000;
|
|
154
|
-
export const BALANCED_FROM = 1_000_000;
|
|
155
|
-
export const POLICY = "none <200k / aggressive 200k-1M / balanced 1M+ (1M+ applies only to live sessions)";
|
|
156
|
-
export const ESTIMATOR_METHOD = "empirical curve (lower 95% bound, rounded down), strategy per request by its original context, " +
|
|
157
|
-
`policy ${POLICY}: < 200k the balanced arm's measured rate floored at 0, 200k-1M aggressive by bin ` +
|
|
158
|
-
"(>= 700k: the 400-700k rate, extrapolated); request count and output held at stock";
|
|
159
|
-
/** The curve bin of an original context (0 = below 100k; the top bin is open-ended). */
|
|
160
|
-
export function binOf(x) {
|
|
161
|
-
let i = 0;
|
|
162
|
-
while (i < CURVE_EDGES.length - 2 && x >= CURVE_EDGES[i + 1])
|
|
163
|
-
i++;
|
|
164
|
-
return i;
|
|
165
|
-
}
|
|
166
|
-
/** The screen never claims a negative saving: a negative measured rate shows as 0. */
|
|
167
|
-
export function floorRate(raw) {
|
|
168
|
-
return raw < 0 ? { screen: 0, note: "floored_at_0" } : { screen: raw, note: null };
|
|
169
|
-
}
|
|
170
|
-
/** The per-request rate the screen uses (`screen`) and the honest measured one (`raw`). */
|
|
137
|
+
// ── the saving rate ──────────────────────────────────────────────────────────
|
|
138
|
+
/** S46 C2n vs stock, median paired total-bill saving (n = 13). */
|
|
139
|
+
export const S46_MEDIAN_SAVING = 0.238;
|
|
140
|
+
/** The aggressive profile's tail budget (cli/src/lib/agent.ts): below it nothing folds. */
|
|
141
|
+
export const AGGRESSIVE_FOLD_FROM = 60_000;
|
|
142
|
+
export const ESTIMATOR_METHOD = "flat S46 median (23.8%, C2n vs stock, n = 13) on every request whose original context is " +
|
|
143
|
+
">= 60k (the aggressive profile's fold point), 0 below; request count and output held at stock";
|
|
144
|
+
/** The per-request rate the screen uses. `raw` equals `screen`: no floor, no curve. */
|
|
171
145
|
export function ruleRate(x) {
|
|
172
|
-
if (x
|
|
173
|
-
return { strategy: "
|
|
174
|
-
}
|
|
175
|
-
if (x < NO_FOLD_BELOW) {
|
|
176
|
-
const raw = CURVE_LOWER.balanced[binOf(x)];
|
|
177
|
-
return { strategy: "none", raw, ...floorRate(raw) };
|
|
178
|
-
}
|
|
179
|
-
if (x >= EXTRAPOLATE_FROM) {
|
|
180
|
-
const r = CURVE_LOWER.aggressive[binOf(400_000)];
|
|
181
|
-
return { strategy: "aggressive", raw: r, screen: r, note: "extrapolated" };
|
|
146
|
+
if (x < AGGRESSIVE_FOLD_FROM) {
|
|
147
|
+
return { strategy: "none", raw: 0, screen: 0, note: "below_aggressive" };
|
|
182
148
|
}
|
|
183
|
-
|
|
184
|
-
return { strategy: "aggressive", raw: r, screen: r, note: null };
|
|
149
|
+
return { strategy: "aggressive", raw: S46_MEDIAN_SAVING, screen: S46_MEDIAN_SAVING, note: null };
|
|
185
150
|
}
|
|
186
151
|
/** The one swap point: rows → list-price actual and the conservative saving. */
|
|
187
152
|
export function estimate(rows) {
|
|
@@ -209,7 +209,7 @@ export const LEDGER_SIGNALS = new Set(["registry_cc_id", "registry_conv_id"]);
|
|
|
209
209
|
/** The screen's hard budget: a regression in buildScreen's line dropping must fail loudly. */
|
|
210
210
|
export function assertScreenBudget(lines) {
|
|
211
211
|
if (lines.length > MAX_LINES || lines.some((l) => l.length > WIDTH)) {
|
|
212
|
-
throw new Error(
|
|
212
|
+
throw new Error(`first-run screen exceeded its ${MAX_LINES} x ${WIDTH} budget`);
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
/** A transcript's mtime (ms), 0 when it vanished since discovery (it then sorts first). */
|
|
@@ -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.
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* - No live counter is promised: `mnemom agent` has no status bar today.
|
|
26
26
|
*/
|
|
27
27
|
export declare const WIDTH = 78;
|
|
28
|
-
export declare const MAX_LINES =
|
|
28
|
+
export declare const MAX_LINES = 22;
|
|
29
29
|
/** Less history than this (API prices, in the window) is too little to backtest. */
|
|
30
30
|
export declare const MIN_HISTORY_USD = 5;
|
|
31
31
|
/** Variation A iff the shown saving is at least this many dollars and this many percent. */
|
|
@@ -33,12 +33,12 @@ export declare const STRONG_MIN_USD = 5;
|
|
|
33
33
|
export declare const STRONG_MIN_PCT = 5;
|
|
34
34
|
export declare const DAYS = 30;
|
|
35
35
|
/**
|
|
36
|
-
* The forward line:
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* undercounts output).
|
|
36
|
+
* The forward line: chain45 S46 C2n (context runtime, aggressive profile) total bill vs
|
|
37
|
+
* stock, -20.9% [-25.6, -15.9], Opus 5.5, n=13 (r15 readout; operator 2026-10-02: quote
|
|
38
|
+
* S46, not S43), 95% CI rounded to fives, 15-25. The d240p1 ~45% figure is NOT used
|
|
39
|
+
* (its re-bill undercounts output).
|
|
40
40
|
*/
|
|
41
|
-
export declare const FORWARD_LINE = "Long sessions are where Mnemom saves the most: about 15 to 25% in our
|
|
41
|
+
export declare const FORWARD_LINE = "Long sessions are where Mnemom saves the most: about 15 to 25% in our datasets.";
|
|
42
42
|
/** Prices, promotions and plan allocations move; the saving is in usage (operator 2026-10-01). */
|
|
43
43
|
export declare const PRICE_LINE = "Prices and plan limits change. Mnemom cuts what you use, so you stay ahead.";
|
|
44
44
|
/** What `mnemom agent` does today: routes through the gateway, records, keeps context lean. */
|
|
@@ -52,8 +52,8 @@ export declare function sig3(x: number): number;
|
|
|
52
52
|
* screen. The implied saving is the shown saving, which never exceeds the raw saving.
|
|
53
53
|
*/
|
|
54
54
|
export declare function pair(total: number, saved: number): [number, number, number];
|
|
55
|
-
/** part / whole as a percentage rounded DOWN
|
|
56
|
-
export declare function
|
|
55
|
+
/** part / whole as a whole-number percentage, decimals dropped (rounded DOWN). */
|
|
56
|
+
export declare function pctDown(part: number, whole: number): number;
|
|
57
57
|
export declare function day(t: number): string;
|
|
58
58
|
export declare function usd(n: number): string;
|
|
59
59
|
export declare function clean(s: string): string;
|