@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 +51 -15
- package/dist/commands/agent-export.d.ts +53 -0
- package/dist/commands/agent-export.js +127 -0
- package/dist/commands/agent.d.ts +13 -4
- package/dist/commands/agent.js +23 -10
- package/dist/commands/experimental.js +8 -1
- package/dist/index.js +24 -25
- package/dist/lib/agent-export.d.ts +133 -0
- package/dist/lib/agent-export.js +293 -0
- package/dist/lib/agent.d.ts +30 -17
- package/dist/lib/agent.js +42 -30
- package/dist/lib/api.d.ts +12 -0
- package/dist/lib/api.js +17 -0
- package/dist/lib/experimental.d.ts +13 -10
- package/dist/lib/experimental.js +19 -38
- package/dist/lib/launcher-config.d.ts +5 -6
- package/dist/lib/launcher-config.js +4 -11
- package/package.json +1 -1
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
|
-
**
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
`x-mnemom-session-config-dropped`) and the session behaves
|
|
203
|
-
|
|
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
|
|
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
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
+
}
|
package/dist/commands/agent.d.ts
CHANGED
|
@@ -94,10 +94,11 @@ export interface AgentOptions {
|
|
|
94
94
|
*/
|
|
95
95
|
contextHandoff?: boolean;
|
|
96
96
|
/**
|
|
97
|
-
*
|
|
98
|
-
* `GATEWAY_USAGE_REAL_SIZE=on` + `GATEWAY_COMPACTION_INTERCEPT=on` for this session
|
|
99
|
-
* context size in usage, so Claude Code compacts at its OWN threshold; the compaction
|
|
100
|
-
* answered at once from the gateway's fold).
|
|
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). */
|
package/dist/commands/agent.js
CHANGED
|
@@ -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
|
|
61
|
-
*
|
|
62
|
-
*
|
|
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 &&
|
|
68
|
-
say(
|
|
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.
|
|
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
|
-
.
|
|
225
|
-
.option("--no-compaction-intercept", "
|
|
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
|
-
//
|
|
382
|
-
|
|
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
|
-
//
|
|
388
|
-
|
|
389
|
-
|
|
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")
|