@mnemom/mnemom 0.17.11-next.0 → 0.17.11

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.
@@ -9,11 +9,14 @@
9
9
  * Checks: settings file, coding-agent CLI, Anthropic key source, gateway health
10
10
  * (200 + the x-mnemom-verdict header that proves the governance pipeline is
11
11
  * live), Remote-Control capability (node + openssl + the --remote-control flag),
12
- * claude.ai login (informational — Remote Control needs it), and CLI auto-update status.
12
+ * claude.ai login (informational — Remote Control needs it), the agent's governance
13
+ * modes (off by default for new agents), and CLI auto-update status.
13
14
  */
14
15
  type Mark = "ok" | "warn" | "miss" | "info";
15
16
  export interface DoctorOptions {
16
17
  cli?: string;
18
+ /** The agent slug a launch would use (--agent / launcher.toml); default agent-<handle>. */
19
+ agent?: string;
17
20
  }
18
21
  /** Run the preflight and print the checklist. Sets process.exitCode on failure. */
19
22
  export declare function agentDoctorCommand(options?: DoctorOptions): Promise<void>;
@@ -21,6 +24,8 @@ export interface CodeAccessProbe {
21
24
  mark: Mark;
22
25
  text: string;
23
26
  hint?: string;
27
+ /** May this account use Remote Control? Only a determined `remote_control: true` says yes. */
28
+ remoteControl: boolean;
24
29
  }
25
30
  /**
26
31
  * The invited-cohort gate, as the doctor reports it. Signed out → info (we can't
@@ -29,4 +34,17 @@ export interface CodeAccessProbe {
29
34
  * anything inconclusive → info, never a MISS (the launch preamble fails open too).
30
35
  */
31
36
  export declare function probeCodeAccess(): Promise<CodeAccessProbe>;
37
+ export interface GovernanceProbe {
38
+ mark: Mark;
39
+ text: string;
40
+ hint?: string;
41
+ }
42
+ /**
43
+ * The governance line: the composed (every layer, strictest wins) integrity and
44
+ * Safe House modes of the agent a launch would use. Signed out, no such agent,
45
+ * or an unreadable card → info with the off-by-default note, never a MISS
46
+ * (governance never blocks a launch). On → info naming the per-turn cost; the
47
+ * agent's own card says off but a stricter layer keeps it on → warn.
48
+ */
49
+ export declare function probeGovernance(agent: string | undefined): Promise<GovernanceProbe>;
32
50
  export {};
@@ -9,7 +9,8 @@
9
9
  * Checks: settings file, coding-agent CLI, Anthropic key source, gateway health
10
10
  * (200 + the x-mnemom-verdict header that proves the governance pipeline is
11
11
  * live), Remote-Control capability (node + openssl + the --remote-control flag),
12
- * claude.ai login (informational — Remote Control needs it), and CLI auto-update status.
12
+ * claude.ai login (informational — Remote Control needs it), the agent's governance
13
+ * modes (off by default for new agents), and CLI auto-update status.
13
14
  */
14
15
  import { spawnSync } from "node:child_process";
15
16
  import { existsSync } from "node:fs";
@@ -19,11 +20,12 @@ import { resolveAgentAuth } from "../lib/subscription-login.js";
19
20
  import { isProdGateway, resolveAnthropicDoor, resolveGatewayHost, singleCellError, } from "../lib/agent.js";
20
21
  import { getApiUrl, getGatewayUrl } from "../lib/config.js";
21
22
  import { LAUNCHER_CONFIG_PATH } from "../lib/launcher-config.js";
22
- import { getCodeEntitlement, MnemomApiError } from "../lib/api.js";
23
+ import { getAgentByName, getCardSources, getCodeEntitlement, MnemomApiError } from "../lib/api.js";
23
24
  import { isLoggedIn } from "../lib/auth.js";
24
25
  import { getActiveOrg } from "../lib/cli-config.js";
25
- import { isCodeNotEnabled, sourceLabel } from "./agent-invite.js";
26
+ import { isCodeNotEnabled, REQUEST_ACCESS_URL, sanitizeTerminalText, sourceLabel, withRequestAccess, } from "./agent-invite.js";
26
27
  import { probeUpdateStatus } from "../lib/update/doctor.js";
28
+ import { describeModes, GOVERNANCE_OPT_IN_HINT, resolveDefaultSlug } from "./agent-setup.js";
27
29
  const MARK = { ok: "[ ok ]", warn: "[warn]", miss: "[MISS]", info: "[info]" };
28
30
  const out = (line = "") => console.log(line);
29
31
  /** Run the preflight and print the checklist. Sets process.exitCode on failure. */
@@ -94,27 +96,35 @@ export async function agentDoctorCommand(options = {}) {
94
96
  else {
95
97
  add("warn", `gateway: ${host} [${cell}] unreachable (${health.detail})`, "check the host (MNEMOM_AGENT_GATEWAY / `mnemom agent config set gateway <url>`) and your network");
96
98
  }
97
- // ── Remote Control capability ──
98
- if (cli) {
99
+ // ── Mnemom Agent access (invited cohort gate) ── probed here, before the Remote Control
100
+ // lines, because Remote Control is internal-only: an account the api does not entitle to it
101
+ // (every customer) never sees those lines at all.
102
+ const gate = await probeCodeAccess();
103
+ // ── Remote Control capability (entitled accounts only) ──
104
+ if (cli && gate.remoteControl) {
99
105
  const rc = checkRcCapability(cli);
100
106
  if (rc.ok)
101
107
  add("ok", "Remote Control: available (node + openssl + --remote-control)");
102
108
  else
103
109
  add("warn", `Remote Control: unavailable — ${rc.missing.join("; ")}`, "launches fall back to terminal-only until these are in place");
104
- // ── claude.ai login (informational — RC needs it) ──
105
- if (cli.name === "claude") {
106
- const login = claudeLoggedIn(cli.bin);
107
- if (login === true)
108
- add("ok", "claude.ai login: logged in");
109
- else if (login === false)
110
- add("info", "claude.ai login: not logged in", "Remote Control needs a claude.ai login; the terminal-only shape does not");
111
- else
112
- add("info", "claude.ai login: could not determine");
113
- }
114
110
  }
115
- // ── Mnemom Agent access (invited cohort gate) ──
116
- const gate = await probeCodeAccess();
111
+ // ── claude.ai login (informational) ── Remote Control needs it, and so does auth =
112
+ // subscription; a terminal launch on an Anthropic key does not, so nobody else sees it.
113
+ if (cli && cli.name === "claude" && (gate.remoteControl || auth === "subscription")) {
114
+ const login = claudeLoggedIn(cli.bin);
115
+ if (login === true)
116
+ add("ok", "claude.ai login: logged in");
117
+ else if (login === false)
118
+ add("info", "claude.ai login: not logged in", auth === "subscription"
119
+ ? "auth = subscription launches Claude Code on your claude.ai login — run `claude` and /login"
120
+ : "Remote Control needs a claude.ai login; the terminal-only shape does not");
121
+ else
122
+ add("info", "claude.ai login: could not determine");
123
+ }
117
124
  add(gate.mark, gate.text, gate.hint);
125
+ // ── governance (off by default for new agents) ──
126
+ const gov = await probeGovernance(options.agent);
127
+ add(gov.mark, gov.text, gov.hint);
118
128
  // ── CLI auto-update ──
119
129
  const upd = await probeUpdateStatus();
120
130
  add(upd.mark, upd.text, upd.hint);
@@ -187,7 +197,8 @@ export async function probeCodeAccess() {
187
197
  return {
188
198
  mark: "info",
189
199
  text: "Mnemom Agent access: sign in to check",
190
- hint: "run `mnemom login`; @mnemom.ai accounts are enabled by default, others need an invitation (mnemom agent --invite <token>)",
200
+ hint: `run \`mnemom login\`; @mnemom.ai accounts are enabled by default, others need an invitation (mnemom agent --invite <token>) — request one at ${REQUEST_ACCESS_URL}`,
201
+ remoteControl: false,
191
202
  };
192
203
  }
193
204
  const org = getActiveOrg();
@@ -197,9 +208,7 @@ export async function probeCodeAccess() {
197
208
  return {
198
209
  mark: "ok",
199
210
  text: `Mnemom Agent access: enabled for ${orgLabel} (${sourceLabel(ent.source)})`,
200
- hint: ent.remote_control
201
- ? undefined
202
- : "Remote Control (steer from claude.ai / the Claude app) is an internal feature — your sessions run terminal-only",
211
+ remoteControl: ent.remote_control === true,
203
212
  };
204
213
  }
205
214
  catch (err) {
@@ -208,12 +217,88 @@ export async function probeCodeAccess() {
208
217
  return {
209
218
  mark: "miss",
210
219
  text: `Mnemom Agent access: not enabled for ${orgLabel}`,
211
- hint: typeof hint === "string" && hint
212
- ? hint
213
- : "redeem an invitation: mnemom agent --invite <token>",
220
+ hint: withRequestAccess(typeof hint === "string" && hint
221
+ ? sanitizeTerminalText(hint)
222
+ : "redeem an invitation: mnemom agent --invite <token>"),
223
+ remoteControl: false,
214
224
  };
215
225
  }
216
226
  const detail = err instanceof MnemomApiError || err instanceof Error ? err.message : String(err);
217
- return { mark: "info", text: `Mnemom Agent access: could not determine (${detail})` };
227
+ return {
228
+ mark: "info",
229
+ text: `Mnemom Agent access: could not determine (${detail})`,
230
+ remoteControl: false,
231
+ };
232
+ }
233
+ }
234
+ const OPT_IN_HINT = GOVERNANCE_OPT_IN_HINT.trim();
235
+ /**
236
+ * The governance line: the composed (every layer, strictest wins) integrity and
237
+ * Safe House modes of the agent a launch would use. Signed out, no such agent,
238
+ * or an unreadable card → info with the off-by-default note, never a MISS
239
+ * (governance never blocks a launch). On → info naming the per-turn cost; the
240
+ * agent's own card says off but a stricter layer keeps it on → warn.
241
+ */
242
+ export async function probeGovernance(agent) {
243
+ const offByDefault = {
244
+ mark: "info",
245
+ text: "governance: off by default for new agents (AIP integrity analysis, Safe House screening)",
246
+ hint: `${OPT_IN_HINT} Agents set up before this default keep their cards.`,
247
+ };
248
+ if (!(await isLoggedIn()))
249
+ return offByDefault;
250
+ try {
251
+ const slug = agent?.trim() || (await resolveDefaultSlug());
252
+ const found = await getAgentByName(slug);
253
+ if (!found)
254
+ return offByDefault;
255
+ const [align, prot] = await Promise.all([
256
+ getCardSources("alignment", found.id),
257
+ getCardSources("protection", found.id),
258
+ ]);
259
+ const read = (s, layer, field) => {
260
+ const v = s?.[layer]?.card_json?.[field];
261
+ return typeof v === "string" && v ? v : undefined;
262
+ };
263
+ // A composed card without the field (or none composed yet) is unknown, not
264
+ // off: the data plane falls back to its own default (observe for integrity),
265
+ // so the doctor never claims governance is off when it can't see it.
266
+ const composed = {
267
+ integrity: read(align, "composed", "integrity_mode") ?? "unknown",
268
+ protection: read(prot, "composed", "mode") ?? "unknown",
269
+ };
270
+ const own = {
271
+ integrity: read(align, "agent", "integrity_mode"),
272
+ protection: read(prot, "agent", "mode"),
273
+ };
274
+ const text = `governance: ${slug} — ${describeModes(composed)}`;
275
+ const stricter = (mine, eff) => mine === "off" && eff !== "off" && eff !== "unknown";
276
+ const overridden = stricter(own.integrity, composed.integrity) || stricter(own.protection, composed.protection);
277
+ if (overridden) {
278
+ return {
279
+ mark: "warn",
280
+ text,
281
+ hint: "the agent's own card says off, but a stricter org, team or platform policy keeps it on — it still runs and is billed",
282
+ };
283
+ }
284
+ if (composed.integrity === "off" && composed.protection === "off") {
285
+ return { mark: "ok", text, hint: OPT_IN_HINT };
286
+ }
287
+ if (composed.integrity === "unknown" || composed.protection === "unknown") {
288
+ return {
289
+ mark: "info",
290
+ text,
291
+ hint: "no composed card yet for at least one feature — it reads once the agent's cards compose (re-run `mnemom agent doctor` in a minute)",
292
+ };
293
+ }
294
+ return {
295
+ mark: "info",
296
+ text,
297
+ hint: "each feature that is on is a model call per turn, billed at cost on top of the agent fee — `mnemom agent setup --no-governance` turns it off",
298
+ };
299
+ }
300
+ catch (err) {
301
+ const detail = err instanceof Error ? err.message : String(err);
302
+ return { ...offByDefault, text: `governance: could not read the agent's cards (${detail})` };
218
303
  }
219
304
  }
@@ -29,6 +29,13 @@ export declare class CodeNotEnabledError extends Error {
29
29
  /** `pinv_` + base64url — the product-invite token shape the api mints. */
30
30
  export declare const INVITE_TOKEN_RE: RegExp;
31
31
  export declare function looksLikeInviteToken(value: unknown): value is string;
32
+ /**
33
+ * Where someone without an invitation (or a Mnemom contact) asks for access. Every
34
+ * locked-out message ends with it, so an uninvited customer is never at a dead end.
35
+ */
36
+ export declare const REQUEST_ACCESS_URL = "https://www.mnemom.ai/contact";
37
+ /** `hint`, plus the request-access pointer unless the hint already carries the URL. */
38
+ export declare function withRequestAccess(hint: string): string;
32
39
  /** Drop control characters (ANSI escapes included) from an api-supplied string before
33
40
  * it reaches the terminal — defense in depth; the value is first-party but printed raw. */
34
41
  export declare function sanitizeTerminalText(s: string): string;
@@ -39,6 +39,25 @@ export function looksLikeInviteToken(value) {
39
39
  return typeof value === "string" && INVITE_TOKEN_RE.test(value.trim());
40
40
  }
41
41
  const FALLBACK_HINT = "Ask your Mnemom contact for a Mnemom Agent invitation, then run: mnemom agent --invite <token>";
42
+ /**
43
+ * Where someone without an invitation (or a Mnemom contact) asks for access. Every
44
+ * locked-out message ends with it, so an uninvited customer is never at a dead end.
45
+ */
46
+ export const REQUEST_ACCESS_URL = "https://www.mnemom.ai/contact";
47
+ /** Whether `hint` already names the request-access page, as a whole word (trailing
48
+ * punctuation ignored) — a token match, not a substring one. */
49
+ function mentionsRequestAccess(hint) {
50
+ return hint.split(/\s+/).some((w) => w.replace(/[.,;:!?)]+$/, "") === REQUEST_ACCESS_URL);
51
+ }
52
+ /** `hint`, plus the request-access pointer unless the hint already carries the URL. */
53
+ export function withRequestAccess(hint) {
54
+ if (mentionsRequestAccess(hint))
55
+ return hint;
56
+ let end = hint.length;
57
+ while (end > 0 && (hint[end - 1] === "." || hint[end - 1].trim() === ""))
58
+ end--;
59
+ return `${hint.slice(0, end)}. No invitation yet? Request access at ${REQUEST_ACCESS_URL}`;
60
+ }
42
61
  /** Drop control characters (ANSI escapes included) from an api-supplied string before
43
62
  * it reaches the terminal — defense in depth; the value is first-party but printed raw. */
44
63
  export function sanitizeTerminalText(s) {
@@ -76,7 +95,7 @@ function printNotEnabled(orgLabel, hint) {
76
95
  say();
77
96
  say(" mnemom agent --invite <token>");
78
97
  say();
79
- say(fmt.dim(` ${hint}`));
98
+ say(fmt.dim(` ${withRequestAccess(hint)}`));
80
99
  }
81
100
  /**
82
101
  * Enforce the Mnemom Agent gate for the org the launch targets. Blocks (prints
@@ -16,6 +16,9 @@
16
16
  * screen_surfaces declared, ready for when it is turned on.
17
17
  * - goal-alignment: nudge — sealed per-conversation via the goal contract
18
18
  * (--goal …), graded and re-anchored by the gateway.
19
+ * `--governance [integrity,safe-house]` opts a setup in (observe-only, billed at
20
+ * cost on top of the agent fee); a refresh of an already-claimed agent keeps the
21
+ * modes its own cards carry unless a --governance / --no-governance flag says otherwise.
19
22
  *
20
23
  * A launch never runs on an unclaimed agent (operator 2026-10-03): an unclaimed agent lands in
21
24
  * the sandbox org and bills nobody. ensureClaimedServedAgent asks the gateway which agent it
@@ -26,6 +29,43 @@
26
29
  * reinventing it.
27
30
  */
28
31
  import { type CodeEntitlement } from "../lib/api.js";
32
+ /** A card's governance mode as the CLI writes it: off, or observe-only. */
33
+ export type GovernanceMode = "off" | "observe";
34
+ /**
35
+ * The governance features a setup turns on. Each is a model call per turn that
36
+ * the customer pays for at cost, so both are off unless asked for.
37
+ */
38
+ export interface GovernanceOptIn {
39
+ /** AIP integrity analysis (alignment card integrity_mode). */
40
+ integrity: boolean;
41
+ /** Safe House screening (protection card mode). */
42
+ safeHouse: boolean;
43
+ }
44
+ /** The feature names `--governance <list>` accepts (plus `all` / `none`). */
45
+ export declare const GOVERNANCE_FEATURES: readonly ["integrity", "safe-house"];
46
+ /**
47
+ * Parse the `--governance [features]` / `--no-governance` flag. undefined = not
48
+ * given (new agents get none; a refresh keeps the agent's current modes),
49
+ * true = every feature, false = none, a string = a comma-separated list of
50
+ * `integrity`, `safe-house`, `all` or `none`. Throws on an unknown name.
51
+ */
52
+ export declare function parseGovernanceFlag(value: boolean | string | undefined): GovernanceOptIn | undefined;
53
+ /**
54
+ * The modes a setup writes into the two cards: off/observe from the flag, or
55
+ * whatever an existing agent's own cards already carry (kept as-is on a refresh).
56
+ */
57
+ export interface CardModes {
58
+ /** autonomy_mode — verification + policy. No model call, so not billed; kept as-is on a refresh. */
59
+ autonomy: string;
60
+ integrity: string;
61
+ protection: string;
62
+ }
63
+ /** One phrase for the posture line: "integrity off, Safe House off". */
64
+ export declare function describeModes(m: Pick<CardModes, "integrity" | "protection">): string;
65
+ /** The modes for a NEW agent: off unless --governance asks for a feature. */
66
+ export declare function newAgentModes(governance?: GovernanceOptIn): CardModes;
67
+ /** Printed when a setup ran without --governance: how to opt in, and what it costs. */
68
+ export declare const GOVERNANCE_OPT_IN_HINT: string;
29
69
  /**
30
70
  * The default governed-agent slug for the current customer: `agent-<handle>`,
31
71
  * where <handle> is the local-part of their mnemom login email (sanitised), or
@@ -73,6 +113,12 @@ export interface ClaimedAgentOptions {
73
113
  orgLabel: string;
74
114
  /** true = --setup (re-ask the gateway, refresh the cards); false = --no-setup (no card writes). */
75
115
  setup?: boolean;
116
+ /**
117
+ * `--governance` / `--no-governance`: the modes the cards get. undefined = a new agent's are
118
+ * off, and a refresh keeps the agent's current modes. Given on a claimed agent, it also
119
+ * refreshes the cards (unless --no-setup).
120
+ */
121
+ governance?: GovernanceOptIn;
76
122
  /** Where the served-agent cache lives (defaults to ~/.mnemom). */
77
123
  cacheDir?: string;
78
124
  }
@@ -124,9 +170,10 @@ export declare function ensureClaimedServedAgent(opts: ClaimedAgentOptions): Pro
124
170
  * (`details.required_scope`). Never the token, never the body.
125
171
  */
126
172
  export declare function describeApiError(err: unknown): string;
127
- /** `mnemom agent setup [--agent <slug>]` — the explicit provisioning entry point. */
173
+ /** `mnemom agent setup [--agent <slug>] [--governance …]` — the explicit provisioning entry point. */
128
174
  export declare function agentSetupCommand(opts?: {
129
175
  agent?: string;
176
+ governance?: GovernanceOptIn;
130
177
  }): Promise<void>;
131
178
  /** Thrown when the agent's org has no spendable MU balance. The user-facing
132
179
  * top-up guidance is already printed when this is thrown — callers exit quietly. */
@@ -187,3 +234,5 @@ export declare function resolvePreflightedAccount(): Promise<{
187
234
  orgLabel: string;
188
235
  entitlement: CodeEntitlement | null;
189
236
  }>;
237
+ export declare function buildCodeAlignmentCard(agentId: string, modes?: Pick<CardModes, "autonomy" | "integrity">): Record<string, unknown>;
238
+ export declare function buildCodeProtectionCard(agentId: string, mode?: string): Record<string, unknown>;
@@ -16,6 +16,9 @@
16
16
  * screen_surfaces declared, ready for when it is turned on.
17
17
  * - goal-alignment: nudge — sealed per-conversation via the goal contract
18
18
  * (--goal …), graded and re-anchored by the gateway.
19
+ * `--governance [integrity,safe-house]` opts a setup in (observe-only, billed at
20
+ * cost on top of the agent fee); a refresh of an already-claimed agent keeps the
21
+ * modes its own cards carry unless a --governance / --no-governance flag says otherwise.
19
22
  *
20
23
  * A launch never runs on an unclaimed agent (operator 2026-10-03): an unclaimed agent lands in
21
24
  * the sandbox org and bills nobody. ensureClaimedServedAgent asks the gateway which agent it
@@ -25,7 +28,7 @@
25
28
  * (agents.ts), claimAgent + putAlignmentCard/putProtectionCard (lib/api.ts) — rather than
26
29
  * reinventing it.
27
30
  */
28
- import { claimAgent, getAgent, getAgentByName, getMe, getMuBalance, getMyPersonalOrg, MnemomApiError, mintProtectionCapability, putAlignmentCard, putProtectionCard, } from "../lib/api.js";
31
+ import { claimAgent, getAgent, getAgentByName, getCardSources, getMe, getMuBalance, getMyPersonalOrg, MnemomApiError, mintProtectionCapability, putAlignmentCard, putProtectionCard, } from "../lib/api.js";
29
32
  import { getAuthInfo, loginWithBrowser, loginWithDeviceFlow, resolveAuth } from "../lib/auth.js";
30
33
  import { getActiveOrg } from "../lib/cli-config.js";
31
34
  import { getApiUrl, getGatewayUrl, getWebsiteUrl, MNEMOM_DIR } from "../lib/config.js";
@@ -40,6 +43,102 @@ import { birthThroughGateway } from "./wrap.js";
40
43
  import { resolveAnthropicKey } from "./agent.js";
41
44
  import { assertCodeEnabled, CodeNotEnabledError } from "./agent-invite.js";
42
45
  const say = (line = "") => console.error(line);
46
+ /** The feature names `--governance <list>` accepts (plus `all` / `none`). */
47
+ export const GOVERNANCE_FEATURES = ["integrity", "safe-house"];
48
+ /**
49
+ * Parse the `--governance [features]` / `--no-governance` flag. undefined = not
50
+ * given (new agents get none; a refresh keeps the agent's current modes),
51
+ * true = every feature, false = none, a string = a comma-separated list of
52
+ * `integrity`, `safe-house`, `all` or `none`. Throws on an unknown name.
53
+ */
54
+ export function parseGovernanceFlag(value) {
55
+ if (value === undefined)
56
+ return undefined;
57
+ if (typeof value === "boolean")
58
+ return { integrity: value, safeHouse: value };
59
+ const choice = { integrity: false, safeHouse: false };
60
+ const names = value
61
+ .split(",")
62
+ .map((s) => s.trim().toLowerCase())
63
+ .filter(Boolean);
64
+ if (names.length === 0)
65
+ return { integrity: true, safeHouse: true };
66
+ for (const name of names) {
67
+ if (name === "all") {
68
+ choice.integrity = true;
69
+ choice.safeHouse = true;
70
+ }
71
+ else if (name === "none") {
72
+ // contributes nothing; `none` alone means both off
73
+ }
74
+ else if (name === "integrity") {
75
+ choice.integrity = true;
76
+ }
77
+ else if (name === "safe-house") {
78
+ choice.safeHouse = true;
79
+ }
80
+ else {
81
+ throw new Error(`--governance: unknown feature '${name}' — use ${GOVERNANCE_FEATURES.join(", ")}, all or none (comma-separated).`);
82
+ }
83
+ }
84
+ return choice;
85
+ }
86
+ const modeOf = (on) => (on ? "observe" : "off");
87
+ /** One phrase for the posture line: "integrity off, Safe House off". */
88
+ export function describeModes(m) {
89
+ return `integrity ${m.integrity}, Safe House ${m.protection}`;
90
+ }
91
+ /** The mode string a raw card carries in `field`, or undefined when absent/not a string. */
92
+ function modeField(card, field) {
93
+ const v = card?.[field];
94
+ return typeof v === "string" && v ? v : undefined;
95
+ }
96
+ /**
97
+ * An existing agent's own (agent-layer) card, to keep its modes on a refresh.
98
+ * null = it has none. THROWS when the read fails: the caller then leaves the
99
+ * cards alone rather than guess a mode in either direction.
100
+ */
101
+ async function existingAgentCard(kind, agentId) {
102
+ const sources = await getCardSources(kind, agentId);
103
+ return sources?.agent?.card_json ?? null;
104
+ }
105
+ /** The modes for a NEW agent: off unless --governance asks for a feature. */
106
+ export function newAgentModes(governance) {
107
+ const integrity = modeOf(governance?.integrity ?? false);
108
+ return { autonomy: integrity, integrity, protection: modeOf(governance?.safeHouse ?? false) };
109
+ }
110
+ /**
111
+ * The modes for refreshing an EXISTING agent: an explicit --governance /
112
+ * --no-governance wins; else it keeps the modes its own cards carry. No agent-layer
113
+ * card → off (it never had one; new setups are off). A card with no mode field →
114
+ * observe (the prior CLI always wrote observe). A failed read → null: skip the refresh.
115
+ */
116
+ async function existingAgentModes(agentId, governance) {
117
+ if (governance)
118
+ return newAgentModes(governance);
119
+ let align;
120
+ let prot;
121
+ try {
122
+ [align, prot] = await Promise.all([
123
+ existingAgentCard("alignment", agentId),
124
+ existingAgentCard("protection", agentId),
125
+ ]);
126
+ }
127
+ catch (err) {
128
+ say(fmt.warn(`mnemom agent: couldn't read ${agentId}'s current cards (${describeApiError(err)}) — leaving them unchanged.`));
129
+ say(fmt.dim(" Re-run `mnemom agent setup`, or pass --governance / --no-governance to set them."));
130
+ return null;
131
+ }
132
+ const integrity = align ? (modeField(align, "integrity_mode") ?? "observe") : "off";
133
+ return {
134
+ autonomy: align ? (modeField(align, "autonomy_mode") ?? "observe") : "off",
135
+ integrity,
136
+ protection: prot ? (modeField(prot, "mode") ?? "observe") : "off",
137
+ };
138
+ }
139
+ /** Printed when a setup ran without --governance: how to opt in, and what it costs. */
140
+ export const GOVERNANCE_OPT_IN_HINT = " Governance (AIP integrity analysis, Safe House screening) is off by default. Turn it on with " +
141
+ "`mnemom agent setup --governance` (or --governance integrity / safe-house); it is billed at cost on top of the agent fee.";
43
142
  /**
44
143
  * The default governed-agent slug for the current customer: `agent-<handle>`,
45
144
  * where <handle> is the local-part of their mnemom login email (sanitised), or
@@ -193,11 +292,18 @@ async function confirmClaimed(opts, anchorKey, fp) {
193
292
  const slug = opts.slug;
194
293
  const agentId = await resolveServedAgent(opts, fp);
195
294
  if (await isClaimed(slug, agentId)) {
196
- if (opts.setup === true) {
295
+ if (opts.setup === true || (opts.governance && opts.setup !== false)) {
197
296
  // An explicit `mnemom agent setup` / --setup repairs or completes the cards (e.g. a
198
297
  // protection card that failed on a prior run before its one-time grant was minted).
199
298
  say(fmt.dim(`mnemom agent: '${slug}' is ${agentId}, already claimed — refreshing its cards.`));
200
- await publishDefaultCards(agentId);
299
+ const modes = await existingAgentModes(agentId, opts.governance);
300
+ if (modes) {
301
+ if (!opts.governance) {
302
+ say(fmt.dim(` Keeping its current governance (${describeModes(modes)}). New agents default to off; ` +
303
+ "`--governance` turns it on, `--no-governance` turns it off."));
304
+ }
305
+ await publishDefaultCards(agentId, modes);
306
+ }
201
307
  persistAgentSlug(slug);
202
308
  }
203
309
  else {
@@ -229,10 +335,14 @@ async function confirmClaimed(opts, anchorKey, fp) {
229
335
  }
230
336
  say(fmt.success(`mnemom agent: claimed '${slug}' (${agentId}) in ${opts.orgLabel}.`));
231
337
  await noteSameNamedAgent(slug, agentId);
232
- // Publish the default (off) cards. A card write that can't complete does NOT undo the claim —
233
- // the agent is claimed and governed on whatever landed; we warn how to finish it.
234
- if (opts.setup !== false)
235
- await publishDefaultCards(agentId);
338
+ // Publish the default cards (off unless --governance). A card write that can't complete does
339
+ // NOT undo the claim — the agent is claimed and governed on whatever landed; we warn how to
340
+ // finish it.
341
+ if (opts.setup !== false) {
342
+ await publishDefaultCards(agentId, newAgentModes(opts.governance));
343
+ if (!opts.governance)
344
+ say(fmt.dim(GOVERNANCE_OPT_IN_HINT));
345
+ }
236
346
  persistAgentSlug(slug);
237
347
  return { slug, agentId, claimedNow: true };
238
348
  }
@@ -354,9 +464,9 @@ async function tryPublishCard(name, write) {
354
464
  }
355
465
  return { name, ok: false, error: "unknown error" };
356
466
  }
357
- /** Publish both default (off) cards; never throws — warns on a card that can't be written. */
358
- async function publishDefaultCards(agentId) {
359
- const align = await tryPublishCard("alignment", () => putAlignmentCard(agentId, JSON.stringify(buildCodeAlignmentCard(agentId)), "application/json"));
467
+ /** Publish both default cards at `modes`; never throws — warns on a card that can't be written. */
468
+ async function publishDefaultCards(agentId, modes) {
469
+ const align = await tryPublishCard("alignment", () => putAlignmentCard(agentId, JSON.stringify(buildCodeAlignmentCard(agentId, modes)), "application/json"));
360
470
  // Protection is the "locked door": it needs a one-time cards:write:protection
361
471
  // grant that the claim does NOT include (alignment rides a standing cap). Mint
362
472
  // it with our human login JWT — the human running `mnemom agent` (which claims
@@ -375,13 +485,13 @@ async function publishDefaultCards(agentId) {
375
485
  say(fmt.dim(" That agent is owned by an org other than your active one — `mnemom org use <org>` to switch, or set up under a different --agent."));
376
486
  }
377
487
  }
378
- const prot = await tryPublishCard("protection", () => putProtectionCard(agentId, JSON.stringify(buildCodeProtectionCard(agentId)), "application/json"));
488
+ const prot = await tryPublishCard("protection", () => putProtectionCard(agentId, JSON.stringify(buildCodeProtectionCard(agentId, modes.protection)), "application/json"));
379
489
  if (align.ok && prot.ok) {
380
- say(fmt.success("mnemom agent: default posture published (alignment + protection off until you turn them on; goal contracts nudge)."));
490
+ say(fmt.success(`mnemom agent: cards published (${describeModes(modes)}; goal contracts nudge).`));
381
491
  return;
382
492
  }
383
493
  if (align.ok)
384
- say(fmt.success("mnemom agent: alignment card published (off)."));
494
+ say(fmt.success(`mnemom agent: alignment card published (integrity ${modes.integrity}).`));
385
495
  for (const c of [align, prot]) {
386
496
  if (c.ok)
387
497
  continue;
@@ -392,7 +502,7 @@ async function publishDefaultCards(agentId) {
392
502
  say(fmt.dim(` The agent is claimed and running; finish it later with \`${publishCmd}\`.`));
393
503
  }
394
504
  }
395
- /** `mnemom agent setup [--agent <slug>]` — the explicit provisioning entry point. */
505
+ /** `mnemom agent setup [--agent <slug>] [--governance …]` — the explicit provisioning entry point. */
396
506
  export async function agentSetupCommand(opts = {}) {
397
507
  const gatewayHost = resolveGatewayHost(process.env);
398
508
  const cellError = singleCellError({
@@ -427,6 +537,7 @@ export async function agentSetupCommand(opts = {}) {
427
537
  orgId: account.orgId,
428
538
  orgLabel: account.orgLabel,
429
539
  setup: true,
540
+ governance: opts.governance,
430
541
  });
431
542
  }
432
543
  function persistAgentSlug(slug) {
@@ -603,12 +714,15 @@ export async function resolvePreflightedAccount() {
603
714
  return { orgId: active?.org_id ?? entitlement?.org_id ?? "", orgLabel, entitlement };
604
715
  }
605
716
  // ── The default coding posture (cards off; goal contracts nudge) ─────────────
606
- function buildCodeAlignmentCard(agentId) {
717
+ export function buildCodeAlignmentCard(agentId, modes = { autonomy: "off", integrity: "off" }) {
607
718
  return {
608
719
  card_version: "unified/2026-04-26",
609
720
  agent_id: agentId,
610
- autonomy_mode: "off",
611
- integrity_mode: "off",
721
+ // Integrity off ⇒ the gateway skips the AIP checkpoint and the observer skips
722
+ // its trace analysis: no model call, no charge. A new agent's autonomy
723
+ // (verification + policy) follows it; a refresh keeps the agent's own.
724
+ autonomy_mode: modes.autonomy,
725
+ integrity_mode: modes.integrity,
612
726
  principal: { type: "agent", identifier: agentId, relationship: "delegated_authority" },
613
727
  values: {
614
728
  declared: ["transparency", "safety", "honesty", "user_control", "harm_prevention"],
@@ -641,11 +755,11 @@ function buildCodeAlignmentCard(agentId) {
641
755
  audit: { retention_days: 30, queryable: false, trace_format: "otel" },
642
756
  };
643
757
  }
644
- function buildCodeProtectionCard(agentId) {
758
+ export function buildCodeProtectionCard(agentId, mode = "off") {
645
759
  return {
646
760
  card_version: "protection/2026-04-26",
647
761
  agent_id: agentId,
648
- mode: "off",
762
+ mode,
649
763
  thresholds: { warn: 0.6, quarantine: 0.8, block: 0.95 },
650
764
  screen_surfaces: {
651
765
  incoming: true,
@@ -29,6 +29,7 @@ import { type MnemomKeyDeps } from "../lib/subscription-login.js";
29
29
  import { type SessionRecord } from "../lib/agent-ledger.js";
30
30
  import { type ExperimentalId } from "../lib/experimental.js";
31
31
  import { type ConsoleKeyDeps } from "../lib/console-key.js";
32
+ import { type GovernanceOptIn } from "./agent-setup.js";
32
33
  import { type ContextProfile, type LaunchShape, type SealedContractEnvelope } from "../lib/agent.js";
33
34
  export interface AgentOptions {
34
35
  goal?: string;
@@ -102,6 +103,11 @@ export interface AgentOptions {
102
103
  * default cards, false (`--no-setup`) skips the card writes.
103
104
  */
104
105
  setup?: boolean;
106
+ /**
107
+ * `--governance` / `--no-governance`: the governance modes a provisioning (or
108
+ * `--setup` refresh) writes. undefined = off for a new agent, kept on a refresh.
109
+ */
110
+ governance?: GovernanceOptIn;
105
111
  /**
106
112
  * Where to source the Anthropic key (from ~/.mnemom/launcher.toml `key_source`).
107
113
  * Restricts the default env → store → prompt search: "env" uses env only,
@@ -339,6 +345,11 @@ export declare function logLaunch(ctx: {
339
345
  goalMode: string | null;
340
346
  cli: ResolvedCli;
341
347
  }): void;
348
+ /**
349
+ * The agent a signed-out `--dry-run` shows when the caller named none: the real name is
350
+ * `agent-<handle>` from the Mnemom login, which a dry run (no sign-in) cannot know yet.
351
+ */
352
+ export declare const DRY_RUN_SIGNED_OUT_AGENT = "agent-<your Mnemom login handle>";
342
353
  export declare function printPlan(ctx: {
343
354
  scenario: string;
344
355
  shape: LaunchShape;
@@ -36,6 +36,7 @@ import { getApiUrl, getGatewayUrl, MNEMOM_DIR } from "../lib/config.js";
36
36
  import { createApiKey } from "../lib/api.js";
37
37
  import { MNEMOM_KEY_STORE_FIELD, resolveAgentAuth, resolveSubscriptionMnemomKey, subscriptionChildEnv, subscriptionShape, } from "../lib/subscription-login.js";
38
38
  import { LEDGER_FILE, appendSessionRecord, findResumeCandidate, readSessionRecords, resolveRetentionDays, resumedContextProfile, toLedgerContract, } from "../lib/agent-ledger.js";
39
+ import { isLoggedIn } from "../lib/auth.js";
39
40
  import { loadCliConfig } from "../lib/cli-config.js";
40
41
  import { configKeySpec, loadLauncherConfig, writeLauncherConfig } from "../lib/launcher-config.js";
41
42
  import { experimentalNotice } from "../lib/experimental.js";
@@ -261,20 +262,26 @@ export async function agentCommand(scenarioArg, options = {}) {
261
262
  // which we only know AFTER sign-in. The entitlement-aware decision
262
263
  // (`resolveRemoteControlShape`) happens below, right after
263
264
  // `ensureSessionAndBalance`. --dry-run prints the plan BEFORE sign-in, so it
264
- // can't know the entitlement; it previews the shape the MACHINE is capable of
265
- // (as the pre-gate behaviour did), then notes that the real launch applies the
266
- // entitlement gate. `shape` itself stays the requested (ungated) shape.
265
+ // can't know the entitlement: it previews the REQUESTED shape (terminal unless
266
+ // --remote-control/--server or `launch` asked otherwise) and never the RC
267
+ // auto-upgrade, which only an entitled (internal) account gets. An explicit RC
268
+ // request gets the note that the real launch still applies the entitlement gate.
267
269
  if (options.dryRun) {
268
270
  const planCli = cli ?? {
269
271
  bin: "(not found — pass --cli <path> or `mnemom agent config set cli <path>`)",
270
272
  name: basename((options.cli || env.MNEMOM_AGENT_CLI || "claude").trim()),
271
273
  };
272
- const previewShape = !shapeExplicit && shape === "terminal" && cli && checkRcCapability(cli).ok
273
- ? "remote-control"
274
- : shape;
274
+ // A defaulted slug is `agent-<handle>` from the login (resolved for real after
275
+ // sign-in, below). Signed in: show that name. Signed out, or no usable handle:
276
+ // say where it comes from rather than print the "agent-user" fallback as if it were it.
277
+ if (!options.agent?.trim()) {
278
+ agent =
279
+ ((await isLoggedIn()) && sanitizeAgentName(await resolveDefaultSlug())) ||
280
+ DRY_RUN_SIGNED_OUT_AGENT;
281
+ }
275
282
  printPlan({
276
283
  scenario,
277
- shape: previewShape,
284
+ shape,
278
285
  agent,
279
286
  // The org is only known after sign-in, so a dry run never offers a resume (IG5 #1).
280
287
  conversationId: resolveConversationId(options.conversationId),
@@ -298,8 +305,8 @@ export async function agentCommand(scenarioArg, options = {}) {
298
305
  }
299
306
  if (expNotice)
300
307
  say(expNotice);
301
- if (previewShape === "remote-control") {
302
- 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.");
308
+ if (shape !== "terminal") {
309
+ say("mnemom agent: the real launch uses this shape only if your account is entitled to it, else terminal-only.");
303
310
  }
304
311
  sayContextProfile(contextProfile);
305
312
  sayCompaction(contextProfile, compactionIntercept, compactAt, launchWindow(windowModelFor(options, cli)));
@@ -314,14 +321,6 @@ export async function agentCommand(scenarioArg, options = {}) {
314
321
  if (!cli) {
315
322
  cli = await promptForCliPath(options.cli, env, cliError);
316
323
  }
317
- // ── resolve the Anthropic key (zero 1Password; never logged) ──
318
- // Not on a subscription launch: Claude Code then sends its own claude.ai login instead.
319
- // A Console launch uses the key Claude Code's Console login created, then runs as api-key.
320
- const anthropicKey = auth === "subscription"
321
- ? undefined
322
- : auth === "console"
323
- ? requireConsoleKey(defaultConsoleKeyDeps(env))
324
- : await resolveAnthropicKey(env, { allowPrompt: true, keySource: options.keySource });
325
324
  // ── account + µ-balance gate (no free tier) ─────────────────────────────────
326
325
  // mnemom agent runs governed and on µ, so before we seal a contract or launch we
327
326
  // (1) ensure a Mnemom session — offering the try-me-style sign-in / sign-up when
@@ -342,6 +341,16 @@ export async function agentCommand(scenarioArg, options = {}) {
342
341
  process.exit(1); // guidance already printed
343
342
  throw err;
344
343
  }
344
+ // ── resolve the Anthropic key (zero 1Password; never logged) ──
345
+ // AFTER the account gate above: an account Mnemom Agent is not enabled for learns that
346
+ // (and how to get access) before it is asked for, or refused for want of, a key.
347
+ // Not on a subscription launch: Claude Code then sends its own claude.ai login instead.
348
+ // A Console launch uses the key Claude Code's Console login created, then runs as api-key.
349
+ const anthropicKey = auth === "subscription"
350
+ ? undefined
351
+ : auth === "console"
352
+ ? requireConsoleKey(defaultConsoleKeyDeps(env))
353
+ : await resolveAnthropicKey(env, { allowPrompt: true, keySource: options.keySource });
345
354
  // ── Remote-Control entitlement gate ─────────────────────────────────────────
346
355
  // Remote Control (a session steerable from claude.ai / the Claude app) is
347
356
  // internal-only. Now that we know the org's entitlement, resolve the final
@@ -402,6 +411,7 @@ export async function agentCommand(scenarioArg, options = {}) {
402
411
  orgId: account.orgId,
403
412
  orgLabel: account.orgLabel,
404
413
  setup: options.setup,
414
+ governance: options.governance,
405
415
  });
406
416
  // ── conversation id ── an implicit relaunch continues its earlier conversation (IG5 #1, D1)
407
417
  // so the goal the gateway inferred there is still the one it steers by. Resolved here, after
@@ -1584,6 +1594,11 @@ export function logLaunch(ctx) {
1584
1594
  say(`mnemom agent: goal mode: ${ctx.goalMode} (inferred from your messages; watch x-mnemom-goal-revision / x-mnemom-goal-steer on each response)`);
1585
1595
  }
1586
1596
  }
1597
+ /**
1598
+ * The agent a signed-out `--dry-run` shows when the caller named none: the real name is
1599
+ * `agent-<handle>` from the Mnemom login, which a dry run (no sign-in) cannot know yet.
1600
+ */
1601
+ export const DRY_RUN_SIGNED_OUT_AGENT = "agent-<your Mnemom login handle>";
1587
1602
  export function printPlan(ctx) {
1588
1603
  const L = [];
1589
1604
  L.push("mnemom agent — launch plan (dry run)");
@@ -16,3 +16,5 @@ export declare function experimentalEnableCommand(id: string | undefined, deps?:
16
16
  export declare function experimentalDisableCommand(id: string | undefined, deps?: ExperimentalDeps): void;
17
17
  /** The one-time migration (lib/experimental.ts migrateExperimental) against the real files. */
18
18
  export declare function runExperimentalMigration(): void;
19
+ /** Does `dir` exist with anything in it? Unreadable counts as yes (an upgrader keeps the notice). */
20
+ export declare function hasPriorState(dir: string): boolean;
@@ -2,8 +2,10 @@
2
2
  * `mnemom experimental list [--json] | enable <id> | disable <id>` — turn experimental
3
3
  * features on and off (registry: lib/experimental.ts; stored in ~/.mnemom/config.json).
4
4
  */
5
+ import { existsSync, readdirSync } from "node:fs";
5
6
  import { EXPERIMENTAL_CONTRACT, EXPERIMENTAL_ENV, EXPERIMENTAL_FEATURES, EXPERIMENTAL_IDS, isExperimentalId, migrateExperimental, resolveExperimental, } from "../lib/experimental.js";
6
7
  import { loadCliConfig, setExperimentalEnabled } from "../lib/cli-config.js";
8
+ import { MNEMOM_DIR } from "../lib/config.js";
7
9
  import { launcherEntries, loadLauncherConfig } from "../lib/launcher-config.js";
8
10
  function defaultDeps() {
9
11
  return {
@@ -74,5 +76,15 @@ export function runExperimentalMigration() {
74
76
  launcherEntries: () => launcherEntries(loadLauncherConfig()),
75
77
  persist: (ids) => setExperimentalEnabled(ids),
76
78
  writeErr: (line) => void process.stderr.write(line),
79
+ priorInstall: () => hasPriorState(MNEMOM_DIR),
77
80
  });
78
81
  }
82
+ /** Does `dir` exist with anything in it? Unreadable counts as yes (an upgrader keeps the notice). */
83
+ export function hasPriorState(dir) {
84
+ try {
85
+ return existsSync(dir) && readdirSync(dir).length > 0;
86
+ }
87
+ catch {
88
+ return true;
89
+ }
90
+ }
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ import { agentCommand } from "./commands/agent.js";
26
26
  import { agentConfigCommand } from "./commands/agent-config.js";
27
27
  import { agentSlackCommand } from "./commands/agent-slack.js";
28
28
  import { agentDoctorCommand } from "./commands/agent-doctor.js";
29
- import { agentSetupCommand } from "./commands/agent-setup.js";
29
+ import { agentSetupCommand, parseGovernanceFlag } from "./commands/agent-setup.js";
30
30
  import { agentInviteCommand, runAgentInvocation } from "./commands/agent-invite.js";
31
31
  import { agentFirstRunCommand, offerFirstRunOnLaunch } from "./commands/agent-first-run.js";
32
32
  import { applyConfigToEnv, launcherEntries, loadLauncherConfig, withoutDisabledExperimental, } from "./lib/launcher-config.js";
@@ -155,8 +155,11 @@ const agentCmd = program
155
155
  .option("--conversation-id <id>", "x-mnemom-conversation-id (default: auto-generated)")
156
156
  .option("--cli <name|path>", "Coding-agent CLI to launch: a name or full path (default: claude)")
157
157
  .option("--gateway <url>", "Gateway host override — must be the same cell as the rest of the CLI (default: that cell's gateway; or MNEMOM_AGENT_GATEWAY)")
158
- .option("--remote-control", "Interactive Remote Control session in THIS terminal (behind a local proxy)")
159
- .option("--server", "Headless `claude remote-control` dispatcher (behind a local proxy)")
158
+ // Remote Control is internal-only (the launch gates it on the org's entitlement and runs a
159
+ // customer terminal-only), so its two shapes stay out of --help for everyone. The help is
160
+ // built at load, before any sign-in, so it cannot ask the entitlement; the flags still parse.
161
+ .addOption(new Option("--remote-control", "Interactive Remote Control session in THIS terminal (behind a local proxy)").hideHelp())
162
+ .addOption(new Option("--server", "Headless `claude remote-control` dispatcher (behind a local proxy)").hideHelp())
160
163
  .option("--auth <mode>", "How Claude Code authenticates: api-key (default) | subscription (your Claude Team/Enterprise login) | console (the API key your Claude Code Anthropic Console login created)")
161
164
  .option("--model <alias|id>", "Model to launch (CLI alias or concrete id)")
162
165
  .addOption(new Option("--effort <level>", "Reasoning effort").choices([
@@ -170,6 +173,8 @@ const agentCmd = program
170
173
  .option("--invite <token>", "Redeem a Mnemom Agent invitation (pinv_…): unlocks Mnemom Agent for you + credits your starter µ; launches too when a session name is given")
171
174
  .option("--setup", "Re-check the served agent and refresh its default cards before launch")
172
175
  .option("--no-setup", "Skip the default-card writes (the agent is still confirmed and claimed)")
176
+ .option("--governance [features]", "Turn governance ON for the agent's cards: AIP integrity analysis + Safe House screening, observe-only (default: off for new agents). Pick features with a comma list: integrity, safe-house. Billed at cost on top of the agent fee")
177
+ .option("--no-governance", "Turn governance OFF for the agent's cards (a card refresh otherwise keeps an existing agent's modes)")
173
178
  .option("--no-yolo", "Do NOT pass --dangerously-skip-permissions (yolo is on by default)")
174
179
  .option("--no-context-hint", "Do NOT seed Claude Code's context-hint feature flag before launch")
175
180
  .addOption(new Option("--slack", "EXPERIMENTAL (`mnemom experimental enable slack`): attach the Slack channel — messages addressed to this session arrive in it, and it can reply (see `mnemom agent slack`)").hideHelp(hiddenUnless("slack")))
@@ -299,13 +304,15 @@ const agentCmd = program
299
304
  return;
300
305
  }
301
306
  if (scenario === "doctor") {
302
- await agentDoctorCommand({ cli: opts.cli });
307
+ await agentDoctorCommand({ cli: opts.cli, agent: program.opts().agent ?? cfg.agent });
303
308
  return;
304
309
  }
310
+ // Validated up front so a typo fails before any sign-in or provisioning.
311
+ const governance = parseGovernanceFlag(opts.governance);
305
312
  // Sub-verb: `mnemom agent setup` provisions/claims the governed agent and
306
313
  // never launches. The agent slug follows the same precedence as a launch.
307
314
  if (scenario === "setup") {
308
- await agentSetupCommand({ agent: program.opts().agent ?? cfg.agent });
315
+ await agentSetupCommand({ agent: program.opts().agent ?? cfg.agent, governance });
309
316
  return;
310
317
  }
311
318
  const numToStr = (n) => n === undefined ? undefined : String(n);
@@ -367,6 +374,7 @@ const agentCmd = program
367
374
  : false,
368
375
  experimental,
369
376
  setup: opts.setup,
377
+ governance,
370
378
  keySource: cfg.keySource,
371
379
  passthrough,
372
380
  dryRun: opts.dryRun,
package/dist/lib/api.d.ts CHANGED
@@ -630,6 +630,27 @@ export declare const PROTECTION_CARD_MAX_BYTES: number;
630
630
  export declare function putProtectionCard(agentId: string, body: string, contentType?: "text/yaml" | "application/json", opts?: {
631
631
  idempotencyKey?: string;
632
632
  }): Promise<PublishedCardResponse>;
633
+ /**
634
+ * The per-layer view of an agent's card (ADR-053 `?include=sources`): the raw
635
+ * card the agent layer itself carries, next to the composed card every layer
636
+ * produced. We type the two rows we read; the platform/org/teams rows ride along.
637
+ */
638
+ export interface CardSources {
639
+ agent: {
640
+ card_json: Record<string, unknown> | null;
641
+ available: boolean;
642
+ };
643
+ composed: {
644
+ card_json: Record<string, unknown> | null;
645
+ available: boolean;
646
+ };
647
+ [key: string]: unknown;
648
+ }
649
+ /**
650
+ * Fetch an agent's alignment or protection card sources envelope. Returns null
651
+ * when the agent has no canonical card yet (404).
652
+ */
653
+ export declare function getCardSources(kind: "alignment" | "protection", agentId: string): Promise<CardSources | null>;
633
654
  export interface CardPreviewResult {
634
655
  /** true ⇔ the server accepted+composed the card (HTTP 200). */
635
656
  valid: boolean;
package/dist/lib/api.js CHANGED
@@ -841,6 +841,22 @@ export async function putProtectionCard(agentId, body, contentType = "text/yaml"
841
841
  }
842
842
  return response.json();
843
843
  }
844
+ /**
845
+ * Fetch an agent's alignment or protection card sources envelope. Returns null
846
+ * when the agent has no canonical card yet (404).
847
+ */
848
+ export async function getCardSources(kind, agentId) {
849
+ const url = validateUrl(`${API_BASE}/v1/${kind}/agent/${encodeURIComponent(agentId)}?include=sources`);
850
+ const response = await mnemomFetch(url, {
851
+ headers: { Accept: "application/json", ...(await authHeaders()) },
852
+ });
853
+ if (!response.ok) {
854
+ if (response.status === 404)
855
+ return null;
856
+ throw await readApiError(response, `Failed to fetch ${kind} card sources`);
857
+ }
858
+ return response.json();
859
+ }
844
860
  /**
845
861
  * Dry-run validate + compose a card body against the server's authoritative
846
862
  * pipeline. Returns a structured result — a 422/400 validation failure is an
@@ -98,6 +98,11 @@ export interface MigrationDeps {
98
98
  launcherEntries(): Array<[string, string]>;
99
99
  persist(ids: ExperimentalId[]): void;
100
100
  writeErr(line: string): void;
101
+ /**
102
+ * Did this machine run the CLI before this invocation (~/.mnemom already held something)?
103
+ * A brand-new install never had implicit on, so it gets no "it used to be on" notice.
104
+ */
105
+ priorInstall(): boolean;
101
106
  }
102
107
  /** One-time notice for users who had implicit goal alignment by default (no goal_mode set). */
103
108
  export declare const IMPLICIT_NOW_OFF_NOTICE: string;
@@ -213,6 +213,7 @@ export function migrateExperimental(deps) {
213
213
  try {
214
214
  if (deps.stored() !== undefined)
215
215
  return null;
216
+ const upgrading = deps.priorInstall();
216
217
  const entries = deps.launcherEntries();
217
218
  const ids = featuresInUse(entries);
218
219
  deps.persist(ids);
@@ -220,9 +221,10 @@ export function migrateExperimental(deps) {
220
221
  deps.writeErr(`mnemom: ${ids.join(", ")} ${ids.length === 1 ? "is" : "are"} now experimental and kept on because your launcher.toml uses ${ids.length === 1 ? "it" : "them"} — see \`mnemom experimental list\`.\n`);
221
222
  }
222
223
  // Implicit was the unconfigured DEFAULT, so most users relied on it without a
223
- // goal_mode entry. Don't let it switch off silently for them: say so once.
224
+ // goal_mode entry. Don't let it switch off silently for them: say so once. A fresh
225
+ // install had no default to lose, so it is told nothing.
224
226
  const optedOut = entries.some(([k, v]) => k === "goal_mode" && v === "off");
225
- if (!ids.includes("implicit") && !optedOut)
227
+ if (upgrading && !ids.includes("implicit") && !optedOut)
226
228
  deps.writeErr(IMPLICIT_NOW_OFF_NOTICE);
227
229
  return ids;
228
230
  }
@@ -135,7 +135,7 @@ export const CONFIG_KEYS = [
135
135
  },
136
136
  {
137
137
  key: "launch",
138
- describe: `Launch shape (${LAUNCH_SHAPES.join("|")})`,
138
+ describe: "Launch shape (terminal)",
139
139
  get: (c) => c.launch,
140
140
  set: (c, v) => {
141
141
  c.launch = coerceLaunch(v);
@@ -74,8 +74,7 @@ export function enforceMinCliVersion(response, exit = process.exit, write = (chu
74
74
  write(`\n✓ Updated the Mnemom CLI (was v${CLI_VERSION}; server minimum v${floor}). Re-run your command.\n\n`);
75
75
  exit(1);
76
76
  }
77
- write(`\n✖ Your Mnemom CLI (v${CLI_VERSION}) is too old for this server (minimum v${floor}).\n` +
78
- ` Old CLIs can silently drop newer request fields — e.g. \`--org\` on \`mnemom agent claim\`.\n\n` +
77
+ write(`\n✖ Your Mnemom CLI (v${CLI_VERSION}) is too old for this server (minimum v${floor}).\n\n` +
79
78
  ` Upgrade: npm i -g @mnemom/mnemom@latest\n\n`);
80
79
  exit(1);
81
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mnemom/mnemom",
3
- "version": "0.17.11-next.0",
3
+ "version": "0.17.11",
4
4
  "description": "Transparent AI agent tracing",
5
5
  "type": "module",
6
6
  "bin": {