@mnemom/mnemom 0.17.3-next.2 → 0.17.3-next.3
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 +25 -0
- package/dist/commands/agent-config.js +3 -0
- package/dist/commands/agent-sessions.js +2 -0
- package/dist/commands/agent.d.ts +13 -1
- package/dist/commands/agent.js +37 -12
- package/dist/index.js +13 -2
- package/dist/lib/agent-ledger.d.ts +7 -1
- package/dist/lib/agent.d.ts +57 -5
- package/dist/lib/agent.js +86 -9
- package/dist/lib/launcher-config.d.ts +7 -1
- package/dist/lib/launcher-config.js +16 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,6 +84,31 @@ An explicit launch shape (`--remote-control`, `--server`, `MNEMOM_AGENT_REMOTE_C
|
|
|
84
84
|
or `launch` in `launcher.toml`) also skips the Remote Control capability probe
|
|
85
85
|
(`claude --help`), which only feeds the automatic upgrade to Remote Control.
|
|
86
86
|
|
|
87
|
+
#### Context profile: how the gateway folds a session's context
|
|
88
|
+
|
|
89
|
+
`--context-profile <off|balanced|aggressive>` picks how the Mnemom gateway folds a
|
|
90
|
+
session's context. Each profile serves a different set of use cases:
|
|
91
|
+
|
|
92
|
+
- **`balanced`** (the default) keeps more of the earlier work verbatim and folds later —
|
|
93
|
+
for long sessions that revisit earlier files and decisions.
|
|
94
|
+
- **`aggressive`** folds earlier and keeps a tighter working set — for cost-sensitive
|
|
95
|
+
sessions and short, focused tasks.
|
|
96
|
+
- **`off`** leaves compaction to the coding agent.
|
|
97
|
+
|
|
98
|
+
Set it per launch, as a standing default, or in the environment (flag > env > `launcher.toml`):
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
mnemom agent my-task --context-profile aggressive # this launch only
|
|
102
|
+
mnemom agent config set context_profile aggressive # persists in ~/.mnemom/launcher.toml
|
|
103
|
+
export MNEMOM_AGENT_CONTEXT_PROFILE=aggressive # for every launch in this shell
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`--no-context` (or `context = false` in `launcher.toml`) is the same as `off`. Pass one or the
|
|
107
|
+
other: `--no-context` together with `--context-profile balanced` or `aggressive` is refused. A
|
|
108
|
+
`--context-profile` flag overrides a standing `context = false` in `launcher.toml`. `--dry-run`
|
|
109
|
+
prints the resolved profile and, when the launch sends the per-session config header, the
|
|
110
|
+
decoded key/value list; `mnemom agent show` and the session ledger record the profile.
|
|
111
|
+
|
|
87
112
|
#### Context handoff (experimental, off by default)
|
|
88
113
|
|
|
89
114
|
**Off by default.** `--context-handoff` (or the standing default `mnemom agent config set
|
|
@@ -190,6 +190,9 @@ function deleteKey(cfg, key) {
|
|
|
190
190
|
case "context_handoff":
|
|
191
191
|
delete cfg.contextHandoff;
|
|
192
192
|
break;
|
|
193
|
+
case "context_profile":
|
|
194
|
+
delete cfg.contextProfile;
|
|
195
|
+
break;
|
|
193
196
|
default:
|
|
194
197
|
requireSpec(key); // throws the known-keys message for anything unknown
|
|
195
198
|
}
|
|
@@ -177,6 +177,8 @@ export function buildShowView(local, remote, reason) {
|
|
|
177
177
|
launchBits.push(`model ${local.model}`);
|
|
178
178
|
if (local.effort)
|
|
179
179
|
launchBits.push(`effort ${local.effort}`);
|
|
180
|
+
if (local.context_profile)
|
|
181
|
+
launchBits.push(`context ${local.context_profile}`);
|
|
180
182
|
L.push(fmt.label("launch: ", launchBits.join(" · ")));
|
|
181
183
|
L.push(fmt.label("conversation: ", local.conversation_id));
|
|
182
184
|
const goalMode = resolveGoalMode(local, remote);
|
package/dist/commands/agent.d.ts
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* line, or the proxy's stdin under RC). It is never logged, printed, or passed as
|
|
26
26
|
* an argv value.
|
|
27
27
|
*/
|
|
28
|
-
import { type LaunchShape, type SealedContractEnvelope } from "../lib/agent.js";
|
|
28
|
+
import { type ContextProfile, type LaunchShape, type SealedContractEnvelope } from "../lib/agent.js";
|
|
29
29
|
export interface AgentOptions {
|
|
30
30
|
goal?: string;
|
|
31
31
|
requirement?: string[];
|
|
@@ -58,6 +58,12 @@ export interface AgentOptions {
|
|
|
58
58
|
* gateway to skip folding for this session only.
|
|
59
59
|
*/
|
|
60
60
|
context?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* How the gateway folds this session's context (`--context-profile`): `off`, `balanced` or
|
|
63
|
+
* `aggressive`. Undefined ⇒ MNEMOM_AGENT_CONTEXT_PROFILE (the `context_profile` setting), else
|
|
64
|
+
* `balanced`. `--no-context` is `off`; combined with balanced|aggressive the launch refuses.
|
|
65
|
+
*/
|
|
66
|
+
contextProfile?: ContextProfile;
|
|
61
67
|
/** context-hint seed default: commander `--no-context-hint` sets this false. */
|
|
62
68
|
contextHint?: boolean;
|
|
63
69
|
/** Attach the Slack channel server (`--slack`; lib/slack-launch.ts). */
|
|
@@ -108,6 +114,8 @@ export declare function recordSessionLaunch(ctx: {
|
|
|
108
114
|
goalMode: string | null;
|
|
109
115
|
/** The Claude Code session id (pickCcSessionId); null/absent when Claude Code picks it. */
|
|
110
116
|
ccSessionId?: string | null;
|
|
117
|
+
/** The resolved context profile this launch runs under. */
|
|
118
|
+
contextProfile: ContextProfile;
|
|
111
119
|
}): Promise<void>;
|
|
112
120
|
/**
|
|
113
121
|
* Resolve the Anthropic key with NO 1Password dependency (built for external
|
|
@@ -233,4 +241,8 @@ export declare function printPlan(ctx: {
|
|
|
233
241
|
goalMode: string | null;
|
|
234
242
|
yolo: boolean;
|
|
235
243
|
cli: ResolvedCli;
|
|
244
|
+
/** The resolved context profile (`resolveContextProfile`). */
|
|
245
|
+
contextProfile: ContextProfile;
|
|
246
|
+
/** The decoded `x-mnemom-session-config` overlay (`buildSessionConfig`); empty ⇒ no header. */
|
|
247
|
+
sessionConfig: Record<string, string>;
|
|
236
248
|
}): void;
|
package/dist/commands/agent.js
CHANGED
|
@@ -40,12 +40,24 @@ import { fmt } from "../lib/format.js";
|
|
|
40
40
|
import { askInput, askYesNo, isInteractive } from "../lib/prompt.js";
|
|
41
41
|
import { goalAlignmentLine, resolveSessionSetup } from "./agent-session.js";
|
|
42
42
|
import { AgentIdentityMismatchError, defaultAgentSlug, ensureGovernedAgent, ensureSessionAndBalance, isQuietPreambleExit, resolveDefaultSlug, resolvePreflightedAccount, } from "./agent-setup.js";
|
|
43
|
-
import { assembleContract, buildGoalModeHeader, buildCustomHeaders, buildHandoffMcpConfigJson,
|
|
43
|
+
import { assembleContract, buildGoalModeHeader, buildCustomHeaders, buildHandoffMcpConfigJson, buildSessionConfig, buildSessionConfigB64, resolveContextHandoff, resolveContextProfile, resolveHandoffGatewayHost, HANDOFF_RC_KEY_ENV_NAME, isProdGateway, launchPreferenceExplicit, launchPreflighted, remoteControlAllowed, resolveAnthropicDoor, resolveConversationId, resolveGatewayHost, resolveLaunchShape, resolveRemoteControlShape, passthroughName, sanitizeAgentName, sessionDisplayName, SESSION_NAME_ENV, SESSION_CONFIG_HEADER, } from "../lib/agent.js";
|
|
44
44
|
import { LAUNCHER_STORE_PATH, mergeStore } from "../lib/launcher-store.js";
|
|
45
45
|
import { slackChannelArgs } from "../lib/slack-launch.js";
|
|
46
46
|
/** The CLI's own provider-key store (zero 1Password): ~/.mnemom/launcher.json, 0600. */
|
|
47
47
|
const CODE_STORE = LAUNCHER_STORE_PATH;
|
|
48
48
|
const say = (line = "") => console.error(line);
|
|
49
|
+
/**
|
|
50
|
+
* The one-line launch note for a non-default context profile (`balanced`, the default, is
|
|
51
|
+
* silent — nothing is sent). `off` keeps the wording `--no-context` has always printed.
|
|
52
|
+
*/
|
|
53
|
+
function sayContextProfile(profile) {
|
|
54
|
+
if (profile === "off") {
|
|
55
|
+
say("mnemom agent: context folding OFF for this session (--no-context or context = false).");
|
|
56
|
+
}
|
|
57
|
+
else if (profile === "aggressive") {
|
|
58
|
+
say("mnemom agent: context profile aggressive — the gateway folds earlier and keeps a tighter working set.");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
49
61
|
/** Entry point for `mnemom agent [scenario]`. Throws on fatal misconfig. */
|
|
50
62
|
export async function agentCommand(scenarioArg, options = {}) {
|
|
51
63
|
// ── session name + goal ── (a bare `mnemom agent` in a terminal asks for both,
|
|
@@ -99,12 +111,18 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
99
111
|
const cellNote = isProdGateway(env) ? "us-2/prod" : `custom (${gatewayHost})`;
|
|
100
112
|
const yolo = options.yolo !== false; // default ON
|
|
101
113
|
const contextHint = options.contextHint !== false; // default ON
|
|
102
|
-
// Context
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
|
|
114
|
+
// Context profile: `balanced` (the gateway's own defaults) unless --context-profile /
|
|
115
|
+
// MNEMOM_AGENT_CONTEXT_PROFILE (the `context_profile` setting) says otherwise; --no-context
|
|
116
|
+
// (or `context = false` in launcher.toml → MNEMOM_AGENT_CONTEXT) is `off`. Anything but
|
|
117
|
+
// balanced rides in the per-session config header; `undefined` sessionConfigB64 ⇒ nothing sent.
|
|
118
|
+
const contextProfile = resolveContextProfile({
|
|
119
|
+
contextProfile: options.contextProfile,
|
|
120
|
+
context: options.context,
|
|
121
|
+
env,
|
|
122
|
+
});
|
|
106
123
|
const contextHandoff = resolveContextHandoff({ contextHandoff: options.contextHandoff, env });
|
|
107
|
-
const
|
|
124
|
+
const sessionConfig = buildSessionConfig({ profile: contextProfile, contextHandoff });
|
|
125
|
+
const sessionConfigB64 = buildSessionConfigB64({ profile: contextProfile, contextHandoff });
|
|
108
126
|
// ── which CLI executable (name or full path) ── resolve GRACEFULLY: if it's not
|
|
109
127
|
// found we don't crash with a technical error — we ask for the path at launch.
|
|
110
128
|
let cli;
|
|
@@ -143,13 +161,13 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
143
161
|
goalMode,
|
|
144
162
|
yolo,
|
|
145
163
|
cli: planCli,
|
|
164
|
+
contextProfile,
|
|
165
|
+
sessionConfig,
|
|
146
166
|
});
|
|
147
167
|
if (previewShape === "remote-control") {
|
|
148
168
|
say("mnemom agent: the plan shows Remote Control because this machine can run it; the real launch uses it only if your account is entitled (internal Mnemom accounts), else terminal-only.");
|
|
149
169
|
}
|
|
150
|
-
|
|
151
|
-
say("mnemom agent: context folding OFF for this session (--no-context or context = false).");
|
|
152
|
-
}
|
|
170
|
+
sayContextProfile(contextProfile);
|
|
153
171
|
if (contextHandoff) {
|
|
154
172
|
say("mnemom agent: context handoff ON (experimental) — the `handoff` MCP tool would be registered.");
|
|
155
173
|
}
|
|
@@ -269,12 +287,11 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
269
287
|
envelope,
|
|
270
288
|
goalMode,
|
|
271
289
|
ccSessionId: cc.id,
|
|
290
|
+
contextProfile,
|
|
272
291
|
});
|
|
273
292
|
logLaunch({ shape, door, agent, conversationId, cellNote, envelope, goalMode, cli });
|
|
274
293
|
say(goalAlignmentLine(goalAlignmentMode(envelope, goalMode)));
|
|
275
|
-
|
|
276
|
-
say("mnemom agent: context folding OFF for this session (--no-context or context = false).");
|
|
277
|
-
}
|
|
294
|
+
sayContextProfile(contextProfile);
|
|
278
295
|
// ── handoff MCP tool (MNE-7564) ── OFF by default: absent/false ⇒ handoffMcpConfigPath stays
|
|
279
296
|
// undefined and buildCliArgs/spawnRcCli are byte-identical to a build without this flag. ON:
|
|
280
297
|
// the config is written to a 0600 temp file and referenced by PATH only — never inlined into
|
|
@@ -377,6 +394,7 @@ export async function recordSessionLaunch(ctx) {
|
|
|
377
394
|
cli: ctx.cli.name,
|
|
378
395
|
contract: toLedgerContract(ctx.envelope?.contract ?? null),
|
|
379
396
|
goal_mode: goalAlignmentMode(ctx.envelope, ctx.goalMode),
|
|
397
|
+
context_profile: ctx.contextProfile,
|
|
380
398
|
cc_session_id: ctx.ccSessionId ?? null,
|
|
381
399
|
}, {
|
|
382
400
|
dir: MNEMOM_DIR,
|
|
@@ -1085,6 +1103,13 @@ export function printPlan(ctx) {
|
|
|
1085
1103
|
L.push(` agent: ${ctx.agent}`);
|
|
1086
1104
|
L.push(` conversation: ${ctx.conversationId}`);
|
|
1087
1105
|
L.push(` yolo: ${ctx.yolo ? "on (--dangerously-skip-permissions)" : "off"}`);
|
|
1106
|
+
L.push(` context: ${ctx.contextProfile}`);
|
|
1107
|
+
const overlay = Object.entries(ctx.sessionConfig);
|
|
1108
|
+
if (overlay.length > 0) {
|
|
1109
|
+
L.push(` session config (${SESSION_CONFIG_HEADER}):`);
|
|
1110
|
+
for (const [k, v] of overlay)
|
|
1111
|
+
L.push(` ${k}=${v}`);
|
|
1112
|
+
}
|
|
1088
1113
|
if (ctx.envelope) {
|
|
1089
1114
|
L.push(` contract: ${ctx.envelope.canonical}`);
|
|
1090
1115
|
L.push(` contract sha: ${ctx.envelope.sha}`);
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { program, Option } from "commander";
|
|
2
|
+
import { program, InvalidArgumentError, Option } from "commander";
|
|
3
3
|
import { isEntrypoint } from "./lib/entrypoint.js";
|
|
4
4
|
import { CLI_VERSION } from "./version.js";
|
|
5
5
|
import { statusCommand } from "./commands/status.js";
|
|
@@ -29,7 +29,7 @@ import { agentDoctorCommand } from "./commands/agent-doctor.js";
|
|
|
29
29
|
import { agentSetupCommand } from "./commands/agent-setup.js";
|
|
30
30
|
import { agentInviteCommand, runAgentInvocation } from "./commands/agent-invite.js";
|
|
31
31
|
import { applyConfigToEnv, loadLauncherConfig } from "./lib/launcher-config.js";
|
|
32
|
-
import { resolveImplicit } from "./lib/agent.js";
|
|
32
|
+
import { parseContextProfile, resolveImplicit } from "./lib/agent.js";
|
|
33
33
|
import { wrapCommand } from "./commands/wrap.js";
|
|
34
34
|
import { onboardCommand } from "./commands/onboard.js";
|
|
35
35
|
import { skillsListCommand, skillsDescribeCommand } from "./commands/skills.js";
|
|
@@ -143,6 +143,16 @@ const agentCmd = program
|
|
|
143
143
|
.option("--slack", "Attach the Slack channel: messages addressed to this session arrive in it, and it can reply (see `mnemom agent slack`)")
|
|
144
144
|
.option("--context", "Force context folding ON for this launch even when launcher.toml (context = false) or MNEMOM_AGENT_CONTEXT turns it off")
|
|
145
145
|
.option("--no-context", "Disable context folding for this launch (folding is on by default; tells the gateway to skip it for this session)")
|
|
146
|
+
.addOption(new Option("--context-profile <profile>", 'How the gateway folds this session\'s context. "balanced" (default) keeps more of the earlier work verbatim and folds later — for long sessions that revisit earlier files and decisions. "aggressive" folds earlier and keeps a tighter working set — for cost-sensitive sessions and short, focused tasks. "off" leaves compaction to the coding agent. Also: config key context_profile, env MNEMOM_AGENT_CONTEXT_PROFILE.').argParser((v) => {
|
|
147
|
+
// One validator for all three surfaces (flag, MNEMOM_AGENT_CONTEXT_PROFILE,
|
|
148
|
+
// launcher.toml context_profile): trimmed, case-insensitive, same error wording.
|
|
149
|
+
try {
|
|
150
|
+
return parseContextProfile(v, "--context-profile");
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
throw new InvalidArgumentError(e.message);
|
|
154
|
+
}
|
|
155
|
+
}))
|
|
146
156
|
.option("--context-handoff", "EXPERIMENTAL, off by default: register the `handoff(note)` MCP tool and tell the gateway to turn on the context-handoff capability for this session")
|
|
147
157
|
.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")
|
|
148
158
|
.option("--dry-run", "Print the resolved launch plan and exit — no key read, no spawn")
|
|
@@ -234,6 +244,7 @@ const agentCmd = program
|
|
|
234
244
|
contextHint: opts.contextHint,
|
|
235
245
|
slack: opts.slack ?? cfg.slackListen,
|
|
236
246
|
context: opts.context,
|
|
247
|
+
contextProfile: opts.contextProfile,
|
|
237
248
|
contextHandoff: opts.contextHandoff,
|
|
238
249
|
setup: opts.setup,
|
|
239
250
|
keySource: cfg.keySource,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* `sessions.retention_days` (default 30) are dropped when the next one is
|
|
15
15
|
* recorded. No count cap — a long retention keeps everything in the window.
|
|
16
16
|
*/
|
|
17
|
-
import type { LaunchShape, SealedContract } from "./agent.js";
|
|
17
|
+
import type { ContextProfile, LaunchShape, SealedContract } from "./agent.js";
|
|
18
18
|
/** The ledger's file name under MNEMOM_DIR. */
|
|
19
19
|
export declare const LEDGER_FILE = "sessions.jsonl";
|
|
20
20
|
/** Days a launch stays in the ledger when `sessions.retention_days` is unset. */
|
|
@@ -75,6 +75,12 @@ export interface SessionRecord {
|
|
|
75
75
|
* records written before this field existed omit it (still `v: 1`).
|
|
76
76
|
*/
|
|
77
77
|
cc_session_id?: string | null;
|
|
78
|
+
/**
|
|
79
|
+
* The context profile the launch ran under (`--context-profile` / `context_profile` /
|
|
80
|
+
* `MNEMOM_AGENT_CONTEXT_PROFILE`, or `balanced` by default). Optional: records written
|
|
81
|
+
* before profiles existed omit it (still `v: 1`).
|
|
82
|
+
*/
|
|
83
|
+
context_profile?: ContextProfile;
|
|
78
84
|
}
|
|
79
85
|
/** Goal-alignment mode as recorded at launch. */
|
|
80
86
|
export type GoalMode = "off" | "on" | "implicit";
|
package/dist/lib/agent.d.ts
CHANGED
|
@@ -161,6 +161,21 @@ export declare const GOAL_MODE_HEADER = "x-mnemom-goal-mode";
|
|
|
161
161
|
export declare const SESSION_CONFIG_HEADER = "x-mnemom-session-config";
|
|
162
162
|
/** The gateway env key that gates context folding (allow-listed for per-session overlay). */
|
|
163
163
|
export declare const CONTEXT_RUNTIME_KEY = "CONTEXT_RUNTIME_ENABLED";
|
|
164
|
+
/** Gateway fold-tuning keys the `aggressive` context profile sets (all allow-listed per session). */
|
|
165
|
+
export declare const CONTEXT_TAIL_BUDGET_KEY = "CONTEXT_RUNTIME_TAIL_BUDGET_TOKENS";
|
|
166
|
+
export declare const CONTEXT_READ_CACHE_KEY = "CONTEXT_RUNTIME_READ_CACHE_TOKENS";
|
|
167
|
+
export declare const CONTEXT_HEAD_RECLAIM_KEY = "CONTEXT_RUNTIME_HEAD_BREAKPOINT_RECLAIM";
|
|
168
|
+
/**
|
|
169
|
+
* The named presets for how the gateway folds a session's context (`--context-profile`,
|
|
170
|
+
* launcher.toml `context_profile`, `MNEMOM_AGENT_CONTEXT_PROFILE`):
|
|
171
|
+
* - `off` → `CONTEXT_RUNTIME_ENABLED: "false"` — compaction is left to the coding agent
|
|
172
|
+
* (the same bytes `--no-context` has always sent).
|
|
173
|
+
* - `balanced` → NO fold keys: the gateway's own defaults stand. The default.
|
|
174
|
+
* - `aggressive` → a smaller tail budget + read cache and head-breakpoint reclaim on: folds
|
|
175
|
+
* earlier and keeps a tighter working set.
|
|
176
|
+
*/
|
|
177
|
+
export declare const CONTEXT_PROFILES: readonly ["off", "balanced", "aggressive"];
|
|
178
|
+
export type ContextProfile = (typeof CONTEXT_PROFILES)[number];
|
|
164
179
|
/**
|
|
165
180
|
* The gateway env key that gates the whole context-handoff capability (level lines +
|
|
166
181
|
* `/mnemom/context/handoff` route + the digest note section) for a single session
|
|
@@ -272,12 +287,49 @@ export declare function resolveContextFold(opts: {
|
|
|
272
287
|
context?: boolean;
|
|
273
288
|
env?: NodeJS.ProcessEnv;
|
|
274
289
|
}): boolean;
|
|
290
|
+
/**
|
|
291
|
+
* Validate a context-profile word — the ONE validator behind all three surfaces (the
|
|
292
|
+
* `--context-profile` flag, launcher.toml `context_profile`, the `MNEMOM_AGENT_CONTEXT_PROFILE`
|
|
293
|
+
* env var); trimmed, case-insensitive. `source` names the surface in the error so a customer
|
|
294
|
+
* knows which setting to fix.
|
|
295
|
+
*/
|
|
296
|
+
export declare function parseContextProfile(raw: string, source: string): ContextProfile;
|
|
297
|
+
/**
|
|
298
|
+
* Which context profile this launch runs under. Precedence, highest wins:
|
|
299
|
+
* 1. `--no-context` — always `off`. Combined with an explicit `--context-profile
|
|
300
|
+
* balanced|aggressive` it is a CONFLICT and the launch refuses (never a silent override);
|
|
301
|
+
* `--context-profile off` agrees with it.
|
|
302
|
+
* 2. an explicit `--context-profile <profile>` flag (`--context` alongside it is fine — the
|
|
303
|
+
* profile wins);
|
|
304
|
+
* 3. `MNEMOM_AGENT_CONTEXT_PROFILE` (which `applyConfigToEnv` sets from the `context_profile`
|
|
305
|
+
* config key) — an invalid value is an error, never a silent default;
|
|
306
|
+
* 4. the folding boolean when NO profile is set anywhere: `context = false` in launcher.toml
|
|
307
|
+
* (→ `MNEMOM_AGENT_CONTEXT`, see `resolveContextFold`) means `off`;
|
|
308
|
+
* 5. `balanced` (the default).
|
|
309
|
+
*/
|
|
310
|
+
export declare function resolveContextProfile(opts: {
|
|
311
|
+
contextProfile?: ContextProfile;
|
|
312
|
+
context?: boolean;
|
|
313
|
+
env?: NodeJS.ProcessEnv;
|
|
314
|
+
}): ContextProfile;
|
|
315
|
+
/**
|
|
316
|
+
* The decoded per-session env overlay for this launch — the `config` object of the
|
|
317
|
+
* `x-mnemom-session-config` payload, in wire order: the profile's keys first
|
|
318
|
+
* (`CONTEXT_RUNTIME_ENABLED` for `off`; the three fold-tuning keys for `aggressive`; nothing
|
|
319
|
+
* for `balanced`), then `CONTEXT_HANDOFF_ENABLED: "true"` when context handoff is ON. Empty
|
|
320
|
+
* for `balanced` with handoff off — the common case, where no header is sent at all.
|
|
321
|
+
* Exported so `--dry-run` can list exactly what the header carries.
|
|
322
|
+
*/
|
|
323
|
+
export declare function buildSessionConfig(opts: {
|
|
324
|
+
profile: ContextProfile;
|
|
325
|
+
contextHandoff: boolean;
|
|
326
|
+
}): Record<string, string>;
|
|
275
327
|
/**
|
|
276
328
|
* The `x-mnemom-session-config` value for this launch, or `undefined` when there is
|
|
277
|
-
* nothing to override (the gateway's own defaults stand —
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
329
|
+
* nothing to override (the gateway's own defaults stand — `balanced` with handoff off,
|
|
330
|
+
* byte-identical to a launch before profiles existed). Two independent overlays:
|
|
331
|
+
* - the context profile (`buildSessionConfig`): `off` → `CONTEXT_RUNTIME_ENABLED: "false"`,
|
|
332
|
+
* `aggressive` → the three fold-tuning keys, `balanced` → nothing;
|
|
281
333
|
* - `contextHandoff: true` → `CONTEXT_HANDOFF_ENABLED: "true"` (the `context_handoff`
|
|
282
334
|
* setting) — the SAME header the `handoff` MCP tool's own call to
|
|
283
335
|
* `/mnemom/context/handoff` must also send, since the gateway resolves the flag
|
|
@@ -285,7 +337,7 @@ export declare function resolveContextFold(opts: {
|
|
|
285
337
|
* base64url so it survives the gateway's inbound strip.
|
|
286
338
|
*/
|
|
287
339
|
export declare function buildSessionConfigB64(opts: {
|
|
288
|
-
|
|
340
|
+
profile: ContextProfile;
|
|
289
341
|
contextHandoff: boolean;
|
|
290
342
|
}): string | undefined;
|
|
291
343
|
/**
|
package/dist/lib/agent.js
CHANGED
|
@@ -226,6 +226,30 @@ export const GOAL_MODE_HEADER = "x-mnemom-goal-mode";
|
|
|
226
226
|
export const SESSION_CONFIG_HEADER = "x-mnemom-session-config";
|
|
227
227
|
/** The gateway env key that gates context folding (allow-listed for per-session overlay). */
|
|
228
228
|
export const CONTEXT_RUNTIME_KEY = "CONTEXT_RUNTIME_ENABLED";
|
|
229
|
+
/** Gateway fold-tuning keys the `aggressive` context profile sets (all allow-listed per session). */
|
|
230
|
+
export const CONTEXT_TAIL_BUDGET_KEY = "CONTEXT_RUNTIME_TAIL_BUDGET_TOKENS";
|
|
231
|
+
export const CONTEXT_READ_CACHE_KEY = "CONTEXT_RUNTIME_READ_CACHE_TOKENS";
|
|
232
|
+
export const CONTEXT_HEAD_RECLAIM_KEY = "CONTEXT_RUNTIME_HEAD_BREAKPOINT_RECLAIM";
|
|
233
|
+
/**
|
|
234
|
+
* The named presets for how the gateway folds a session's context (`--context-profile`,
|
|
235
|
+
* launcher.toml `context_profile`, `MNEMOM_AGENT_CONTEXT_PROFILE`):
|
|
236
|
+
* - `off` → `CONTEXT_RUNTIME_ENABLED: "false"` — compaction is left to the coding agent
|
|
237
|
+
* (the same bytes `--no-context` has always sent).
|
|
238
|
+
* - `balanced` → NO fold keys: the gateway's own defaults stand. The default.
|
|
239
|
+
* - `aggressive` → a smaller tail budget + read cache and head-breakpoint reclaim on: folds
|
|
240
|
+
* earlier and keeps a tighter working set.
|
|
241
|
+
*/
|
|
242
|
+
export const CONTEXT_PROFILES = ["off", "balanced", "aggressive"];
|
|
243
|
+
/** The per-session env overlay each profile expands to (key order is the wire order). */
|
|
244
|
+
const CONTEXT_PROFILE_CONFIG = {
|
|
245
|
+
off: [[CONTEXT_RUNTIME_KEY, "false"]],
|
|
246
|
+
balanced: [],
|
|
247
|
+
aggressive: [
|
|
248
|
+
[CONTEXT_TAIL_BUDGET_KEY, "60000"],
|
|
249
|
+
[CONTEXT_READ_CACHE_KEY, "8000"],
|
|
250
|
+
[CONTEXT_HEAD_RECLAIM_KEY, "true"],
|
|
251
|
+
],
|
|
252
|
+
};
|
|
229
253
|
/**
|
|
230
254
|
* The gateway env key that gates the whole context-handoff capability (level lines +
|
|
231
255
|
* `/mnemom/context/handoff` route + the digest note section) for a single session
|
|
@@ -432,12 +456,69 @@ export function resolveContextFold(opts) {
|
|
|
432
456
|
return true;
|
|
433
457
|
return !(raw === "0" || raw === "false" || raw === "off" || raw === "no");
|
|
434
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* Validate a context-profile word — the ONE validator behind all three surfaces (the
|
|
461
|
+
* `--context-profile` flag, launcher.toml `context_profile`, the `MNEMOM_AGENT_CONTEXT_PROFILE`
|
|
462
|
+
* env var); trimmed, case-insensitive. `source` names the surface in the error so a customer
|
|
463
|
+
* knows which setting to fix.
|
|
464
|
+
*/
|
|
465
|
+
export function parseContextProfile(raw, source) {
|
|
466
|
+
const v = raw.trim().toLowerCase();
|
|
467
|
+
if (CONTEXT_PROFILES.includes(v))
|
|
468
|
+
return v;
|
|
469
|
+
throw new Error(`${source} must be one of ${CONTEXT_PROFILES.join(", ")} (got '${raw}').`);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Which context profile this launch runs under. Precedence, highest wins:
|
|
473
|
+
* 1. `--no-context` — always `off`. Combined with an explicit `--context-profile
|
|
474
|
+
* balanced|aggressive` it is a CONFLICT and the launch refuses (never a silent override);
|
|
475
|
+
* `--context-profile off` agrees with it.
|
|
476
|
+
* 2. an explicit `--context-profile <profile>` flag (`--context` alongside it is fine — the
|
|
477
|
+
* profile wins);
|
|
478
|
+
* 3. `MNEMOM_AGENT_CONTEXT_PROFILE` (which `applyConfigToEnv` sets from the `context_profile`
|
|
479
|
+
* config key) — an invalid value is an error, never a silent default;
|
|
480
|
+
* 4. the folding boolean when NO profile is set anywhere: `context = false` in launcher.toml
|
|
481
|
+
* (→ `MNEMOM_AGENT_CONTEXT`, see `resolveContextFold`) means `off`;
|
|
482
|
+
* 5. `balanced` (the default).
|
|
483
|
+
*/
|
|
484
|
+
export function resolveContextProfile(opts) {
|
|
485
|
+
if (opts.context === false) {
|
|
486
|
+
if (opts.contextProfile !== undefined && opts.contextProfile !== "off") {
|
|
487
|
+
throw new Error(`mnemom agent: --no-context and --context-profile ${opts.contextProfile} disagree — drop one (--no-context is the same as --context-profile off).`);
|
|
488
|
+
}
|
|
489
|
+
return "off";
|
|
490
|
+
}
|
|
491
|
+
if (opts.contextProfile !== undefined)
|
|
492
|
+
return opts.contextProfile;
|
|
493
|
+
const raw = (opts.env?.MNEMOM_AGENT_CONTEXT_PROFILE ?? "").trim();
|
|
494
|
+
if (raw !== "")
|
|
495
|
+
return parseContextProfile(raw, "MNEMOM_AGENT_CONTEXT_PROFILE");
|
|
496
|
+
if (!resolveContextFold({ context: opts.context, env: opts.env }))
|
|
497
|
+
return "off";
|
|
498
|
+
return "balanced";
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* The decoded per-session env overlay for this launch — the `config` object of the
|
|
502
|
+
* `x-mnemom-session-config` payload, in wire order: the profile's keys first
|
|
503
|
+
* (`CONTEXT_RUNTIME_ENABLED` for `off`; the three fold-tuning keys for `aggressive`; nothing
|
|
504
|
+
* for `balanced`), then `CONTEXT_HANDOFF_ENABLED: "true"` when context handoff is ON. Empty
|
|
505
|
+
* for `balanced` with handoff off — the common case, where no header is sent at all.
|
|
506
|
+
* Exported so `--dry-run` can list exactly what the header carries.
|
|
507
|
+
*/
|
|
508
|
+
export function buildSessionConfig(opts) {
|
|
509
|
+
const config = {};
|
|
510
|
+
for (const [k, v] of CONTEXT_PROFILE_CONFIG[opts.profile])
|
|
511
|
+
config[k] = v;
|
|
512
|
+
if (opts.contextHandoff)
|
|
513
|
+
config[CONTEXT_HANDOFF_KEY] = "true";
|
|
514
|
+
return config;
|
|
515
|
+
}
|
|
435
516
|
/**
|
|
436
517
|
* The `x-mnemom-session-config` value for this launch, or `undefined` when there is
|
|
437
|
-
* nothing to override (the gateway's own defaults stand —
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
518
|
+
* nothing to override (the gateway's own defaults stand — `balanced` with handoff off,
|
|
519
|
+
* byte-identical to a launch before profiles existed). Two independent overlays:
|
|
520
|
+
* - the context profile (`buildSessionConfig`): `off` → `CONTEXT_RUNTIME_ENABLED: "false"`,
|
|
521
|
+
* `aggressive` → the three fold-tuning keys, `balanced` → nothing;
|
|
441
522
|
* - `contextHandoff: true` → `CONTEXT_HANDOFF_ENABLED: "true"` (the `context_handoff`
|
|
442
523
|
* setting) — the SAME header the `handoff` MCP tool's own call to
|
|
443
524
|
* `/mnemom/context/handoff` must also send, since the gateway resolves the flag
|
|
@@ -445,11 +526,7 @@ export function resolveContextFold(opts) {
|
|
|
445
526
|
* base64url so it survives the gateway's inbound strip.
|
|
446
527
|
*/
|
|
447
528
|
export function buildSessionConfigB64(opts) {
|
|
448
|
-
const config =
|
|
449
|
-
if (!opts.fold)
|
|
450
|
-
config[CONTEXT_RUNTIME_KEY] = "false";
|
|
451
|
-
if (opts.contextHandoff)
|
|
452
|
-
config[CONTEXT_HANDOFF_KEY] = "true";
|
|
529
|
+
const config = buildSessionConfig(opts);
|
|
453
530
|
if (Object.keys(config).length === 0)
|
|
454
531
|
return undefined;
|
|
455
532
|
const payload = JSON.stringify({ v: 1, config });
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* resolvers in lib/code.ts keep the built-in default as the final fallback);
|
|
16
16
|
* the caller folds the plain-value settings under the CLI flags.
|
|
17
17
|
*/
|
|
18
|
-
import { type GoalModeDefault, type LaunchShape } from "./agent.js";
|
|
18
|
+
import { type ContextProfile, type GoalModeDefault, type LaunchShape } from "./agent.js";
|
|
19
19
|
/** The human-editable settings file. Secret key lives elsewhere (launcher.json). */
|
|
20
20
|
export declare const LAUNCHER_CONFIG_PATH: string;
|
|
21
21
|
/** Where the CLI resolves the Anthropic key from; advisory ordering/restriction. */
|
|
@@ -45,6 +45,12 @@ export interface LauncherConfig {
|
|
|
45
45
|
* every launch. Off means byte-identical to a launch with no MNE-7564 change at all.
|
|
46
46
|
*/
|
|
47
47
|
contextHandoff?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* How the gateway folds each session's context (`balanced` when unset): `off` leaves
|
|
50
|
+
* compaction to the coding agent, `balanced` keeps more earlier work verbatim, `aggressive`
|
|
51
|
+
* folds earlier with a tighter working set. `--context-profile` overrides it per launch.
|
|
52
|
+
*/
|
|
53
|
+
contextProfile?: ContextProfile;
|
|
48
54
|
guardrails?: {
|
|
49
55
|
maxTurns?: number;
|
|
50
56
|
budgetUsd?: number;
|
|
@@ -19,7 +19,7 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "
|
|
|
19
19
|
import { dirname, join } from "node:path";
|
|
20
20
|
import { parse as parseToml, stringify as stringifyToml, TomlError } from "smol-toml";
|
|
21
21
|
import { MNEMOM_DIR } from "./config.js";
|
|
22
|
-
import { isPositiveInt, isPositiveNumber, } from "./agent.js";
|
|
22
|
+
import { CONTEXT_PROFILES, isPositiveInt, isPositiveNumber, parseContextProfile, } from "./agent.js";
|
|
23
23
|
/** The human-editable settings file. Secret key lives elsewhere (launcher.json). */
|
|
24
24
|
export const LAUNCHER_CONFIG_PATH = join(MNEMOM_DIR, "launcher.toml");
|
|
25
25
|
const LAUNCH_SHAPES = ["terminal", "remote-control", "server"];
|
|
@@ -188,6 +188,14 @@ export const CONFIG_KEYS = [
|
|
|
188
188
|
c.contextHandoff = coerceBool("context_handoff", v);
|
|
189
189
|
},
|
|
190
190
|
},
|
|
191
|
+
{
|
|
192
|
+
key: "context_profile",
|
|
193
|
+
describe: `How the gateway folds each session's context (${CONTEXT_PROFILES.join("|")}; balanced by default)`,
|
|
194
|
+
get: (c) => c.contextProfile,
|
|
195
|
+
set: (c, v) => {
|
|
196
|
+
c.contextProfile = parseContextProfile(v, "context_profile");
|
|
197
|
+
},
|
|
198
|
+
},
|
|
191
199
|
{
|
|
192
200
|
key: "guardrails.max_turns",
|
|
193
201
|
describe: "Contract guardrail: turn ceiling (positive integer)",
|
|
@@ -266,6 +274,8 @@ export function parseLauncherConfig(text) {
|
|
|
266
274
|
cfg.slackChannel = coerceNonEmpty("slack_channel", str(raw.slack_channel) ?? "");
|
|
267
275
|
if (raw.context_handoff !== undefined)
|
|
268
276
|
cfg.contextHandoff = coerceBool("context_handoff", str(raw.context_handoff) ?? "");
|
|
277
|
+
if (raw.context_profile !== undefined)
|
|
278
|
+
cfg.contextProfile = parseContextProfile(str(raw.context_profile) ?? "", "context_profile");
|
|
269
279
|
const g = raw.guardrails;
|
|
270
280
|
if (g !== undefined) {
|
|
271
281
|
if (typeof g !== "object" || g === null || Array.isArray(g)) {
|
|
@@ -334,6 +344,8 @@ export function serializeLauncherConfig(cfg) {
|
|
|
334
344
|
doc.slack_allow = cfg.slackAllow;
|
|
335
345
|
if (cfg.contextHandoff !== undefined)
|
|
336
346
|
doc.context_handoff = cfg.contextHandoff;
|
|
347
|
+
if (cfg.contextProfile !== undefined)
|
|
348
|
+
doc.context_profile = cfg.contextProfile;
|
|
337
349
|
const g = cfg.guardrails;
|
|
338
350
|
if (g && (g.maxTurns !== undefined || g.budgetUsd !== undefined || g.stallTurns !== undefined)) {
|
|
339
351
|
const gr = {};
|
|
@@ -386,4 +398,7 @@ export function applyConfigToEnv(cfg, env = process.env) {
|
|
|
386
398
|
if (cfg.contextHandoff !== undefined && !(env.MNEMOM_AGENT_CONTEXT_HANDOFF ?? "").trim()) {
|
|
387
399
|
env.MNEMOM_AGENT_CONTEXT_HANDOFF = cfg.contextHandoff ? "1" : "0";
|
|
388
400
|
}
|
|
401
|
+
if (cfg.contextProfile !== undefined && !(env.MNEMOM_AGENT_CONTEXT_PROFILE ?? "").trim()) {
|
|
402
|
+
env.MNEMOM_AGENT_CONTEXT_PROFILE = cfg.contextProfile;
|
|
403
|
+
}
|
|
389
404
|
}
|