@mnemom/mnemom 0.17.21 → 0.17.23

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 CHANGED
@@ -45,6 +45,7 @@ That's it. `mnemom init` detects your configured AI provider API keys (Anthropic
45
45
  | `mnemom card validate <file>` | Validate card JSON locally |
46
46
  | `mnemom usage --org <id>` | Show per-person token and request consumption |
47
47
  | `mnemom agent [name]` | Launch a governed coding agent through the gateway |
48
+ | `mnemom agent export [agent]` | Save an agent's full record to a file (JSON or CSV) |
48
49
 
49
50
  ### `mnemom agent` — goal alignment
50
51
 
@@ -192,22 +193,33 @@ which request is a compaction (a gateway that does not read them ignores them).
192
193
  never sets or changes your own auto-compaction settings (`CLAUDE_AUTOCOMPACT_PCT_OVERRIDE`,
193
194
  `CLAUDE_CODE_AUTO_COMPACT_WINDOW`, or `autoCompactWindow` in settings.json).
194
195
 
195
- **Experimental, off by default.** `--compaction-intercept` (after `mnemom experimental enable
196
- compaction-intercept`; standing default `mnemom agent config set compaction_intercept on`, or
197
- `MNEMOM_AGENT_COMPACTION_INTERCEPT=1`) sends `GATEWAY_USAGE_REAL_SIZE=on` and
198
- `GATEWAY_COMPACTION_INTERCEPT=on` in the per-session config header, asking the gateway to report
199
- the real context size (so Claude Code compacts at its own threshold) and to answer that
200
- compaction at once from its fold instead of a blocking model call. This needs the companion
201
- gateway support: a gateway without it drops both keys (they are echoed back in
202
- `x-mnemom-session-config-dropped`) and the session behaves exactly as with the setting off. `--no-compaction-intercept` turns it off for
203
- one launch.
196
+ **Compaction intercept: on by default (since 0.17.23).** Every launch sends
197
+ `GATEWAY_USAGE_REAL_SIZE=on` and `GATEWAY_COMPACTION_INTERCEPT=on` in the per-session config
198
+ header, asking the gateway to report the real context size (so Claude Code compacts at its own
199
+ threshold) and to answer that compaction at once from its fold instead of a blocking model call.
200
+ This matches the production gateway cell's own default, so the header mainly makes the setting
201
+ explicit and keeps every cell consistent; the launch banner shows it as
202
+ `compaction: intercept on (gateway)`. A gateway without the companion support drops both keys
203
+ (they are echoed back in `x-mnemom-session-config-dropped`) and the session behaves as before.
204
+
205
+ It used to be experimental (`mnemom experimental enable compaction-intercept`); that command is
206
+ now a harmless no-op that says so. To turn it off, use any of:
207
+
208
+ ```bash
209
+ mnemom agent my-task --no-compaction-intercept # this launch only
210
+ mnemom agent config set compaction_intercept false # persists in ~/.mnemom/launcher.toml
211
+ export MNEMOM_AGENT_COMPACTION_INTERCEPT=0 # for every launch in this shell
212
+ ```
213
+
214
+ An opt-out sends both keys as `off` by name, so it also turns the intercept off on a cell whose
215
+ own default is on (sending nothing would leave that cell intercepting).
204
216
 
205
217
  #### Compaction target (`--compact-at`)
206
218
 
207
219
  `--compact-at <n|200k|1m|off>` lets you play with WHEN the gateway pulls this session's own
208
220
  compaction point forward, instead of leaving it entirely to Claude Code's normal 1M-window
209
221
  schedule. **Unset, `1m`, or `off` all mean no target** — Claude Code runs its normal 1M window
210
- and the launcher sends nothing new (byte-identical to a launch without this setting at all). A
222
+ and the launcher sends no target key (only the default intercept keys above). A
211
223
  token count below 1M (k/m suffixes OK, e.g. `200k` or `350000`, from 50k up to just under 1M)
212
224
  arms a gateway-side target for this session: the per-session config header carries
213
225
  `GATEWAY_USAGE_REAL_SIZE=on` + `GATEWAY_COMPACTION_INTERCEPT=on` (the two keys a target needs to
@@ -222,10 +234,10 @@ nothing back, so the launcher prints a warning at launch (and in `--dry-run`) wh
222
234
  the target will be ignored, and a note when it can't tell the window (a passthrough `-- --model`,
223
235
  a resume, or a CLI other than Claude Code).
224
236
 
225
- **Arming a target needs the same opt-in as `--compaction-intercept`** (`mnemom experimental
226
- enable compaction-intercept`) — a target arms the identical pair of gateway keys, so it needs
227
- the identical gate; the flag itself stays visible in `--help` (it's the knob you asked for), only
228
- actually _arming_ a target is gated. Passing `--no-compaction-intercept` together with an armed
237
+ **Arming a target needs no opt-in** (since 0.17.23 it rides on the default-on intercept). A
238
+ target arms the intercept's two keys even when a standing `compaction_intercept = false` or
239
+ `MNEMOM_AGENT_COMPACTION_INTERCEPT=0` turned it off, since the target cannot work without them.
240
+ Passing `--no-compaction-intercept` together with an armed
229
241
  `--compact-at` is refused outright (they contradict: the target cannot arm without the intercept
230
242
  you just turned off). So is an armed `--compact-at` (or `--compaction-intercept`) with context
231
243
  profile `off`: with the fold off there is no fold to answer the compaction from.
@@ -234,7 +246,6 @@ Set it per launch, as a standing default, or in the environment (flag > env > `l
234
246
  default):
235
247
 
236
248
  ```bash
237
- mnemom experimental enable compaction-intercept # one-time opt-in
238
249
  mnemom agent my-task --compact-at 200k # this launch only
239
250
  mnemom agent config set compact_at 200k # persists in ~/.mnemom/launcher.toml
240
251
  export MNEMOM_COMPACT_AT=200k # for every launch in this shell
@@ -244,6 +255,31 @@ export MNEMOM_COMPACT_AT=200k # for every launch in this shell
244
255
  Code default (1M)`) and, when a target is set, the decoded session-config keys; the session
245
256
  ledger records the effective value.
246
257
 
258
+ ### `mnemom agent export` — an agent's full record
259
+
260
+ `mnemom agent export [id-or-name]` saves the agent's full record — integrity checkpoints, score
261
+ history, reclassifications, card amendments, disagreement reviews and policy evaluations with
262
+ gaps — to `./mnemom-record-<agent>-<date>.json` (`--format csv` for CSV). Without an agent it
263
+ uses `MNEMOM_AGENT`, else the agent saved in `~/.mnemom/launcher.toml` (`mnemom agent config
264
+ set agent …`), else the agent of your latest `mnemom agent` launch. You need to be a member of
265
+ the org that owns the agent.
266
+
267
+ ```bash
268
+ mnemom agent export my-agent # everything, as JSON
269
+ mnemom agent export my-agent --since 30d # the last 30 days
270
+ mnemom agent export my-agent --from 2026-09-01 --to 2026-09-30 --format csv
271
+ mnemom agent export my-agent -o - | jq '.checkpoints | length' # stdout; summary on stderr
272
+ ```
273
+
274
+ `--from`/`--to` take an ISO-8601 date or datetime (a bare `--to` date includes that whole UTC
275
+ day). The command refuses to overwrite an existing file unless you pass `--force`, and prints a
276
+ summary: the period, rows per section, a SHA-256 checksum of the file (so a recipient can
277
+ confirm it was not altered in transit) and the agent's public verification links. The file
278
+ holds full checkpoint reasoning that outsiders normally see redacted, so it is written
279
+ readable by you only (mode 0600); to prove the agent's record to someone without sharing the
280
+ contents, send the public verification link instead. If the period is too large to export in
281
+ one go, narrow it with `--since`, `--from` or `--to`.
282
+
247
283
  ## How It Works
248
284
 
249
285
  ```
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `mnemom agent export [id-or-name]` — download an agent's full record
3
+ * (`GET /v1/agents/{id}/compliance-export`) to a file, or to stdout with `-o -`.
4
+ *
5
+ * Without `[id-or-name]` the agent is, in order: MNEMOM_AGENT, the saved
6
+ * `agent` in ~/.mnemom/launcher.toml, then the agent of the newest launch in the
7
+ * session ledger (lib/agent-export.ts pickAgentRef).
8
+ *
9
+ * Pure apart from the injected deps (`resolveAgentId`, `agentSources`,
10
+ * `fetchExport`, `writeFile`, `writeStdout`, `out`, `err`, `now`), so every arm is
11
+ * unit-tested; `liveExportDeps` is the thin driver index.ts wires in. The
12
+ * parsing, counts, summary and error wording live in lib/agent-export.ts.
13
+ *
14
+ * Returns the process exit code (0 ok, 1 on any error) instead of exiting, so
15
+ * index.ts sets `process.exitCode` from it.
16
+ */
17
+ import { type DefaultAgentSources, type ExportFlags } from "../lib/agent-export.js";
18
+ export interface AgentExportDeps {
19
+ /** id-or-name → agent id. Exits itself when unresolvable. */
20
+ resolveAgentId: (ref: string) => Promise<string>;
21
+ /** Where the agent comes from when no `[id-or-name]` is given. */
22
+ agentSources: DefaultAgentSources;
23
+ fetchExport: (agentId: string, query: string) => Promise<{
24
+ body: Uint8Array;
25
+ headers: Headers;
26
+ }>;
27
+ /** Write owner-only (0600). Without `force`, throws EEXIST when the file exists. */
28
+ writeFile: (path: string, bytes: Uint8Array, force: boolean) => void;
29
+ writeStdout: (bytes: Uint8Array) => void;
30
+ out: (line: string) => void;
31
+ err: (line: string) => void;
32
+ now: () => Date;
33
+ apiBase: () => string;
34
+ websiteBase: () => string;
35
+ }
36
+ export interface PrivateFileOps {
37
+ chmodSync: (path: string, mode: number) => void;
38
+ writeFileSync: (path: string, bytes: Uint8Array, opts: {
39
+ mode: number;
40
+ flag: string;
41
+ }) => void;
42
+ }
43
+ /**
44
+ * Write `bytes` to `path` readable by the owner only. Without `force`, `wx`
45
+ * makes the no-overwrite check and the create one step. With `force`, an
46
+ * existing file is narrowed to 0600 BEFORE the write (so the new contents are
47
+ * never readable under its old mode) — chmod-and-catch-ENOENT rather than
48
+ * exists-then-chmod — and chmod'ed again after, since `writeFileSync` only
49
+ * applies `mode` to a file it creates.
50
+ */
51
+ export declare function writePrivateFile(path: string, bytes: Uint8Array, force: boolean, ops?: PrivateFileOps): void;
52
+ export declare const liveExportDeps: AgentExportDeps;
53
+ export declare function agentExportCommand(ref: string | undefined, flags: ExportFlags, deps: AgentExportDeps): Promise<number>;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * `mnemom agent export [id-or-name]` — download an agent's full record
3
+ * (`GET /v1/agents/{id}/compliance-export`) to a file, or to stdout with `-o -`.
4
+ *
5
+ * Without `[id-or-name]` the agent is, in order: MNEMOM_AGENT, the saved
6
+ * `agent` in ~/.mnemom/launcher.toml, then the agent of the newest launch in the
7
+ * session ledger (lib/agent-export.ts pickAgentRef).
8
+ *
9
+ * Pure apart from the injected deps (`resolveAgentId`, `agentSources`,
10
+ * `fetchExport`, `writeFile`, `writeStdout`, `out`, `err`, `now`), so every arm is
11
+ * unit-tested; `liveExportDeps` is the thin driver index.ts wires in. The
12
+ * parsing, counts, summary and error wording live in lib/agent-export.ts.
13
+ *
14
+ * Returns the process exit code (0 ok, 1 on any error) instead of exiting, so
15
+ * index.ts sets `process.exitCode` from it.
16
+ */
17
+ import { chmodSync, writeFileSync } from "node:fs";
18
+ import { readSessionRecords } from "../lib/agent-ledger.js";
19
+ import { getComplianceExport, resolveAgentId } from "../lib/api.js";
20
+ import { getApiUrl, getWebsiteUrl, MNEMOM_DIR } from "../lib/config.js";
21
+ import { loadLauncherConfig } from "../lib/launcher-config.js";
22
+ import { buildExportQuery, buildSummary, defaultOutputPath, exportErrorMessage, extractCounts, filenameLabel, formatSummaryLines, NO_AGENT_MESSAGE, parseExportFlags, pickAgentRef, sensitivityNote, writeErrorMessage, } from "../lib/agent-export.js";
23
+ import { fmt } from "../lib/format.js";
24
+ /**
25
+ * Write `bytes` to `path` readable by the owner only. Without `force`, `wx`
26
+ * makes the no-overwrite check and the create one step. With `force`, an
27
+ * existing file is narrowed to 0600 BEFORE the write (so the new contents are
28
+ * never readable under its old mode) — chmod-and-catch-ENOENT rather than
29
+ * exists-then-chmod — and chmod'ed again after, since `writeFileSync` only
30
+ * applies `mode` to a file it creates.
31
+ */
32
+ export function writePrivateFile(path, bytes, force, ops = { chmodSync, writeFileSync }) {
33
+ if (force) {
34
+ try {
35
+ ops.chmodSync(path, 0o600);
36
+ }
37
+ catch (error) {
38
+ if (error.code !== "ENOENT")
39
+ throw error;
40
+ }
41
+ }
42
+ ops.writeFileSync(path, bytes, { mode: 0o600, flag: force ? "w" : "wx" });
43
+ ops.chmodSync(path, 0o600);
44
+ }
45
+ export const liveExportDeps = {
46
+ resolveAgentId: (ref) => resolveAgentId(ref),
47
+ agentSources: {
48
+ env: () => process.env.MNEMOM_AGENT,
49
+ configAgent: () => loadLauncherConfig().agent,
50
+ // The ledger is oldest-first on disk.
51
+ ledger: () => readSessionRecords({ dir: MNEMOM_DIR }).reverse(),
52
+ },
53
+ fetchExport: (agentId, query) => getComplianceExport(agentId, query),
54
+ writeFile: (path, bytes, force) => writePrivateFile(path, bytes, force),
55
+ writeStdout: (bytes) => {
56
+ process.stdout.write(bytes);
57
+ },
58
+ out: (line) => console.log(line),
59
+ err: (line) => console.error(line),
60
+ now: () => new Date(),
61
+ apiBase: getApiUrl,
62
+ websiteBase: getWebsiteUrl,
63
+ };
64
+ export async function agentExportCommand(ref, flags, deps) {
65
+ const now = deps.now();
66
+ let parsed;
67
+ try {
68
+ parsed = parseExportFlags(flags, now);
69
+ }
70
+ catch (error) {
71
+ deps.err(fmt.error(error.message));
72
+ return 1;
73
+ }
74
+ let target;
75
+ try {
76
+ target = pickAgentRef(ref, deps.agentSources);
77
+ }
78
+ catch (error) {
79
+ // A malformed ~/.mnemom/launcher.toml — its message names the file to fix.
80
+ deps.err(fmt.error(error.message));
81
+ return 1;
82
+ }
83
+ if (target === null) {
84
+ deps.err(fmt.error(NO_AGENT_MESSAGE));
85
+ return 1;
86
+ }
87
+ const agentId = await deps.resolveAgentId(target.ref);
88
+ const output = parsed.output ?? defaultOutputPath(filenameLabel(target.label, agentId), parsed.format, now);
89
+ const toStdout = output === "-";
90
+ let response;
91
+ try {
92
+ response = await deps.fetchExport(agentId, buildExportQuery(parsed.format, parsed.period));
93
+ }
94
+ catch (error) {
95
+ deps.err(fmt.error(exportErrorMessage(error, agentId)));
96
+ return 1;
97
+ }
98
+ try {
99
+ if (toStdout)
100
+ deps.writeStdout(response.body);
101
+ else
102
+ deps.writeFile(output, response.body, parsed.force);
103
+ }
104
+ catch (error) {
105
+ deps.err(fmt.error(writeErrorMessage(error, output)));
106
+ return 1;
107
+ }
108
+ const summary = buildSummary({
109
+ agentId,
110
+ output,
111
+ format: parsed.format,
112
+ period: parsed.period,
113
+ body: response.body,
114
+ counts: extractCounts(parsed.format, response.body, response.headers),
115
+ websiteBase: deps.websiteBase(),
116
+ apiBase: deps.apiBase(),
117
+ });
118
+ // With `-o -` stdout carries the record itself, so the summary goes to stderr.
119
+ const say = toStdout ? deps.err : deps.out;
120
+ if (parsed.json)
121
+ say(JSON.stringify(summary, null, 2));
122
+ else
123
+ for (const line of formatSummaryLines(summary))
124
+ say(line);
125
+ deps.err(fmt.dim(sensitivityNote(toStdout)));
126
+ return 0;
127
+ }
@@ -94,10 +94,11 @@ export interface AgentOptions {
94
94
  */
95
95
  contextHandoff?: boolean;
96
96
  /**
97
- * EXPERIMENTAL, off by default (payload-bound fix): tells the gateway
98
- * `GATEWAY_USAGE_REAL_SIZE=on` + `GATEWAY_COMPACTION_INTERCEPT=on` for this session only (real
99
- * context size in usage, so Claude Code compacts at its OWN threshold; the compaction request
100
- * answered at once from the gateway's fold). Absent/false ⇒ no header key.
97
+ * ON by default since 0.17.23 (payload-bound fix; graduated from experimental): tells the
98
+ * gateway `GATEWAY_USAGE_REAL_SIZE=on` + `GATEWAY_COMPACTION_INTERCEPT=on` for this session
99
+ * (real context size in usage, so Claude Code compacts at its OWN threshold; the compaction
100
+ * request answered at once from the gateway's fold). `false` (--no-compaction-intercept) sends
101
+ * both `"off"`; absent ⇒ MNEMOM_AGENT_COMPACTION_INTERCEPT / launcher.toml, else on.
101
102
  */
102
103
  compactionIntercept?: boolean;
103
104
  /**
@@ -135,6 +136,12 @@ export interface AgentOptions {
135
136
  /** Print the resolved launch plan and exit — no key read, no spawn. */
136
137
  dryRun?: boolean;
137
138
  }
139
+ /**
140
+ * The intercept state as the launch banner and the dry-run plan print it. Context profile `off`
141
+ * sends the intercept off (`buildSessionConfig`), so it reads off there whatever the setting; an
142
+ * armed `--compact-at` target turns it on even over a standing opt-out.
143
+ */
144
+ export declare function describeCompactionIntercept(profile: ContextProfile | null, compactionIntercept: boolean, compactAt: number | null): string;
138
145
  /** Entry point for `mnemom agent [scenario]`. Throws on fatal misconfig. */
139
146
  export declare function agentCommand(scenarioArg: string | undefined, options?: AgentOptions): Promise<void>;
140
147
  /** The Claude Code session id a passthrough `--resume <id>` / `-r <id>` / `--resume=<id>` names. */
@@ -399,6 +406,8 @@ export declare function printPlan(ctx: {
399
406
  contextProfile: ContextProfile | null;
400
407
  /** The resolved compaction target (`resolveCompactAt`); `null` = no target (default). */
401
408
  compactAt: number | null;
409
+ /** The resolved compaction intercept (`resolveCompactionIntercept`); on by default. */
410
+ compactionIntercept?: boolean;
402
411
  /** The decoded `x-mnemom-session-config` overlay (`buildSessionConfig`); empty ⇒ no header. */
403
412
  sessionConfig: Record<string, string>;
404
413
  /** The experimental features on for this launch (the header's `experimental` array). */
@@ -53,21 +53,30 @@ import { CLI_VERSION } from "../version.js";
53
53
  /** The CLI's own provider-key store (zero 1Password): ~/.mnemom/launcher.json, 0600. */
54
54
  const CODE_STORE = LAUNCHER_STORE_PATH;
55
55
  const say = (line = "") => console.error(line);
56
- /** The one-line launch note when the compaction intercept is ON. */
57
- const COMPACTION_INTERCEPT_NOTE = "mnemom agent: compaction intercept ON (experimental) — requested from the gateway (GATEWAY_USAGE_REAL_SIZE, GATEWAY_COMPACTION_INTERCEPT); a gateway without the companion support drops both keys and the session runs as before.";
58
- const OFF_DROPS_COMPACTION_NOTE = "mnemom agent: compaction intercept off for this session — context profile off leaves compaction to the coding agent, so the standing compaction_intercept / compact_at setting is not sent.";
59
56
  /**
60
- * The compaction notes for a resolved launch. Context profile `off` sends the intercept off and
61
- * no target (`buildSessionConfig`), so a standing intercept or target is reported as dropped
62
- * rather than as ON.
57
+ * The intercept state as the launch banner and the dry-run plan print it. Context profile `off`
58
+ * sends the intercept off (`buildSessionConfig`), so it reads off there whatever the setting; an
59
+ * armed `--compact-at` target turns it on even over a standing opt-out.
60
+ */
61
+ export function describeCompactionIntercept(profile, compactionIntercept, compactAt) {
62
+ if (profile === "off")
63
+ return "intercept off (context profile off)";
64
+ if (compactionIntercept || compactAt !== null)
65
+ return "intercept on (gateway)";
66
+ return "intercept off (opted out)";
67
+ }
68
+ /** Context profile off drops a standing compaction target: say so rather than show it as set. */
69
+ const OFF_DROPS_TARGET_NOTE = "mnemom agent: context profile off leaves compaction to the coding agent, so the standing compact_at target is not sent.";
70
+ /**
71
+ * The compaction lines for a resolved launch: the intercept (on by default since 0.17.23 — no
72
+ * longer an experimental line) and the compaction target.
63
73
  */
64
74
  function sayCompaction(profile, compactionIntercept, compactAt, window) {
65
75
  const off = profile === "off";
76
+ say(`mnemom agent: compaction: ${describeCompactionIntercept(profile, compactionIntercept, compactAt)}`);
66
77
  sayCompactAt(off ? null : compactAt, window);
67
- if (off && (compactionIntercept || compactAt !== null))
68
- say(OFF_DROPS_COMPACTION_NOTE);
69
- else if (compactionIntercept)
70
- say(COMPACTION_INTERCEPT_NOTE);
78
+ if (off && compactAt !== null)
79
+ say(OFF_DROPS_TARGET_NOTE);
71
80
  }
72
81
  /**
73
82
  * The one-line launch note for the context profile: `null` (none chosen — nothing is sent) names
@@ -310,6 +319,7 @@ export async function agentCommand(scenarioArg, options = {}) {
310
319
  cli: planCli,
311
320
  contextProfile,
312
321
  compactAt,
322
+ compactionIntercept,
313
323
  sessionConfig,
314
324
  experimental,
315
325
  plugin: pluginFor(planCli.name).line,
@@ -1766,6 +1776,9 @@ export function printPlan(ctx) {
1766
1776
  L.push(` yolo: ${ctx.yolo ? "on (--dangerously-skip-permissions)" : "off"}`);
1767
1777
  L.push(` context: ${ctx.contextProfile ?? "cell default"}`);
1768
1778
  L.push(` compaction: ${describeCompactAt(ctx.compactAt)}`);
1779
+ if (ctx.compactionIntercept !== undefined) {
1780
+ L.push(` intercept: ${describeCompactionIntercept(ctx.contextProfile, ctx.compactionIntercept, ctx.compactAt).replace(/^intercept /, "")}`);
1781
+ }
1769
1782
  const overlay = Object.entries(ctx.sessionConfig);
1770
1783
  if (overlay.length > 0) {
1771
1784
  L.push(` session config (${SESSION_CONFIG_HEADER}):`);
@@ -3,7 +3,7 @@
3
3
  * features on and off (registry: lib/experimental.ts; stored in ~/.mnemom/config.json).
4
4
  */
5
5
  import { existsSync, readdirSync } from "node:fs";
6
- import { EXPERIMENTAL_CONTRACT, EXPERIMENTAL_ENV, EXPERIMENTAL_FEATURES, EXPERIMENTAL_IDS, isExperimentalId, migrateExperimental, resolveExperimental, } from "../lib/experimental.js";
6
+ import { EXPERIMENTAL_CONTRACT, EXPERIMENTAL_ENV, EXPERIMENTAL_FEATURES, EXPERIMENTAL_IDS, graduatedNote, isExperimentalId, migrateExperimental, resolveExperimental, } from "../lib/experimental.js";
7
7
  import { loadCliConfig, setExperimentalEnabled } from "../lib/cli-config.js";
8
8
  import { MNEMOM_DIR } from "../lib/config.js";
9
9
  import { launcherEntries, loadLauncherConfig } from "../lib/launcher-config.js";
@@ -50,6 +50,13 @@ export function experimentalListCommand(opts = {}, deps = defaultDeps()) {
50
50
  deps.log("Turn one on with `mnemom experimental enable <id>`.");
51
51
  }
52
52
  function toggle(verb, rawId, deps) {
53
+ // A feature that graduated to on-by-default: accept its old id as a no-op with a note, so
54
+ // a script or an old README that still runs `mnemom experimental enable <id>` keeps working.
55
+ const note = graduatedNote((rawId ?? "").trim().toLowerCase());
56
+ if (note) {
57
+ deps.log(note);
58
+ return;
59
+ }
53
60
  const id = requireId(rawId, verb);
54
61
  const current = new Set(resolveExperimental({ stored: deps.stored(), env: {} }).enabled);
55
62
  if (verb === "enable")
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ import { cardShowCommand, cardPublishCommand, cardValidateCommand, cardEditComma
13
13
  import { policyInitCommand, policyValidateCommand, policyPublishCommand, policyListCommand, policyTestCommand, policyEvaluateCommand, } from "./commands/policy.js";
14
14
  import { protectionShowCommand, protectionPublishCommand, protectionValidateCommand, protectionEditCommand, protectionDriftCommand, } from "./commands/protection.js";
15
15
  import { agentsListCommand, agentsClaimCommand, agentsMoveCommand } from "./commands/agents.js";
16
+ import { agentExportCommand, liveExportDeps } from "./commands/agent-export.js";
16
17
  import { agentSessionsCommand, agentShowCommand, liveSessionDeps, } from "./commands/agent-sessions.js";
17
18
  import { agentGoalPinCommand, agentGoalResetCommand, agentGoalShowCommand, liveGoalDeps, } from "./commands/agent-goal.js";
18
19
  import { orgListCommand, orgShowCommand, orgUseCommand } from "./commands/org.js";
@@ -144,7 +145,7 @@ function printAgentHelp(verb) {
144
145
  const agentCmd = program
145
146
  .command("agent [session] [passthrough...]")
146
147
  .description("Launch your governed agent (Claude Code in v1) through the Mnemom gateway — governed identity + optional sealed goal contract. Plain `mnemom agent` asks for a session name. " +
147
- "Sub-verbs: `setup` provisions your governed agent; `doctor` preflights prerequisites; `config …` manages persistent settings (~/.mnemom/launcher.toml); `--invite <token>` redeems a Mnemom Agent invitation (the one-liner your invitation email shows; `invite <token>` is its alias); `list` / `claim` / `move` manage the agents in your orgs; `sessions` lists your past launches and `show [ref]` merges one with its live goal state; `help [verb]` prints help and never launches.")
148
+ "Sub-verbs: `setup` provisions your governed agent; `doctor` preflights prerequisites; `config …` manages persistent settings (~/.mnemom/launcher.toml); `--invite <token>` redeems a Mnemom Agent invitation (the one-liner your invitation email shows; `invite <token>` is its alias); `list` / `claim` / `move` manage the agents in your orgs; `export` saves an agent's full record to a file; `sessions` lists your past launches and `show [ref]` merges one with its live goal state; `help [verb]` prints help and never launches.")
148
149
  .option("--goal <statement>", "Contract goal statement (required if any contract flag is given)")
149
150
  .option("--requirement <text>", "Contract requirement (repeatable)", collectRepeatable, [])
150
151
  .option("--allow <glob>", "Allowed path/glob (repeatable)", collectRepeatable, [])
@@ -203,7 +204,7 @@ const agentCmd = program
203
204
  throw new InvalidArgumentError(e.message);
204
205
  }
205
206
  }))
206
- .addOption(new Option("--compact-at <n|200k|1m|off>", 'Play with WHEN the gateway pulls this session\'s own compaction point forward, instead of leaving it to Claude Code\'s normal 1M-window schedule. A token count below 1M (k/m suffix OK, e.g. "200k") arms the gateway\'s compaction target for this session; unset, "1m" or "off" all mean no target — Claude Code runs its normal 1M window and nothing new is sent. Arming a target needs `mnemom experimental enable compaction-intercept` (the same opt-in --compaction-intercept uses — a target arms the identical gateway keys) and is refused if you also pass --no-compaction-intercept. Never touches Claude Code\'s own autocompact env/window. Also: config key compact_at, env MNEMOM_COMPACT_AT.').argParser((v) => {
207
+ .addOption(new Option("--compact-at <n|200k|1m|off>", 'Play with WHEN the gateway pulls this session\'s own compaction point forward, instead of leaving it to Claude Code\'s normal 1M-window schedule. A token count below 1M (k/m suffix OK, e.g. "200k") arms the gateway\'s compaction target for this session; unset, "1m" or "off" all mean no target — Claude Code runs its normal 1M window and nothing new is sent. A target rides on the compaction intercept (on by default) and is refused if you also pass --no-compaction-intercept. Never touches Claude Code\'s own autocompact env/window. Also: config key compact_at, env MNEMOM_COMPACT_AT.').argParser((v) => {
207
208
  // Validate with the same parser the other two surfaces (MNEMOM_COMPACT_AT, launcher.toml
208
209
  // compact_at) use, but return the validated RAW string rather than parseCompactAt's
209
210
  // `number | null` — commander's own option-value handling coerces a parseArg return of
@@ -221,8 +222,8 @@ const agentCmd = program
221
222
  }))
222
223
  .addOption(new Option("--context-handoff", "EXPERIMENTAL (`mnemom experimental enable context-handoff`): register the `handoff(note)` MCP tool and tell the gateway to turn on the context-handoff capability for this session").hideHelp(hiddenUnless("context-handoff")))
223
224
  .option("--no-context-handoff", "Keep context handoff off for this launch even when launcher.toml (context_handoff = true) or MNEMOM_AGENT_CONTEXT_HANDOFF turns it on")
224
- .addOption(new Option("--compaction-intercept", "EXPERIMENTAL (`mnemom experimental enable compaction-intercept`): tell the gateway to report the real context size (Claude Code compacts at its own threshold) and answer the compaction at once from its fold, for this session").hideHelp(hiddenUnless("compaction-intercept")))
225
- .option("--no-compaction-intercept", "Keep the compaction intercept off for this launch even when launcher.toml (compaction_intercept = true) or MNEMOM_AGENT_COMPACTION_INTERCEPT turns it on")
225
+ .option("--compaction-intercept", "Turn the compaction intercept on for this launch even when launcher.toml (compaction_intercept = false) or MNEMOM_AGENT_COMPACTION_INTERCEPT=0 turns it off. On by default: the gateway reports the real context size (so Claude Code compacts at its own threshold) and answers the compaction at once from its fold")
226
+ .option("--no-compaction-intercept", "Turn the compaction intercept off for this launch (it is on by default) — Claude Code's compaction goes to the model as usual. To turn it off for every launch: config key compaction_intercept = false, or env MNEMOM_AGENT_COMPACTION_INTERCEPT=0")
226
227
  .option("--dry-run", "Print the resolved launch plan and exit — no key read, no spawn")
227
228
  .option("--first-run", "Show the first-run look again: what Mnemom would have saved on your last 30 days of Claude Code (runs on this machine, read-only, nothing uploaded) — then exit")
228
229
  .action(async (scenario, passthrough, opts) => {
@@ -277,19 +278,6 @@ const agentCmd = program
277
278
  used: opts.contextHandoff === true,
278
279
  },
279
280
  { what: "--implicit", id: "implicit", used: opts.implicit === true },
280
- {
281
- what: "--compaction-intercept",
282
- id: "compaction-intercept",
283
- used: opts.compactionIntercept === true,
284
- },
285
- // --compact-at arms the SAME two gateway keys --compaction-intercept does
286
- // (buildSessionConfig ORs them) — it needs the identical opt-in, not a free pass
287
- // around it. `null`/unset ("1m"/"off"/no flag) is not a target, so it never gates.
288
- {
289
- what: "--compact-at",
290
- id: "compaction-intercept",
291
- used: typeof compactAtFlag === "number",
292
- },
293
281
  ], experimental);
294
282
  if (opts.gateway)
295
283
  process.env.MNEMOM_AGENT_GATEWAY = opts.gateway;
@@ -378,16 +366,13 @@ const agentCmd = program
378
366
  slack: opts.slack ?? cfg.slackListen,
379
367
  context: opts.context,
380
368
  contextProfile: opts.contextProfile,
381
- // Same gate: off ⇒ an explicit null (no target), so MNEMOM_COMPACT_AT cannot arm it —
382
- // an armed --compact-at was already refused above, and ignoredSettingLines told the
383
- // user the env value is ignored, so this is what makes that true.
384
- compactAt: experimental.includes("compaction-intercept") ? compactAtFlag : null,
369
+ // undefined ⇒ MNEMOM_COMPACT_AT / launcher.toml compact_at (resolveCompactAt).
370
+ compactAt: compactAtFlag,
385
371
  // Off ⇒ an explicit false, so MNEMOM_AGENT_CONTEXT_HANDOFF cannot turn it on.
386
372
  contextHandoff: experimental.includes("context-handoff") ? opts.contextHandoff : false,
387
- // Same gate: off ⇒ an explicit false, so MNEMOM_AGENT_COMPACTION_INTERCEPT cannot turn it on.
388
- compactionIntercept: experimental.includes("compaction-intercept")
389
- ? opts.compactionIntercept
390
- : false,
373
+ // On by default (since 0.17.23, no longer experimental): undefined ⇒
374
+ // MNEMOM_AGENT_COMPACTION_INTERCEPT / launcher.toml compaction_intercept, else ON.
375
+ compactionIntercept: opts.compactionIntercept,
391
376
  experimental,
392
377
  setup: opts.setup,
393
378
  governance,
@@ -875,6 +860,20 @@ agentCmd
875
860
  process.exit(1);
876
861
  }
877
862
  });
863
+ agentCmd
864
+ .command("export [id-or-name]")
865
+ .description("Save an agent's full record (checkpoints, score history, corrections) to a file. " +
866
+ "Without id-or-name: MNEMOM_AGENT, else the saved agent (mnemom agent config), else the agent of your latest launch")
867
+ .option("--format <json|csv>", "File format (default: json)")
868
+ .option("--since <Nd|Nh>", "Only the last N days or hours (instead of --from)")
869
+ .option("--from <date>", "Start of the period (ISO-8601 date or datetime)")
870
+ .option("--to <date>", "End of the period (ISO-8601 date or datetime; a date includes that day)")
871
+ .option("-o, --output <file|->", "Where to write it; - for stdout (default: ./mnemom-record-<agent>-<date>.<format>)")
872
+ .option("--force", "Overwrite the output file if it exists")
873
+ .option("--json", "Print the summary as JSON")
874
+ .action(async (idOrName, opts) => {
875
+ process.exitCode = await agentExportCommand(idOrName, opts, liveExportDeps);
876
+ });
878
877
  agentCmd
879
878
  .command("sessions")
880
879
  .description("List your Mnemom Agent sessions from the local ledger (~/.mnemom/sessions.jsonl), newest first — the last 20 unless --all")