@mnemom/mnemom 0.17.11-next.0 → 0.17.11-next.1
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/dist/commands/agent-doctor.d.ts +17 -1
- package/dist/commands/agent-doctor.js +78 -2
- package/dist/commands/agent-setup.d.ts +50 -1
- package/dist/commands/agent-setup.js +133 -19
- package/dist/commands/agent.d.ts +6 -0
- package/dist/commands/agent.js +1 -0
- package/dist/index.js +8 -3
- package/dist/lib/api.d.ts +21 -0
- package/dist/lib/api.js +16 -0
- package/package.json +1 -1
|
@@ -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),
|
|
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>;
|
|
@@ -29,4 +32,17 @@ export interface CodeAccessProbe {
|
|
|
29
32
|
* anything inconclusive → info, never a MISS (the launch preamble fails open too).
|
|
30
33
|
*/
|
|
31
34
|
export declare function probeCodeAccess(): Promise<CodeAccessProbe>;
|
|
35
|
+
export interface GovernanceProbe {
|
|
36
|
+
mark: Mark;
|
|
37
|
+
text: string;
|
|
38
|
+
hint?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The governance line: the composed (every layer, strictest wins) integrity and
|
|
42
|
+
* Safe House modes of the agent a launch would use. Signed out, no such agent,
|
|
43
|
+
* or an unreadable card → info with the off-by-default note, never a MISS
|
|
44
|
+
* (governance never blocks a launch). On → info naming the per-turn cost; the
|
|
45
|
+
* agent's own card says off but a stricter layer keeps it on → warn.
|
|
46
|
+
*/
|
|
47
|
+
export declare function probeGovernance(agent: string | undefined): Promise<GovernanceProbe>;
|
|
32
48
|
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),
|
|
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
26
|
import { isCodeNotEnabled, sourceLabel } 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. */
|
|
@@ -115,6 +117,9 @@ export async function agentDoctorCommand(options = {}) {
|
|
|
115
117
|
// ── Mnemom Agent access (invited cohort gate) ──
|
|
116
118
|
const gate = await probeCodeAccess();
|
|
117
119
|
add(gate.mark, gate.text, gate.hint);
|
|
120
|
+
// ── governance (off by default for new agents) ──
|
|
121
|
+
const gov = await probeGovernance(options.agent);
|
|
122
|
+
add(gov.mark, gov.text, gov.hint);
|
|
118
123
|
// ── CLI auto-update ──
|
|
119
124
|
const upd = await probeUpdateStatus();
|
|
120
125
|
add(upd.mark, upd.text, upd.hint);
|
|
@@ -217,3 +222,74 @@ export async function probeCodeAccess() {
|
|
|
217
222
|
return { mark: "info", text: `Mnemom Agent access: could not determine (${detail})` };
|
|
218
223
|
}
|
|
219
224
|
}
|
|
225
|
+
const OPT_IN_HINT = GOVERNANCE_OPT_IN_HINT.trim();
|
|
226
|
+
/**
|
|
227
|
+
* The governance line: the composed (every layer, strictest wins) integrity and
|
|
228
|
+
* Safe House modes of the agent a launch would use. Signed out, no such agent,
|
|
229
|
+
* or an unreadable card → info with the off-by-default note, never a MISS
|
|
230
|
+
* (governance never blocks a launch). On → info naming the per-turn cost; the
|
|
231
|
+
* agent's own card says off but a stricter layer keeps it on → warn.
|
|
232
|
+
*/
|
|
233
|
+
export async function probeGovernance(agent) {
|
|
234
|
+
const offByDefault = {
|
|
235
|
+
mark: "info",
|
|
236
|
+
text: "governance: off by default for new agents (AIP integrity analysis, Safe House screening)",
|
|
237
|
+
hint: `${OPT_IN_HINT} Agents set up before this default keep their cards.`,
|
|
238
|
+
};
|
|
239
|
+
if (!(await isLoggedIn()))
|
|
240
|
+
return offByDefault;
|
|
241
|
+
try {
|
|
242
|
+
const slug = agent?.trim() || (await resolveDefaultSlug());
|
|
243
|
+
const found = await getAgentByName(slug);
|
|
244
|
+
if (!found)
|
|
245
|
+
return offByDefault;
|
|
246
|
+
const [align, prot] = await Promise.all([
|
|
247
|
+
getCardSources("alignment", found.id),
|
|
248
|
+
getCardSources("protection", found.id),
|
|
249
|
+
]);
|
|
250
|
+
const read = (s, layer, field) => {
|
|
251
|
+
const v = s?.[layer]?.card_json?.[field];
|
|
252
|
+
return typeof v === "string" && v ? v : undefined;
|
|
253
|
+
};
|
|
254
|
+
// A composed card without the field (or none composed yet) is unknown, not
|
|
255
|
+
// off: the data plane falls back to its own default (observe for integrity),
|
|
256
|
+
// so the doctor never claims governance is off when it can't see it.
|
|
257
|
+
const composed = {
|
|
258
|
+
integrity: read(align, "composed", "integrity_mode") ?? "unknown",
|
|
259
|
+
protection: read(prot, "composed", "mode") ?? "unknown",
|
|
260
|
+
};
|
|
261
|
+
const own = {
|
|
262
|
+
integrity: read(align, "agent", "integrity_mode"),
|
|
263
|
+
protection: read(prot, "agent", "mode"),
|
|
264
|
+
};
|
|
265
|
+
const text = `governance: ${slug} — ${describeModes(composed)}`;
|
|
266
|
+
const stricter = (mine, eff) => mine === "off" && eff !== "off" && eff !== "unknown";
|
|
267
|
+
const overridden = stricter(own.integrity, composed.integrity) || stricter(own.protection, composed.protection);
|
|
268
|
+
if (overridden) {
|
|
269
|
+
return {
|
|
270
|
+
mark: "warn",
|
|
271
|
+
text,
|
|
272
|
+
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",
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
if (composed.integrity === "off" && composed.protection === "off") {
|
|
276
|
+
return { mark: "ok", text, hint: OPT_IN_HINT };
|
|
277
|
+
}
|
|
278
|
+
if (composed.integrity === "unknown" || composed.protection === "unknown") {
|
|
279
|
+
return {
|
|
280
|
+
mark: "info",
|
|
281
|
+
text,
|
|
282
|
+
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)",
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
mark: "info",
|
|
287
|
+
text,
|
|
288
|
+
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",
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
293
|
+
return { ...offByDefault, text: `governance: could not read the agent's cards (${detail})` };
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -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
|
|
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)
|
|
233
|
-
// the agent is claimed and governed on whatever landed; we warn how to
|
|
234
|
-
|
|
235
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
611
|
-
|
|
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
|
|
762
|
+
mode,
|
|
649
763
|
thresholds: { warn: 0.6, quarantine: 0.8, block: 0.95 },
|
|
650
764
|
screen_surfaces: {
|
|
651
765
|
incoming: true,
|
package/dist/commands/agent.d.ts
CHANGED
|
@@ -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,
|
package/dist/commands/agent.js
CHANGED
|
@@ -402,6 +402,7 @@ export async function agentCommand(scenarioArg, options = {}) {
|
|
|
402
402
|
orgId: account.orgId,
|
|
403
403
|
orgLabel: account.orgLabel,
|
|
404
404
|
setup: options.setup,
|
|
405
|
+
governance: options.governance,
|
|
405
406
|
});
|
|
406
407
|
// ── conversation id ── an implicit relaunch continues its earlier conversation (IG5 #1, D1)
|
|
407
408
|
// so the goal the gateway inferred there is still the one it steers by. Resolved here, after
|
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";
|
|
@@ -170,6 +170,8 @@ const agentCmd = program
|
|
|
170
170
|
.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
171
|
.option("--setup", "Re-check the served agent and refresh its default cards before launch")
|
|
172
172
|
.option("--no-setup", "Skip the default-card writes (the agent is still confirmed and claimed)")
|
|
173
|
+
.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")
|
|
174
|
+
.option("--no-governance", "Turn governance OFF for the agent's cards (a card refresh otherwise keeps an existing agent's modes)")
|
|
173
175
|
.option("--no-yolo", "Do NOT pass --dangerously-skip-permissions (yolo is on by default)")
|
|
174
176
|
.option("--no-context-hint", "Do NOT seed Claude Code's context-hint feature flag before launch")
|
|
175
177
|
.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 +301,15 @@ const agentCmd = program
|
|
|
299
301
|
return;
|
|
300
302
|
}
|
|
301
303
|
if (scenario === "doctor") {
|
|
302
|
-
await agentDoctorCommand({ cli: opts.cli });
|
|
304
|
+
await agentDoctorCommand({ cli: opts.cli, agent: program.opts().agent ?? cfg.agent });
|
|
303
305
|
return;
|
|
304
306
|
}
|
|
307
|
+
// Validated up front so a typo fails before any sign-in or provisioning.
|
|
308
|
+
const governance = parseGovernanceFlag(opts.governance);
|
|
305
309
|
// Sub-verb: `mnemom agent setup` provisions/claims the governed agent and
|
|
306
310
|
// never launches. The agent slug follows the same precedence as a launch.
|
|
307
311
|
if (scenario === "setup") {
|
|
308
|
-
await agentSetupCommand({ agent: program.opts().agent ?? cfg.agent });
|
|
312
|
+
await agentSetupCommand({ agent: program.opts().agent ?? cfg.agent, governance });
|
|
309
313
|
return;
|
|
310
314
|
}
|
|
311
315
|
const numToStr = (n) => n === undefined ? undefined : String(n);
|
|
@@ -367,6 +371,7 @@ const agentCmd = program
|
|
|
367
371
|
: false,
|
|
368
372
|
experimental,
|
|
369
373
|
setup: opts.setup,
|
|
374
|
+
governance,
|
|
370
375
|
keySource: cfg.keySource,
|
|
371
376
|
passthrough,
|
|
372
377
|
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
|