@mnemom/mnemom 0.17.2-next.2 → 0.17.2-next.4

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
@@ -71,6 +71,39 @@ Make one mode the standing default with `mnemom agent config set goal_mode impli
71
71
  (or `off`); `--goal` still seals for that launch and `--no-implicit` turns it off for one
72
72
  launch. A sealed contract needs a per-launch statement, so it cannot be the default.
73
73
 
74
+ #### Orchestrated launches: `MNEMOM_AGENT_PREFLIGHTED=1`
75
+
76
+ An orchestrator (the ADW fleet) that already checked sign-in and the µ balance before
77
+ spawning many launches can set `MNEMOM_AGENT_PREFLIGHTED=1`. Each launch then skips its
78
+ own session, personal-org and µ-balance requests. The Mnemom Agent entitlement is still
79
+ fetched, because it gates access and Remote Control. Only the exact value `1` counts.
80
+ This is not for interactive use: a launch with no session or no µ will fail at the
81
+ gateway instead of printing the sign-in or top-up guidance.
82
+
83
+ An explicit launch shape (`--remote-control`, `--server`, `MNEMOM_AGENT_REMOTE_CONTROL`
84
+ or `launch` in `launcher.toml`) also skips the Remote Control capability probe
85
+ (`claude --help`), which only feeds the automatic upgrade to Remote Control.
86
+
87
+ #### Context handoff (experimental, off by default)
88
+
89
+ **Off by default.** `--context-handoff` (or the standing default `mnemom agent config set
90
+ context_handoff on`) registers one extra MCP tool, `handoff(note)`, that the agent can call
91
+ itself — at a natural stopping point, or as soon as it sees a `[mnemom context warning]` line
92
+ from the gateway — to hand a short note (goal, what's done, next action, open questions) to
93
+ its own successor context ahead of the gateway's next fold. It also tells the gateway to turn
94
+ the context-handoff capability on for that one session. Leaving the setting off (the default)
95
+ sends neither the tool nor the header, and the launch is unaffected.
96
+
97
+ Turn it on for a single launch with `--context-handoff`, or set the standing default:
98
+
99
+ ```bash
100
+ mnemom agent config set context_handoff on # persists in ~/.mnemom/launcher.toml
101
+ mnemom agent config set context_handoff off # back to the default
102
+ ```
103
+
104
+ This is experimental: it only ever ADDS the one tool (no gateway behavior changes for a
105
+ session that leaves the setting off, and no change to Claude Code's own `/compact`).
106
+
74
107
  ## How It Works
75
108
 
76
109
  ```
@@ -187,6 +187,9 @@ function deleteKey(cfg, key) {
187
187
  case "key_source":
188
188
  delete cfg.keySource;
189
189
  break;
190
+ case "context_handoff":
191
+ delete cfg.contextHandoff;
192
+ break;
190
193
  default:
191
194
  requireSpec(key); // throws the known-keys message for anything unknown
192
195
  }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `mnemom agent goal show|pin|reset` (implicit-goal CLI, cli-telemetry §1.4): read and steer the
3
+ * goal the gateway INFERRED for an implicit-mode session, through the gateway's control route
4
+ * (lib/goal-api.ts). The session is picked from the local ledger exactly like `mnemom agent show`
5
+ * (a name, newest match wins, or a unique conversation-id prefix; omitted = the newest session).
6
+ *
7
+ * mnemom agent goal show [ref] [--thread <id>|--all-threads] [--history] [--json]
8
+ * mnemom agent goal pin [ref] [--thread <id>] [--off] [--json]
9
+ * mnemom agent goal reset [ref] [--thread <id>] [--statement <s>] [--requirement <r>...] [--yes] [--json]
10
+ *
11
+ * Exit codes: 0 ok; 1 no such session / auth or agent not found / gateway or network failure /
12
+ * reset declined at the prompt; 2 a usage error (not implicit mode, a bad --thread, --requirement
13
+ * without --statement, reset without --yes off a TTY) or a 409 from the gateway (sealed contract,
14
+ * nothing to pin).
15
+ *
16
+ * Pure apart from the injected deps, so every arm is unit-tested; `liveGoalDeps` is the driver
17
+ * index.ts wires in. Key material is never printed.
18
+ */
19
+ import { type SessionRecord } from "../lib/agent-ledger.js";
20
+ import { type FetchLike, type GoalControlCreds, type GoalShowResponse } from "../lib/goal-api.js";
21
+ export interface GoalCommandDeps {
22
+ /** The ledger, newest first. */
23
+ readRecords: () => SessionRecord[];
24
+ fetch: FetchLike;
25
+ /** The launch's credential; rejects when no key resolves. */
26
+ resolveCreds: () => Promise<GoalControlCreds>;
27
+ isTTY: () => boolean;
28
+ confirm: (question: string) => Promise<boolean>;
29
+ out: (line: string) => void;
30
+ err: (line: string) => void;
31
+ }
32
+ /**
33
+ * The Anthropic key the launch would resolve (env, then ~/.mnemom/launcher.json), never prompted
34
+ * for. Lazy import: commands/agent.ts is the launcher and carries a heavy import graph.
35
+ */
36
+ export declare function resolveLaunchCreds(): Promise<GoalControlCreds>;
37
+ export declare const liveGoalDeps: GoalCommandDeps;
38
+ export interface GoalShowOptions {
39
+ thread?: string;
40
+ allThreads?: boolean;
41
+ history?: boolean;
42
+ json?: boolean;
43
+ }
44
+ export interface GoalPinOptions {
45
+ thread?: string;
46
+ off?: boolean;
47
+ json?: boolean;
48
+ }
49
+ export interface GoalResetOptions {
50
+ thread?: string;
51
+ statement?: string;
52
+ requirement?: string[];
53
+ yes?: boolean;
54
+ json?: boolean;
55
+ }
56
+ /** A failed command: the message and the exit code. */
57
+ declare class Exit extends Error {
58
+ readonly code: 1 | 2;
59
+ constructor(code: 1 | 2, message: string);
60
+ }
61
+ /** Validate `--thread` locally (same regex as the gateway); `all` only where `allowAll`. */
62
+ export declare function parseThreadOption(raw: string | undefined, allowAll: boolean): string;
63
+ /** The ledger record for `ref`, refusing anything that is not an implicit-mode session. */
64
+ export declare function pickImplicitSession(records: SessionRecord[], ref: string | undefined): SessionRecord;
65
+ /** A gateway / transport failure → the exit code and a message that never names the key. */
66
+ export declare function mapGoalError(e: unknown, host: string): Exit;
67
+ /** `rev 3 · pinned@3 · class refine · 2 pending`, or `rev 0 (no goal yet)`. */
68
+ export declare function revisionLine(body: Pick<GoalShowResponse, "intent" | "last_revision">): string;
69
+ /** The human view of one show body. */
70
+ export declare function buildGoalShowView(record: SessionRecord, body: GoalShowResponse, opts: {
71
+ history?: boolean;
72
+ }): string[];
73
+ /** `mnemom agent goal show [ref] [--thread <id>|--all-threads] [--history] [--json]` */
74
+ export declare function agentGoalShowCommand(ref: string | undefined, opts: GoalShowOptions, deps: GoalCommandDeps): Promise<number>;
75
+ /** `mnemom agent goal pin [ref] [--thread <id>] [--off] [--json]` */
76
+ export declare function agentGoalPinCommand(ref: string | undefined, opts: GoalPinOptions, deps: GoalCommandDeps): Promise<number>;
77
+ /** `mnemom agent goal reset [ref] [--thread <id>] [--statement <s>] [--requirement <r>...] [--yes] [--json]` */
78
+ export declare function agentGoalResetCommand(ref: string | undefined, opts: GoalResetOptions, deps: GoalCommandDeps): Promise<number>;
79
+ export {};
@@ -0,0 +1,276 @@
1
+ /**
2
+ * `mnemom agent goal show|pin|reset` (implicit-goal CLI, cli-telemetry §1.4): read and steer the
3
+ * goal the gateway INFERRED for an implicit-mode session, through the gateway's control route
4
+ * (lib/goal-api.ts). The session is picked from the local ledger exactly like `mnemom agent show`
5
+ * (a name, newest match wins, or a unique conversation-id prefix; omitted = the newest session).
6
+ *
7
+ * mnemom agent goal show [ref] [--thread <id>|--all-threads] [--history] [--json]
8
+ * mnemom agent goal pin [ref] [--thread <id>] [--off] [--json]
9
+ * mnemom agent goal reset [ref] [--thread <id>] [--statement <s>] [--requirement <r>...] [--yes] [--json]
10
+ *
11
+ * Exit codes: 0 ok; 1 no such session / auth or agent not found / gateway or network failure /
12
+ * reset declined at the prompt; 2 a usage error (not implicit mode, a bad --thread, --requirement
13
+ * without --statement, reset without --yes off a TTY) or a 409 from the gateway (sealed contract,
14
+ * nothing to pin).
15
+ *
16
+ * Pure apart from the injected deps, so every arm is unit-tested; `liveGoalDeps` is the driver
17
+ * index.ts wires in. Key material is never printed.
18
+ */
19
+ import { readSessionRecords } from "../lib/agent-ledger.js";
20
+ import { MNEMOM_DIR } from "../lib/config.js";
21
+ import { fmt, formatRelativeAge } from "../lib/format.js";
22
+ import { GoalControlError, goalControlFetch, isValidGoalThread, } from "../lib/goal-api.js";
23
+ import { askYesNo, isInteractive } from "../lib/prompt.js";
24
+ import { localGoalFlag, resolveSessionRef } from "./agent-sessions.js";
25
+ /**
26
+ * The Anthropic key the launch would resolve (env, then ~/.mnemom/launcher.json), never prompted
27
+ * for. Lazy import: commands/agent.ts is the launcher and carries a heavy import graph.
28
+ */
29
+ export async function resolveLaunchCreds() {
30
+ const { resolveAnthropicKey } = await import("./agent.js");
31
+ try {
32
+ return { anthropicKey: await resolveAnthropicKey(process.env, { allowPrompt: false }) };
33
+ }
34
+ catch {
35
+ throw new Error("no Anthropic key in env (MNEMOM_AGENT_ANTHROPIC_KEY / ANTHROPIC_API_KEY) or ~/.mnemom/launcher.json — " +
36
+ "the goal commands authenticate with the same key the session launched with");
37
+ }
38
+ }
39
+ export const liveGoalDeps = {
40
+ readRecords: () => readSessionRecords({ dir: MNEMOM_DIR }).reverse(),
41
+ fetch: (url, init) => fetch(url, init),
42
+ resolveCreds: resolveLaunchCreds,
43
+ isTTY: () => isInteractive(),
44
+ confirm: (q) => askYesNo(q, false),
45
+ out: (line) => console.log(line),
46
+ err: (line) => console.error(line),
47
+ };
48
+ /** A failed command: the message and the exit code. */
49
+ class Exit extends Error {
50
+ code;
51
+ constructor(code, message) {
52
+ super(message);
53
+ this.code = code;
54
+ }
55
+ }
56
+ /** Validate `--thread` locally (same regex as the gateway); `all` only where `allowAll`. */
57
+ export function parseThreadOption(raw, allowAll) {
58
+ const thread = raw?.trim() || "main";
59
+ if (!isValidGoalThread(thread)) {
60
+ throw new Exit(2, `invalid --thread '${raw}': use main, or a thread id such as a.<agent-id> (see \`mnemom agent goal show --all-threads\`)`);
61
+ }
62
+ if (thread === "all" && !allowAll) {
63
+ throw new Exit(2, "--thread all is read-only; pin and reset take one thread");
64
+ }
65
+ return thread;
66
+ }
67
+ /** The ledger record for `ref`, refusing anything that is not an implicit-mode session. */
68
+ export function pickImplicitSession(records, ref) {
69
+ if (records.length === 0) {
70
+ throw new Exit(1, "No sessions yet — run `mnemom agent` to start one.");
71
+ }
72
+ const resolved = resolveSessionRef(records, ref);
73
+ if (resolved === null) {
74
+ throw new Exit(1, `No session matches '${ref ?? ""}' — try \`mnemom agent sessions\`.`);
75
+ }
76
+ if (resolved.record === null) {
77
+ const ids = resolved.candidates.map((c) => c.conversation_id.slice(0, 12)).join(", ");
78
+ throw new Exit(1, `'${ref ?? ""}' matches ${resolved.candidates.length} sessions (${ids}) — give more of the conversation id.`);
79
+ }
80
+ const mode = localGoalFlag(resolved.record);
81
+ if (mode !== "implicit") {
82
+ throw new Exit(2, `session is not in implicit goal mode (goal_mode=${mode})`);
83
+ }
84
+ return resolved.record;
85
+ }
86
+ /** A gateway / transport failure → the exit code and a message that never names the key. */
87
+ export function mapGoalError(e, host) {
88
+ if (e instanceof Exit)
89
+ return e;
90
+ if (e instanceof GoalControlError) {
91
+ if (e.status === 409) {
92
+ if (e.code === "sealed_contract") {
93
+ return new Exit(2, "this conversation runs a sealed goal contract, not an implicit goal");
94
+ }
95
+ if (e.code === "nothing_to_pin") {
96
+ return new Exit(2, "this thread has no live goal to pin yet (rev 0)");
97
+ }
98
+ return new Exit(2, `the gateway refused: ${e.message}`);
99
+ }
100
+ if (e.status === 401) {
101
+ return new Exit(1, `the gateway on ${host} rejected the credential: ${e.message}`);
102
+ }
103
+ if (e.status === 404) {
104
+ if (e.code === "agent_not_found") {
105
+ return new Exit(1, `agent not found for this key on ${host}: was the session launched with a different key or agent?`);
106
+ }
107
+ if (e.code === "goal_intent_disabled") {
108
+ return new Exit(1, `implicit goal alignment is not enabled on ${host}`);
109
+ }
110
+ return new Exit(1, `${host} has no goal control route yet (HTTP 404)`);
111
+ }
112
+ if (e.status === 400)
113
+ return new Exit(2, `the gateway rejected the request: ${e.message}`);
114
+ if (e.status === 0)
115
+ return new Exit(1, e.message);
116
+ return new Exit(1, `the gateway on ${host} failed (HTTP ${e.status}): ${e.message}`);
117
+ }
118
+ return new Exit(1, e instanceof Error ? e.message : String(e));
119
+ }
120
+ async function run(deps, body) {
121
+ try {
122
+ await body();
123
+ return 0;
124
+ }
125
+ catch (e) {
126
+ const exit = mapGoalError(e, "the gateway");
127
+ deps.err(fmt.error(`mnemom agent goal: ${exit.message}`));
128
+ process.exitCode = exit.code;
129
+ return exit.code;
130
+ }
131
+ }
132
+ async function call(host, fn) {
133
+ try {
134
+ return await fn();
135
+ }
136
+ catch (e) {
137
+ throw mapGoalError(e, host);
138
+ }
139
+ }
140
+ // ─── show ─────────────────────────────────────────────────────────────────────
141
+ function listOrDash(items) {
142
+ return items.length > 0 ? items.join(", ") : "—";
143
+ }
144
+ /** `rev 3 · pinned@3 · class refine · 2 pending`, or `rev 0 (no goal yet)`. */
145
+ export function revisionLine(body) {
146
+ const i = body.intent;
147
+ if (!i) {
148
+ return body.last_revision > 0
149
+ ? `rev 0 (no goal yet; last revision ${body.last_revision})`
150
+ : "rev 0 (no goal yet)";
151
+ }
152
+ const bits = [`rev ${i.revision}`];
153
+ if (i.pinned)
154
+ bits.push(`pinned@${i.pinned.revision}`);
155
+ const last = i.history[i.history.length - 1];
156
+ if (last)
157
+ bits.push(`class ${last.class}`);
158
+ bits.push(`${i.pending_count} pending`);
159
+ return bits.join(" · ");
160
+ }
161
+ function intentLines(i, history) {
162
+ const L = [];
163
+ L.push(fmt.label("statement: ", i.statement));
164
+ if (i.requirements.length === 0) {
165
+ L.push(fmt.label("requirements: ", "—"));
166
+ }
167
+ else {
168
+ L.push(fmt.label("requirements:", ""));
169
+ for (const r of i.requirements)
170
+ L.push(` - ${r}`);
171
+ }
172
+ L.push(fmt.label("allowed paths: ", listOrDash(i.allowed_paths)));
173
+ L.push(fmt.label("forbidden paths:", listOrDash(i.forbidden_paths)));
174
+ if (history) {
175
+ L.push(fmt.label("history:", ""));
176
+ for (const h of i.history) {
177
+ L.push(` r${h.revision} ${h.class.padEnd(10)} ${String(h.ops).padStart(2)} ops ${h.at} ${h.source ?? "—"}`);
178
+ }
179
+ }
180
+ return L;
181
+ }
182
+ /** The human view of one show body. */
183
+ export function buildGoalShowView(record, body, opts) {
184
+ const L = [];
185
+ L.push(fmt.label("session: ", `${record.session} (${record.conversation_id.slice(0, 8)}, ${formatRelativeAge(record.started_at)})`));
186
+ L.push(fmt.label("thread: ", body.thread));
187
+ if (body.mode === "sealed") {
188
+ L.push(fmt.dim(` this conversation runs a sealed goal contract (hash ${body.sealed_hash ?? "?"})`));
189
+ return L;
190
+ }
191
+ L.push(revisionLine(body));
192
+ if (body.intent)
193
+ L.push(...intentLines(body.intent, opts.history === true));
194
+ const v = body.verdict;
195
+ L.push(fmt.label("verdict: ", v
196
+ ? `${v.verdict.toUpperCase()} at turn ${v.turn}${v.superseded ? " (superseded)" : ""}`
197
+ : fmt.dim("none yet")));
198
+ if (body.threads) {
199
+ L.push(fmt.label("threads:", ""));
200
+ for (const t of body.threads) {
201
+ L.push(` ${t.thread.padEnd(22)} ${t.role.padEnd(9)} rev ${t.revision}${t.pinned ? " · pinned" : ""} ${t.statement ?? fmt.dim("(no goal)")}`);
202
+ }
203
+ }
204
+ return L;
205
+ }
206
+ /** `mnemom agent goal show [ref] [--thread <id>|--all-threads] [--history] [--json]` */
207
+ export function agentGoalShowCommand(ref, opts, deps) {
208
+ return run(deps, async () => {
209
+ if (opts.allThreads && opts.thread && opts.thread.trim() !== "all") {
210
+ throw new Exit(2, "--thread and --all-threads are exclusive");
211
+ }
212
+ const thread = opts.allThreads ? "all" : parseThreadOption(opts.thread, true);
213
+ const record = pickImplicitSession(deps.readRecords(), ref);
214
+ const creds = await deps.resolveCreds();
215
+ const body = await call(record.gateway_host, () => goalControlFetch(record, "show", { thread }, { fetch: deps.fetch, creds }));
216
+ if (opts.json) {
217
+ deps.out(JSON.stringify({ gateway: { ...body, source: "gateway" }, ledger: { ...record, source: "ledger" } }, null, 2));
218
+ return;
219
+ }
220
+ for (const line of buildGoalShowView(record, body, { history: opts.history }))
221
+ deps.out(line);
222
+ });
223
+ }
224
+ // ─── pin ──────────────────────────────────────────────────────────────────────
225
+ /** `mnemom agent goal pin [ref] [--thread <id>] [--off] [--json]` */
226
+ export function agentGoalPinCommand(ref, opts, deps) {
227
+ return run(deps, async () => {
228
+ const thread = parseThreadOption(opts.thread, false);
229
+ const record = pickImplicitSession(deps.readRecords(), ref);
230
+ const creds = await deps.resolveCreds();
231
+ const pinned = opts.off !== true;
232
+ const body = await call(record.gateway_host, () => goalControlFetch(record, "pin", { thread, pinned }, { fetch: deps.fetch, creds }));
233
+ if (opts.json) {
234
+ deps.out(JSON.stringify({ ...body, source: "gateway" }, null, 2));
235
+ return;
236
+ }
237
+ deps.out(fmt.success(body.pinned
238
+ ? `pinned ${thread} at rev ${body.pinned.revision} — the gateway keeps this goal until \`mnemom agent goal pin --off\``
239
+ : `unpinned ${thread} (rev ${body.revision}) — new directions revise the goal again`));
240
+ });
241
+ }
242
+ // ─── reset ────────────────────────────────────────────────────────────────────
243
+ /** `mnemom agent goal reset [ref] [--thread <id>] [--statement <s>] [--requirement <r>...] [--yes] [--json]` */
244
+ export function agentGoalResetCommand(ref, opts, deps) {
245
+ return run(deps, async () => {
246
+ const thread = parseThreadOption(opts.thread, false);
247
+ const statement = opts.statement?.trim() || undefined;
248
+ const requirements = (opts.requirement ?? []).map((r) => r.trim()).filter((r) => r !== "");
249
+ if (requirements.length > 0 && statement === undefined) {
250
+ throw new Exit(2, "--requirement needs --statement (requirements belong to a stated goal)");
251
+ }
252
+ const record = pickImplicitSession(deps.readRecords(), ref);
253
+ if (!opts.yes) {
254
+ if (!deps.isTTY())
255
+ throw new Exit(2, "refusing to reset without --yes (no TTY to confirm on)");
256
+ const what = statement
257
+ ? `replace the ${thread} goal of '${record.session}' with your statement`
258
+ : `clear the ${thread} goal of '${record.session}' (the next message re-infers it)`;
259
+ if (!(await deps.confirm(`Reset: ${what}?`)))
260
+ throw new Exit(1, "reset cancelled");
261
+ }
262
+ const creds = await deps.resolveCreds();
263
+ const body = await call(record.gateway_host, () => goalControlFetch(record, "reset", {
264
+ thread,
265
+ ...(statement !== undefined ? { statement } : {}),
266
+ ...(requirements.length > 0 ? { requirements } : {}),
267
+ }, { fetch: deps.fetch, creds }));
268
+ if (opts.json) {
269
+ deps.out(JSON.stringify({ ...body, source: "gateway" }, null, 2));
270
+ return;
271
+ }
272
+ deps.out(fmt.success(body.intent
273
+ ? `reset ${thread} to your goal at rev ${body.revision} (was rev ${body.prior_revision})`
274
+ : `reset ${thread}: rev 0 (no goal yet) — the next message re-infers the goal as rev ${body.prior_revision + 1}`));
275
+ });
276
+ }
@@ -6,28 +6,22 @@
6
6
  * a terminal we ask two things, explaining each in a sentence:
7
7
  *
8
8
  * 1. a SESSION NAME — labels the session in traces and the claude.ai session
9
- * list (defaults to the current folder's name);
10
- * 2. GOAL ALIGNMENT — one of three:
11
- * - INFER it from your messages (implicit mode): nothing to type; the
12
- * gateway reads your own messages, revises the goal as you steer, and the
13
- * judge nudges the agent back when it drifts (a little µ per new message
14
- * and per judged turn). Sends `x-mnemom-goal-mode: implicit`, no contract.
15
- * - TYPE a one-line goal: sealed as a contract for the session; the judge
16
- * nudges against it (a little µ per turn). Sends `x-mnemom-contract`.
17
- * - OFF: the session runs governed with goal alignment off — neither header
18
- * is sent, so the gateway's goal hooks are simply not engaged.
9
+ * list (defaults to the current folder's name).
10
+ *
11
+ * GOAL ALIGNMENT is not prompted for: it defaults to IMPLICIT. A bare launch
12
+ * runs governed with the goal inferred from your own messages
13
+ * (`x-mnemom-goal-mode: implicit`, no contract). `--goal` seals a one-line
14
+ * contract instead (`x-mnemom-contract`); `--no-implicit` (or launcher.toml
15
+ * `goal_mode = "off"`) turns alignment off — neither header is sent. The mode is
16
+ * resolved upstream (`resolveImplicit`) and announced on the launch line.
19
17
  *
20
18
  * Non-interactive runs (pipes, CI) never prompt — they get a clear error asking
21
- * for a name. `--goal` on the command line always wins over the prompt, and a
22
- * resolved implicit launch (`--implicit`, or launcher.toml `goal_mode =
23
- * "implicit"` with no `--no-implicit`) skips it too. Pure apart from the
24
- * injected `ask` functions, so every arm is unit-tested.
19
+ * for a name. Pure apart from the injected `ask` functions, so every arm is
20
+ * unit-tested.
25
21
  */
26
22
  export interface SessionAsk {
27
23
  askInput(question: string): Promise<string>;
28
24
  askYesNo(question: string, defaultYes: boolean): Promise<boolean>;
29
- /** Numbered single-select; resolves the chosen label, or null on no/invalid choice. */
30
- askSelect(question: string, options: string[]): Promise<string | null>;
31
25
  }
32
26
  export interface SessionSetupInput {
33
27
  /** The positional `[scenario]` (session name), if given. */
@@ -51,12 +45,6 @@ export interface SessionSetup {
51
45
  prompted: boolean;
52
46
  }
53
47
  export declare const NO_SESSION_NAME_MESSAGE: string;
54
- /** The three goal-alignment choices offered by the interactive prompt (labels are the select options). */
55
- export declare const GOAL_CHOICE_IMPLICIT = "Infer my goal from my messages \u2014 nothing to type; revised as I steer (implicit mode)";
56
- export declare const GOAL_CHOICE_TYPED = "Type a one-line goal \u2014 sealed as a contract for this session";
57
- export declare const GOAL_CHOICE_OFF = "Off \u2014 run governed with goal alignment off (Enter)";
58
- export declare const GOAL_CHOICES: readonly string[];
59
- export declare const GOAL_CHOICE_QUESTION = "Goal alignment for this session?";
60
48
  /** Current folder name, sanitised like an agent slug; "session" when that is empty. */
61
49
  export declare function defaultSessionName(cwd?: string): string;
62
50
  /** The one-line status printed at launch so the customer always knows the mode. */
@@ -6,37 +6,23 @@
6
6
  * a terminal we ask two things, explaining each in a sentence:
7
7
  *
8
8
  * 1. a SESSION NAME — labels the session in traces and the claude.ai session
9
- * list (defaults to the current folder's name);
10
- * 2. GOAL ALIGNMENT — one of three:
11
- * - INFER it from your messages (implicit mode): nothing to type; the
12
- * gateway reads your own messages, revises the goal as you steer, and the
13
- * judge nudges the agent back when it drifts (a little µ per new message
14
- * and per judged turn). Sends `x-mnemom-goal-mode: implicit`, no contract.
15
- * - TYPE a one-line goal: sealed as a contract for the session; the judge
16
- * nudges against it (a little µ per turn). Sends `x-mnemom-contract`.
17
- * - OFF: the session runs governed with goal alignment off — neither header
18
- * is sent, so the gateway's goal hooks are simply not engaged.
9
+ * list (defaults to the current folder's name).
10
+ *
11
+ * GOAL ALIGNMENT is not prompted for: it defaults to IMPLICIT. A bare launch
12
+ * runs governed with the goal inferred from your own messages
13
+ * (`x-mnemom-goal-mode: implicit`, no contract). `--goal` seals a one-line
14
+ * contract instead (`x-mnemom-contract`); `--no-implicit` (or launcher.toml
15
+ * `goal_mode = "off"`) turns alignment off — neither header is sent. The mode is
16
+ * resolved upstream (`resolveImplicit`) and announced on the launch line.
19
17
  *
20
18
  * Non-interactive runs (pipes, CI) never prompt — they get a clear error asking
21
- * for a name. `--goal` on the command line always wins over the prompt, and a
22
- * resolved implicit launch (`--implicit`, or launcher.toml `goal_mode =
23
- * "implicit"` with no `--no-implicit`) skips it too. Pure apart from the
24
- * injected `ask` functions, so every arm is unit-tested.
19
+ * for a name. Pure apart from the injected `ask` functions, so every arm is
20
+ * unit-tested.
25
21
  */
26
22
  import { basename } from "node:path";
27
23
  import { sanitizeAgentName } from "../lib/agent.js";
28
24
  export const NO_SESSION_NAME_MESSAGE = "mnemom agent: a session name is required here (e.g. `mnemom agent onboarding`). " +
29
25
  "In a terminal, plain `mnemom agent` asks for one.";
30
- /** The three goal-alignment choices offered by the interactive prompt (labels are the select options). */
31
- export const GOAL_CHOICE_IMPLICIT = "Infer my goal from my messages — nothing to type; revised as I steer (implicit mode)";
32
- export const GOAL_CHOICE_TYPED = "Type a one-line goal — sealed as a contract for this session";
33
- export const GOAL_CHOICE_OFF = "Off — run governed with goal alignment off (Enter)";
34
- export const GOAL_CHOICES = [
35
- GOAL_CHOICE_IMPLICIT,
36
- GOAL_CHOICE_TYPED,
37
- GOAL_CHOICE_OFF,
38
- ];
39
- export const GOAL_CHOICE_QUESTION = "Goal alignment for this session?";
40
26
  /** Current folder name, sanitised like an agent slug; "session" when that is empty. */
41
27
  export function defaultSessionName(cwd = process.cwd()) {
42
28
  return sanitizeAgentName(basename(cwd)) || "session";
@@ -50,7 +36,7 @@ export function goalAlignmentLine(mode) {
50
36
  }
51
37
  return m === "on"
52
38
  ? "mnemom agent: goal alignment: on (the judge nudges the agent back when it drifts from your goal)"
53
- : "mnemom agent: goal alignment: off (no goal set — pass --goal or --implicit, or set one when prompted)";
39
+ : "mnemom agent: goal alignment: off (--no-implicit or goal_mode = off — pass --goal or drop --no-implicit to turn it back on)";
54
40
  }
55
41
  const defaultSay = (line = "") => console.error(line);
56
42
  /**
@@ -79,36 +65,13 @@ export async function resolveSessionSetup(input, ask) {
79
65
  say(` (Next time you can pass it directly: mnemom agent <name>.)`);
80
66
  const typed = await ask.askInput(`Session name [${fallback}]:`);
81
67
  const scenario = sanitizeAgentName(typed.trim()) || fallback;
82
- // ── 2. goal alignment (skipped when --goal or implicit mode was resolved) ──
68
+ // ── 2. goal alignment — resolved upstream, no prompt. Implicit is the DEFAULT
69
+ // (a bare launch runs governed with the goal inferred from your messages), so
70
+ // there is nothing to pick here: `--goal` seals a contract, the default (and
71
+ // `--implicit` / `goal_mode = "implicit"`) is implicit, and `--no-implicit` /
72
+ // `goal_mode = "off"` turns it off. The launch line announces the mode.
83
73
  if (flagGoal) {
84
74
  return { scenario, goal: flagGoal, goalAlignment: "on", prompted: true };
85
75
  }
86
- if (implicit) {
87
- return { scenario, goalAlignment: "implicit", prompted: true };
88
- }
89
- say();
90
- say("Goal alignment — how Mnemom keeps this session on track. Three choices:");
91
- say(" Infer: nothing to type — the gateway reads your own messages, revises the goal as you");
92
- say(" steer, and the judge nudges the agent back when it drifts (a little µ per message + turn).");
93
- say(" Type a goal: one line, sealed as a contract; the judge nudges against it (a little µ per turn).");
94
- say(" Off: the session runs governed with goal alignment off (the default — just press Enter).");
95
- say(" (Make one the default: mnemom agent config set goal_mode implicit|off.)");
96
- const choice = await ask.askSelect(GOAL_CHOICE_QUESTION, [...GOAL_CHOICES]);
97
- if (choice === GOAL_CHOICE_IMPLICIT) {
98
- return { scenario, goalAlignment: "implicit", prompted: true };
99
- }
100
- if (choice === GOAL_CHOICE_TYPED) {
101
- const goal = (await ask.askInput("Goal (one line):")).trim();
102
- if (!goal) {
103
- say(" No goal entered — continuing with goal alignment off.");
104
- return { scenario, goalAlignment: "off", prompted: true };
105
- }
106
- return { scenario, goal, goalAlignment: "on", prompted: true };
107
- }
108
- // Enter (no pick) and any invalid pick both resolve to null → off. Off is the
109
- // deliberate default for now: a µ-costing mode is never picked by a bare Enter.
110
- if (choice !== GOAL_CHOICE_OFF) {
111
- say(" Goal alignment off (press 1 or 2 next time to turn it on).");
112
- }
113
- return { scenario, goalAlignment: "off", prompted: true };
76
+ return { scenario, goalAlignment: implicit ? "implicit" : "off", prompted: true };
114
77
  }
@@ -15,11 +15,17 @@
15
15
  */
16
16
  import { type GoalMode, type SessionRecord } from "../lib/agent-ledger.js";
17
17
  import { type AgentSessionResponse } from "../lib/api.js";
18
+ import { type GoalShowResponse } from "../lib/goal-api.js";
18
19
  export interface SessionCommandDeps {
19
20
  /** The ledger, newest first. */
20
21
  readRecords: () => SessionRecord[];
21
22
  /** The live state from mnemom-api; rejects when unreachable / not found. */
22
23
  fetchSession: (conversationId: string, agentId: string, orgId: string) => Promise<AgentSessionResponse>;
24
+ /**
25
+ * The live implicit goal from the gateway's control route (`GET /mnemom/goal/intent`), for the
26
+ * one `live goal` line an implicit session gets. Optional: absent ⇒ no line.
27
+ */
28
+ fetchGoal?: (record: SessionRecord) => Promise<GoalShowResponse>;
23
29
  out: (line: string) => void;
24
30
  err: (line: string) => void;
25
31
  }
@@ -35,15 +41,17 @@ export interface SessionRefMatch {
35
41
  }
36
42
  export interface SessionRefAmbiguous {
37
43
  record: null;
38
- /** Every record whose conversation id starts with the ref. */
44
+ /** The newest record of each distinct conversation whose id starts with the ref. */
39
45
  candidates: SessionRecord[];
40
46
  }
41
47
  /**
42
48
  * Resolve `[ref]` against the ledger (newest first):
43
49
  * - no ref → the newest record;
44
50
  * - an exact session name → the newest record with that name, older ones in `others`;
45
- * - else a conversation-id prefix → the unique match, or `{record: null, candidates}`
46
- * when several share the prefix;
51
+ * - else a conversation-id prefix → the newest record of the one conversation it names
52
+ * (a relaunch / `--resume` appends another row for the same conversation id, which is not
53
+ * ambiguity), or `{record: null, candidates}` — newest row per conversation — when several
54
+ * distinct conversations share the prefix (an exact full id among them wins);
47
55
  * - nothing matches (or the ledger is empty) → null.
48
56
  */
49
57
  export declare function resolveSessionRef(records: SessionRecord[], ref?: string): SessionRefMatch | SessionRefAmbiguous | null;
@@ -106,5 +114,7 @@ export interface AgentShowOptions {
106
114
  }
107
115
  /** Why the live read could not happen, from the thrown error. */
108
116
  export declare function unavailableReason(err: unknown): string;
117
+ /** `rev N[ · pinned]` from a live show body, or `rev 0 (no goal yet)`. */
118
+ export declare function liveGoalLine(body: GoalShowResponse): string;
109
119
  /** `mnemom agent show [ref] [--json]` */
110
120
  export declare function agentShowCommand(ref: string | undefined, opts: AgentShowOptions, deps: SessionCommandDeps): Promise<void>;