@mnemom/mnemom 0.17.0-next.6 → 0.17.0-next.8

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.
Files changed (34) hide show
  1. package/README.md +26 -0
  2. package/dist/commands/agent-config.d.ts +29 -0
  3. package/dist/commands/agent-config.js +193 -0
  4. package/dist/commands/{code-doctor.d.ts → agent-doctor.d.ts} +2 -2
  5. package/dist/commands/{code-doctor.js → agent-doctor.js} +27 -22
  6. package/dist/commands/{code-invite.d.ts → agent-invite.d.ts} +18 -18
  7. package/dist/commands/{code-invite.js → agent-invite.js} +30 -30
  8. package/dist/commands/agent-session.d.ts +68 -0
  9. package/dist/commands/agent-session.js +114 -0
  10. package/dist/commands/{code-setup.d.ts → agent-setup.d.ts} +13 -13
  11. package/dist/commands/{code-setup.js → agent-setup.js} +46 -42
  12. package/dist/commands/{code.d.ts → agent.d.ts} +50 -14
  13. package/dist/commands/{code.js → agent.js} +144 -86
  14. package/dist/commands/agents.d.ts +2 -2
  15. package/dist/commands/agents.js +5 -5
  16. package/dist/commands/onboard.js +1 -1
  17. package/dist/commands/wrap.js +1 -1
  18. package/dist/index.js +59 -51
  19. package/dist/lib/{code.d.ts → agent.d.ts} +52 -10
  20. package/dist/lib/{code.js → agent.js} +94 -15
  21. package/dist/lib/api.d.ts +4 -4
  22. package/dist/lib/api.js +9 -9
  23. package/dist/lib/auth.js +21 -1
  24. package/dist/lib/{code-config.d.ts → launcher-config.d.ts} +25 -23
  25. package/dist/lib/{code-config.js → launcher-config.js} +50 -31
  26. package/dist/lib/oauth.d.ts +26 -4
  27. package/dist/lib/oauth.js +98 -29
  28. package/dist/lib/version-gate.js +1 -1
  29. package/dist/rc-proxy.mjs +309 -136
  30. package/package.json +7 -3
  31. package/dist/commands/code-config.d.ts +0 -17
  32. package/dist/commands/code-config.js +0 -147
  33. package/dist/commands/code-session.d.ts +0 -52
  34. package/dist/commands/code-session.js +0 -78
package/dist/index.js CHANGED
@@ -20,12 +20,13 @@ import { apiKeyListCommand, apiKeyCreateCommand, apiKeyRotateCommand, apiKeyRevo
20
20
  import { webhooksListCommand, webhooksGetCommand, webhooksCreateCommand, webhooksUpdateCommand, webhooksDeleteCommand, webhooksRotateSecretCommand, webhooksTriggerCommand, webhooksListDeliveriesCommand, webhooksRedeliverCommand, webhooksReplayCommand, } from "./commands/webhooks.js";
21
21
  import { listenCommand } from "./commands/listen.js";
22
22
  import { tryMeCommand } from "./commands/try-me.js";
23
- import { codeCommand } from "./commands/code.js";
24
- import { codeConfigCommand } from "./commands/code-config.js";
25
- import { codeDoctorCommand } from "./commands/code-doctor.js";
26
- import { codeSetupCommand } from "./commands/code-setup.js";
27
- import { codeInviteCommand, runCodeInvocation } from "./commands/code-invite.js";
28
- import { applyConfigToEnv, loadCodeConfig } from "./lib/code-config.js";
23
+ import { agentCommand } from "./commands/agent.js";
24
+ import { agentConfigCommand } from "./commands/agent-config.js";
25
+ import { agentDoctorCommand } from "./commands/agent-doctor.js";
26
+ import { agentSetupCommand } from "./commands/agent-setup.js";
27
+ import { agentInviteCommand, runAgentInvocation } from "./commands/agent-invite.js";
28
+ import { applyConfigToEnv, loadLauncherConfig } from "./lib/launcher-config.js";
29
+ import { resolveImplicit } from "./lib/agent.js";
29
30
  import { wrapCommand } from "./commands/wrap.js";
30
31
  import { onboardCommand } from "./commands/onboard.js";
31
32
  import { skillsListCommand, skillsDescribeCommand } from "./commands/skills.js";
@@ -82,7 +83,7 @@ program
82
83
  });
83
84
  // ── code — first-class governed coding-agent launcher ────────────────────────
84
85
  //
85
- // `mnemom code [scenario]` launches your coding-agent CLI (Claude Code in v1) with
86
+ // `mnemom agent [session]` launches your coding-agent CLI (Claude Code in v1) with
86
87
  // its dev-time model traffic routed through the Mnemom gateway's /anthropic door
87
88
  // for observability + governance, under a governed agent identity (the
88
89
  // program-level `--agent`, default "code-agent") and an optional per-conversation
@@ -90,7 +91,7 @@ program
90
91
  // (default), --remote-control (interactive RC in this terminal), --server
91
92
  // (headless RC dispatcher) — with yolo on by default. `--cli` selects the
92
93
  // executable (name or full path; default claude). The us-2/prod door is the
93
- // DEFAULT; set MNEMOM_CODE_GATEWAY (or --gateway) to point elsewhere. The
94
+ // DEFAULT; set MNEMOM_AGENT_GATEWAY (or --gateway) to point elsewhere. The
94
95
  // Anthropic key is resolved with zero 1Password dependency (env → ~/.mnemom store
95
96
  // → interactive prompt) inside the launched process and never logged. See
96
97
  // commands/code.ts.
@@ -99,10 +100,10 @@ const collectRepeatable = (value, previous) => {
99
100
  previous.push(value);
100
101
  return previous;
101
102
  };
102
- program
103
- .command("code [scenario] [passthrough...]")
104
- .description("Launch a governed coding agent through the Mnemom gateway (governed identity + optional sealed contract). Plain `mnemom code` asks for a session name and an optional goal. " +
105
- "Sub-verbs: `mnemom code setup` provisions your governed agent; `mnemom code doctor` preflights prerequisites; `mnemom code config …` manages persistent settings (~/.mnemom/code.toml); `mnemom code invite <token>` redeems a Mnemom Code invitation.")
103
+ const agentCmd = program
104
+ .command("agent [session] [passthrough...]")
105
+ .description("Launch your governed agent (Claude Code in v1) through the Mnemom gateway — governed identity + optional sealed goal contract. Plain `mnemom agent` asks for a session name and an optional goal. " +
106
+ "Sub-verbs: `setup` provisions your governed agent; `doctor` preflights prerequisites; `config …` manages persistent settings (~/.mnemom/launcher.toml); `invite <token>` redeems a Mnemom Agent invitation; `list` / `claim` / `move` manage the agents in your orgs.")
106
107
  .option("--goal <statement>", "Contract goal statement (required if any contract flag is given)")
107
108
  .option("--requirement <text>", "Contract requirement (repeatable)", collectRepeatable, [])
108
109
  .option("--allow <glob>", "Allowed path/glob (repeatable)", collectRepeatable, [])
@@ -111,9 +112,11 @@ program
111
112
  .option("--budget <usd>", "Guardrail: spend ceiling in USD (positive number)")
112
113
  .option("--max-turns <n>", "Guardrail: turn ceiling (positive integer)")
113
114
  .option("--stall <n>", "Guardrail: consecutive no-write turns (positive integer)")
115
+ .option("--implicit", "Goal alignment without a sealed contract: the gateway infers the goal from your messages and revises it as you steer (not combinable with --goal/--requirement/--allow/--forbid/--goal-id; the guardrail ceilings still apply)")
116
+ .option("--no-implicit", "Goal alignment off for this launch even when launcher.toml sets goal_mode = implicit")
114
117
  .option("--conversation-id <id>", "x-mnemom-conversation-id (default: auto-generated)")
115
118
  .option("--cli <name|path>", "Coding-agent CLI to launch: a name or full path (default: claude)")
116
- .option("--gateway <url>", "Gateway host override (default: https://gateway.mnemom.ai; or MNEMOM_CODE_GATEWAY)")
119
+ .option("--gateway <url>", "Gateway host override (default: https://gateway.mnemom.ai; or MNEMOM_AGENT_GATEWAY)")
117
120
  .option("--remote-control", "Interactive Remote Control session in THIS terminal (behind a local proxy)")
118
121
  .option("--server", "Headless `claude remote-control` dispatcher (behind a local proxy)")
119
122
  .option("--model <alias|id>", "Model to launch (CLI alias or concrete id)")
@@ -125,7 +128,7 @@ program
125
128
  "max",
126
129
  ]))
127
130
  .option("--trim-mcp", "Launch the CLI with no MCP servers (--strict-mcp-config + empty --mcp-config)")
128
- .option("--invite <token>", "Redeem a Mnemom Code invitation (pinv_…): unlocks Mnemom Code for you + credits your starter µ; launches too when a session name is given")
131
+ .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")
129
132
  .option("--setup", "Provision + claim the governed agent before launch (non-interactive opt-in)")
130
133
  .option("--no-setup", "Skip governed-agent provisioning entirely for this launch")
131
134
  .option("--no-yolo", "Do NOT pass --dangerously-skip-permissions (yolo is on by default)")
@@ -133,58 +136,71 @@ program
133
136
  .option("--dry-run", "Print the resolved launch plan and exit — no key read, no spawn")
134
137
  .action(async (scenario, passthrough, opts) => {
135
138
  try {
136
- // Invitation redemption — `mnemom code invite <token>` (sub-verb) or
137
- // `mnemom code --invite <token> [scenario]` — composes with the launch in
138
- // runCodeInvocation (commands/code-invite.ts, unit-tested); everything below
139
+ // Invitation redemption — `mnemom agent invite <token>` (sub-verb) or
140
+ // `mnemom agent --invite <token> [session]` — composes with the launch in
141
+ // runAgentInvocation (commands/code-invite.ts, unit-tested); everything below
139
142
  // is the launch half, running only when a real scenario was given.
140
- await runCodeInvocation({ scenario, passthrough, inviteFlag: opts.invite }, { redeem: (token) => codeInviteCommand(token), launch: (scenario) => launchCode(scenario) });
143
+ await runAgentInvocation({ scenario, passthrough, inviteFlag: opts.invite }, {
144
+ redeem: (token) => agentInviteCommand(token),
145
+ launch: (scenario) => launchAgent(scenario),
146
+ });
141
147
  }
142
148
  catch (error) {
143
149
  console.error("Error:", error instanceof Error ? error.message : error);
144
150
  process.exit(1);
145
151
  }
146
- async function launchCode(scenario) {
147
- // Sub-verb: `mnemom code config …` manages persistent settings and never
152
+ async function launchAgent(scenario) {
153
+ // Sub-verb: `mnemom agent config …` manages persistent settings and never
148
154
  // launches. (A scenario literally named "config" is reserved for this.)
149
155
  if (scenario === "config") {
150
- await codeConfigCommand(passthrough);
156
+ await agentConfigCommand(passthrough);
151
157
  return;
152
158
  }
153
159
  if (opts.gateway)
154
- process.env.MNEMOM_CODE_GATEWAY = opts.gateway;
155
- // Persistent settings (~/.mnemom/code.toml) sit UNDER env and flags:
156
- // precedence is flag > env > code.toml > built-in. Env-backed settings are
160
+ process.env.MNEMOM_AGENT_GATEWAY = opts.gateway;
161
+ // Persistent settings (~/.mnemom/launcher.toml) sit UNDER env and flags:
162
+ // precedence is flag > env > launcher.toml > built-in. Env-backed settings are
157
163
  // folded into env only when unset (so env/flags still win); the resolvers
158
164
  // in lib/code.ts keep the built-in default as the final fallback.
159
- const cfg = loadCodeConfig();
165
+ const cfg = loadLauncherConfig();
160
166
  applyConfigToEnv(cfg, process.env);
161
- // Sub-verb: `mnemom code doctor` runs a read-only preflight and never
167
+ // Sub-verb: `mnemom agent doctor` runs a read-only preflight and never
162
168
  // launches. Placed after config load so it reports the effective settings.
163
169
  if (scenario === "doctor") {
164
- await codeDoctorCommand({ cli: opts.cli });
170
+ await agentDoctorCommand({ cli: opts.cli });
165
171
  return;
166
172
  }
167
- // Sub-verb: `mnemom code setup` provisions/claims the governed agent and
173
+ // Sub-verb: `mnemom agent setup` provisions/claims the governed agent and
168
174
  // never launches. The agent slug follows the same precedence as a launch.
169
175
  if (scenario === "setup") {
170
- await codeSetupCommand({ agent: program.opts().agent ?? cfg.agent });
176
+ await agentSetupCommand({ agent: program.opts().agent ?? cfg.agent });
171
177
  return;
172
178
  }
173
179
  const numToStr = (n) => n === undefined ? undefined : String(n);
174
- // Config guardrails apply only when a contract is actually intended (a
175
- // --goal is present), so a saved ceiling never forces an unrequested
176
- // contract (which would then demand --goal and fail the launch).
177
- const contractIntended = Boolean(opts.goal && opts.goal.trim());
180
+ // Implicit mode: --implicit / --no-implicit win; a --goal statement seals
181
+ // (the standing config default never overrides it); else launcher.toml
182
+ // `goal_mode`; else off. Pure + unit-tested in lib/agent.ts.
183
+ const implicit = resolveImplicit({
184
+ goal: opts.goal,
185
+ implicit: opts.implicit,
186
+ configGoalMode: cfg.goalMode,
187
+ });
188
+ // Config guardrails apply only when goal alignment is actually intended (a
189
+ // --goal is present, or implicit mode — where they ride on the mode header),
190
+ // so a saved ceiling never forces an unrequested contract (which would
191
+ // then demand --goal and fail the launch).
192
+ const contractIntended = Boolean(opts.goal && opts.goal.trim()) || implicit;
178
193
  // The governed identity rides on the program-level `--agent` (which
179
194
  // shadows a subcommand `--agent` under commander@12 — the MNE-238 class),
180
195
  // exactly as status/logs/onboard read it.
181
196
  const parentOpts = program.opts();
182
- await codeCommand(scenario, {
197
+ await agentCommand(scenario, {
183
198
  goal: opts.goal,
184
199
  requirement: opts.requirement,
185
200
  allow: opts.allow,
186
201
  forbid: opts.forbid,
187
202
  goalId: opts.goalId,
203
+ implicit,
188
204
  budget: opts.budget ?? (contractIntended ? numToStr(cfg.guardrails?.budgetUsd) : undefined),
189
205
  maxTurns: opts.maxTurns ?? (contractIntended ? numToStr(cfg.guardrails?.maxTurns) : undefined),
190
206
  stall: opts.stall ?? (contractIntended ? numToStr(cfg.guardrails?.stallTurns) : undefined),
@@ -625,10 +641,10 @@ policyCmd
625
641
  await policyEvaluateCommand(file, options);
626
642
  });
627
643
  // ============================================================================
628
- // Agent management
644
+ // Agent management — sub-verbs of `mnemom agent` (the launcher above): list / claim / move.
629
645
  // ============================================================================
630
- const agentsCmd = program
631
- .command("agents")
646
+ agentCmd
647
+ .command("list")
632
648
  .description("List agents across the orgs you belong to (ADR-062 org-scoped)")
633
649
  .option("--org <id>", "Scope to a single org (default: all orgs you belong to)")
634
650
  .action(async (opts) => {
@@ -640,7 +656,7 @@ const agentsCmd = program
640
656
  process.exit(1);
641
657
  }
642
658
  });
643
- agentsCmd
659
+ agentCmd
644
660
  .command("claim <id-or-name>")
645
661
  .description("Claim an agent into an org (ADR-062 claim-to-org)")
646
662
  .option("--org <slug>", "Org slug or id to claim into (default: your ACTIVE org from `mnemom org use`, else your personal org)")
@@ -650,18 +666,10 @@ agentsCmd
650
666
  .option("--json", "Output JSON instead of human-readable text")
651
667
  .action(async (idOrName, opts) => {
652
668
  try {
653
- // `agentsCmd` (the parent `agents` command) ALSO defines `--org` (for
654
- // `mnemom agents --org <id>` list-scoping). Commander@12 binds a flag
655
- // shared by a parent and a subcommand to whichever of the two owns it
656
- // and runs first in the parse chain — here that's the parent — so
657
- // `agents claim <id> --org <slug>` had its --org silently swallowed by
658
- // `agentsCmd` before this subcommand's own `--org` option ever saw it
659
- // (MNE-2496: confirmed live, the agent always landed in the caller's
660
- // personal org regardless of --org). Same shadow class as the global
661
- // `--agent` fix at MNE-238 (see the `advisories list`/`show` actions
662
- // below) — resolve the local value first, falling back to the
663
- // parent's captured value so either parse order still works.
664
- const org = opts.org ?? agentsCmd.opts().org;
669
+ // The parent `agent` command defines no `--org`, so the MNE-2496 shadow
670
+ // (a parent-owned flag swallowing the subcommand.s) cannot recur here;
671
+ // `list` owns its own `--org` as a sibling sub-verb.
672
+ const org = opts.org;
665
673
  await agentsClaimCommand(idOrName, {
666
674
  org,
667
675
  key: opts.key,
@@ -675,7 +683,7 @@ agentsCmd
675
683
  process.exit(1);
676
684
  }
677
685
  });
678
- agentsCmd
686
+ agentCmd
679
687
  .command("move <id-or-name>")
680
688
  .description("Move an agent to another org — requires owner/admin in BOTH the current and destination org " +
681
689
  "(role-based; no agent key needed, unlike re-claim)")
@@ -1,8 +1,8 @@
1
1
  /**
2
- * lib/code.ts — the pure, IO-free core of `mnemom code`, the CLI's first-class
2
+ * lib/code.ts — the pure, IO-free core of `mnemom agent`, the CLI's first-class
3
3
  * governed-coding-agent launcher.
4
4
  *
5
- * `mnemom code` launches your coding-agent CLI (Claude Code in v1) with its
5
+ * `mnemom agent` launches your coding-agent CLI (Claude Code in v1) with its
6
6
  * dev-time model traffic routed through the Mnemom gateway for observability and
7
7
  * governance, under a governed agent identity and (optionally) a governed
8
8
  * per-conversation CONTRACT (goal / requirements / path allow-forbid lists +
@@ -16,7 +16,7 @@
16
16
  * key resolution, env assembly, and spawning the coding-agent CLI.
17
17
  *
18
18
  * The us-2/PROD gateway is the DEFAULT door (https://gateway.mnemom.ai). An
19
- * explicit MNEMOM_CODE_GATEWAY still wins (so any other cell is reachable when
19
+ * explicit MNEMOM_AGENT_GATEWAY still wins (so any other cell is reachable when
20
20
  * NAMED), but prod is never selected silently — see `resolveGatewayHost`.
21
21
  */
22
22
  /** The us-2/prod gateway host. The default door — never any other cell silently. */
@@ -48,20 +48,28 @@ export interface ContractFlags {
48
48
  maxTurns?: string | number;
49
49
  budget?: string | number;
50
50
  stall?: string | number;
51
+ /**
52
+ * `--implicit`: goal alignment WITHOUT a sealed contract. No `x-mnemom-contract`
53
+ * is sent; the gateway infers the goal from the user's own messages (revision 1
54
+ * after the first message) and revises it as the user steers. The guardrail
55
+ * ceilings still apply — they ride on the `x-mnemom-goal-mode` header instead
56
+ * of inside a contract. Cannot be combined with the sealed-contract flags.
57
+ */
58
+ implicit?: boolean;
51
59
  }
52
60
  /** The three launch shapes the command supports (see commands/code.ts). */
53
61
  export type LaunchShape = "terminal" | "remote-control" | "server";
54
62
  /**
55
63
  * Resolve the gateway HOST (scheme + host, no path). An explicit, non-empty
56
- * `MNEMOM_CODE_GATEWAY` wins verbatim; otherwise the us-2/prod host. The default
64
+ * `MNEMOM_AGENT_GATEWAY` wins verbatim; otherwise the us-2/prod host. The default
57
65
  * is PROD — an unset var resolves to prod, and any other cell is reached only by
58
66
  * NAMING it. Trailing slash trimmed so door concatenation is clean.
59
67
  */
60
68
  export declare function resolveGatewayHost(env?: NodeJS.ProcessEnv): string;
61
69
  /**
62
70
  * The Anthropic door the CLI points Claude Code at:
63
- * `${MNEMOM_CODE_GATEWAY}/anthropic`, or the full-door override
64
- * `MNEMOM_CODE_ANTHROPIC_GATEWAY` when set. v1 is Anthropic-only (no router door
71
+ * `${MNEMOM_AGENT_GATEWAY}/anthropic`, or the full-door override
72
+ * `MNEMOM_AGENT_ANTHROPIC_GATEWAY` when set. v1 is Anthropic-only (no router door
65
73
  * / Mnemom key), so the /anthropic door carries the whole contract: x-api-key +
66
74
  * x-mnemom-{agent,conversation-id,contract}.
67
75
  */
@@ -116,11 +124,20 @@ export interface SealedContractEnvelope {
116
124
  * here matches whatever the gateway seals for the same flags.
117
125
  */
118
126
  export declare function assembleContract(flags: ContractFlags): SealedContractEnvelope | null;
127
+ /** The request header that opts a conversation into implicit goal mode. */
128
+ export declare const GOAL_MODE_HEADER = "x-mnemom-goal-mode";
119
129
  /**
120
- * Resolve the launch shape from the flags and the `MNEMOM_CODE_REMOTE_CONTROL`
130
+ * The `x-mnemom-goal-mode` header value for `--implicit`, or null when the flag
131
+ * is absent: `implicit[; max_turns=N][; budget_usd=X][; stall_turns=N]` — the
132
+ * guardrail ceilings validated exactly as `assembleContract` validates them (a
133
+ * non-positive ceiling is refused, never silently dropped into an unguarded run).
134
+ */
135
+ export declare function buildGoalModeHeader(flags: ContractFlags): string | null;
136
+ /**
137
+ * Resolve the launch shape from the flags and the `MNEMOM_AGENT_REMOTE_CONTROL`
121
138
  * env var (the flags win). Default is `terminal` — a plain governed launch in
122
139
  * THIS terminal. `--remote-control` → `remote-control` (interactive RC in this
123
- * terminal); `--server` (or `MNEMOM_CODE_REMOTE_CONTROL=server`) → `server` (the
140
+ * terminal); `--server` (or `MNEMOM_AGENT_REMOTE_CONTROL=server`) → `server` (the
124
141
  * headless `claude remote-control` dispatcher). Throws on an invalid env value.
125
142
  */
126
143
  export declare function resolveLaunchShape(opts: {
@@ -129,7 +146,7 @@ export declare function resolveLaunchShape(opts: {
129
146
  }, env?: NodeJS.ProcessEnv): LaunchShape;
130
147
  /**
131
148
  * Did anyone EXPLICITLY choose a launch shape — a `--remote-control`/`--server`
132
- * flag, or a non-empty `MNEMOM_CODE_REMOTE_CONTROL` (which config.launch also
149
+ * flag, or a non-empty `MNEMOM_AGENT_REMOTE_CONTROL` (which config.launch also
133
150
  * sets)? When false, the command is free to AUTO-select the best shape: prefer
134
151
  * Remote-Control-in-terminal when the machine can do it, else terminal-only. An
135
152
  * explicit `terminal` choice (config `launch = "terminal"` → env "0") is
@@ -142,7 +159,8 @@ export declare function launchPreferenceExplicit(opts: {
142
159
  /**
143
160
  * Build the `ANTHROPIC_CUSTOM_HEADERS` value for the terminal-only launch: the
144
161
  * real Anthropic key as `x-api-key`, plus the governed-identity headers and the
145
- * sealed contract when present. Newline-separated, exactly the wire shape Claude
162
+ * sealed contract when present (or the implicit goal-mode header when the launch
163
+ * is `--implicit` — never both). Newline-separated, exactly the wire shape Claude
146
164
  * Code sends to the /anthropic door. NB: the returned string contains the key —
147
165
  * callers must place it only in the child's env, never in a log line.
148
166
  */
@@ -151,4 +169,28 @@ export declare function buildCustomHeaders(opts: {
151
169
  agent: string;
152
170
  conversationId: string;
153
171
  contractB64?: string;
172
+ /** `buildGoalModeHeader` output for an `--implicit` launch. */
173
+ goalMode?: string;
154
174
  }): string;
175
+ /**
176
+ * The persistable goal-alignment default (`launcher.toml` `goal_mode`). Only the
177
+ * two statement-free modes can be a standing default: a SEALED contract needs a
178
+ * per-session statement, so it is always a per-launch `--goal`.
179
+ */
180
+ export type GoalModeDefault = "implicit" | "off";
181
+ /**
182
+ * Is this launch implicit-mode? Precedence, highest wins:
183
+ * 1. an explicit flag — `--implicit` (true) or `--no-implicit` (false);
184
+ * 2. a `--goal` statement — a sealed contract is a per-launch choice that the
185
+ * standing config default must never silently override;
186
+ * 3. the config default `goal_mode = "implicit"`;
187
+ * 4. off.
188
+ * Pure so every arm is unit-tested; the caller passes the resolved value on to
189
+ * `assembleContract` / `buildGoalModeHeader` (which still refuse `--implicit`
190
+ * combined with a contract flag — that conflict is EXPLICIT and is reported).
191
+ */
192
+ export declare function resolveImplicit(opts: {
193
+ goal?: string;
194
+ implicit?: boolean;
195
+ configGoalMode?: GoalModeDefault;
196
+ }): boolean;
@@ -1,8 +1,8 @@
1
1
  /**
2
- * lib/code.ts — the pure, IO-free core of `mnemom code`, the CLI's first-class
2
+ * lib/code.ts — the pure, IO-free core of `mnemom agent`, the CLI's first-class
3
3
  * governed-coding-agent launcher.
4
4
  *
5
- * `mnemom code` launches your coding-agent CLI (Claude Code in v1) with its
5
+ * `mnemom agent` launches your coding-agent CLI (Claude Code in v1) with its
6
6
  * dev-time model traffic routed through the Mnemom gateway for observability and
7
7
  * governance, under a governed agent identity and (optionally) a governed
8
8
  * per-conversation CONTRACT (goal / requirements / path allow-forbid lists +
@@ -16,7 +16,7 @@
16
16
  * key resolution, env assembly, and spawning the coding-agent CLI.
17
17
  *
18
18
  * The us-2/PROD gateway is the DEFAULT door (https://gateway.mnemom.ai). An
19
- * explicit MNEMOM_CODE_GATEWAY still wins (so any other cell is reachable when
19
+ * explicit MNEMOM_AGENT_GATEWAY still wins (so any other cell is reachable when
20
20
  * NAMED), but prod is never selected silently — see `resolveGatewayHost`.
21
21
  */
22
22
  import { createHash, randomUUID } from "node:crypto";
@@ -24,24 +24,24 @@ import { createHash, randomUUID } from "node:crypto";
24
24
  export const PROD_GATEWAY_HOST = "https://gateway.mnemom.ai";
25
25
  /**
26
26
  * Resolve the gateway HOST (scheme + host, no path). An explicit, non-empty
27
- * `MNEMOM_CODE_GATEWAY` wins verbatim; otherwise the us-2/prod host. The default
27
+ * `MNEMOM_AGENT_GATEWAY` wins verbatim; otherwise the us-2/prod host. The default
28
28
  * is PROD — an unset var resolves to prod, and any other cell is reached only by
29
29
  * NAMING it. Trailing slash trimmed so door concatenation is clean.
30
30
  */
31
31
  export function resolveGatewayHost(env = process.env) {
32
- const raw = (env.MNEMOM_CODE_GATEWAY ?? "").trim();
32
+ const raw = (env.MNEMOM_AGENT_GATEWAY ?? "").trim();
33
33
  const host = raw.length > 0 ? raw : PROD_GATEWAY_HOST;
34
34
  return host.replace(/\/+$/, "");
35
35
  }
36
36
  /**
37
37
  * The Anthropic door the CLI points Claude Code at:
38
- * `${MNEMOM_CODE_GATEWAY}/anthropic`, or the full-door override
39
- * `MNEMOM_CODE_ANTHROPIC_GATEWAY` when set. v1 is Anthropic-only (no router door
38
+ * `${MNEMOM_AGENT_GATEWAY}/anthropic`, or the full-door override
39
+ * `MNEMOM_AGENT_ANTHROPIC_GATEWAY` when set. v1 is Anthropic-only (no router door
40
40
  * / Mnemom key), so the /anthropic door carries the whole contract: x-api-key +
41
41
  * x-mnemom-{agent,conversation-id,contract}.
42
42
  */
43
43
  export function resolveAnthropicDoor(env = process.env) {
44
- const override = (env.MNEMOM_CODE_ANTHROPIC_GATEWAY ?? "").trim();
44
+ const override = (env.MNEMOM_AGENT_ANTHROPIC_GATEWAY ?? "").trim();
45
45
  if (override.length > 0)
46
46
  return override.replace(/\/+$/, "");
47
47
  return `${resolveGatewayHost(env)}/anthropic`;
@@ -132,6 +132,12 @@ function hasText(v) {
132
132
  * here matches whatever the gateway seals for the same flags.
133
133
  */
134
134
  export function assembleContract(flags) {
135
+ // Implicit goal mode seals nothing: the ceilings ride on the mode header
136
+ // (`buildGoalModeHeader`), so a `--budget` alone must not demand `--goal` here.
137
+ if (flags.implicit) {
138
+ assertImplicitCompatible(flags);
139
+ return null;
140
+ }
135
141
  if (!contractFlagsPresent(flags))
136
142
  return null;
137
143
  const statement = (flags.goal ?? "").trim();
@@ -178,21 +184,73 @@ export function assembleContract(flags) {
178
184
  sha: sha256Hex16(canonical),
179
185
  };
180
186
  }
187
+ /** The request header that opts a conversation into implicit goal mode. */
188
+ export const GOAL_MODE_HEADER = "x-mnemom-goal-mode";
189
+ const SEALED_ONLY_FLAGS = "--goal/--requirement/--allow/--forbid/--goal-id";
190
+ /**
191
+ * `--implicit` and a sealed contract are mutually exclusive: on the gateway a
192
+ * sealed `x-mnemom-contract` always wins and would silently switch implicit mode
193
+ * off, so the launch refuses the combination rather than ship a header that does
194
+ * nothing.
195
+ */
196
+ function assertImplicitCompatible(flags) {
197
+ const sealed = hasText(flags.goal) ||
198
+ (flags.requirement?.length ?? 0) > 0 ||
199
+ (flags.allow?.length ?? 0) > 0 ||
200
+ (flags.forbid?.length ?? 0) > 0 ||
201
+ hasText(flags.goalId);
202
+ if (sealed) {
203
+ throw new Error(`--implicit cannot be combined with ${SEALED_ONLY_FLAGS}: a sealed contract always wins on ` +
204
+ "the gateway and would silently switch implicit mode off. Drop --implicit to seal a " +
205
+ "contract, or drop the contract flags to let the gateway infer the goal from your messages.");
206
+ }
207
+ }
208
+ /**
209
+ * The `x-mnemom-goal-mode` header value for `--implicit`, or null when the flag
210
+ * is absent: `implicit[; max_turns=N][; budget_usd=X][; stall_turns=N]` — the
211
+ * guardrail ceilings validated exactly as `assembleContract` validates them (a
212
+ * non-positive ceiling is refused, never silently dropped into an unguarded run).
213
+ */
214
+ export function buildGoalModeHeader(flags) {
215
+ if (!flags.implicit)
216
+ return null;
217
+ assertImplicitCompatible(flags);
218
+ const parts = ["implicit"];
219
+ if (flags.maxTurns !== undefined) {
220
+ if (!isPositiveInt(flags.maxTurns)) {
221
+ throw new Error(`--max-turns must be a positive integer (got '${flags.maxTurns}').`);
222
+ }
223
+ parts.push(`max_turns=${Number(flags.maxTurns)}`);
224
+ }
225
+ if (flags.budget !== undefined) {
226
+ if (!isPositiveNumber(flags.budget)) {
227
+ throw new Error(`--budget must be a positive USD amount (got '${flags.budget}').`);
228
+ }
229
+ parts.push(`budget_usd=${Number(flags.budget)}`);
230
+ }
231
+ if (flags.stall !== undefined) {
232
+ if (!isPositiveInt(flags.stall)) {
233
+ throw new Error(`--stall must be a positive integer (got '${flags.stall}').`);
234
+ }
235
+ parts.push(`stall_turns=${Number(flags.stall)}`);
236
+ }
237
+ return parts.join("; ");
238
+ }
181
239
  /** Trim, drop empties — matches the gateway's `stringArray` filtering. */
182
240
  function cleanList(values) {
183
241
  return (values ?? []).map((v) => v.trim()).filter((v) => v.length > 0);
184
242
  }
185
243
  /**
186
- * Resolve the launch shape from the flags and the `MNEMOM_CODE_REMOTE_CONTROL`
244
+ * Resolve the launch shape from the flags and the `MNEMOM_AGENT_REMOTE_CONTROL`
187
245
  * env var (the flags win). Default is `terminal` — a plain governed launch in
188
246
  * THIS terminal. `--remote-control` → `remote-control` (interactive RC in this
189
- * terminal); `--server` (or `MNEMOM_CODE_REMOTE_CONTROL=server`) → `server` (the
247
+ * terminal); `--server` (or `MNEMOM_AGENT_REMOTE_CONTROL=server`) → `server` (the
190
248
  * headless `claude remote-control` dispatcher). Throws on an invalid env value.
191
249
  */
192
250
  export function resolveLaunchShape(opts, env = process.env) {
193
251
  let rc = false;
194
252
  let server = false;
195
- const envRc = (env.MNEMOM_CODE_REMOTE_CONTROL ?? "").trim();
253
+ const envRc = (env.MNEMOM_AGENT_REMOTE_CONTROL ?? "").trim();
196
254
  switch (envRc) {
197
255
  case "":
198
256
  case "0":
@@ -206,7 +264,7 @@ export function resolveLaunchShape(opts, env = process.env) {
206
264
  server = true;
207
265
  break;
208
266
  default:
209
- throw new Error(`MNEMOM_CODE_REMOTE_CONTROL must be 0, 1, terminal or server (got '${envRc}').`);
267
+ throw new Error(`MNEMOM_AGENT_REMOTE_CONTROL must be 0, 1, terminal or server (got '${envRc}').`);
210
268
  }
211
269
  if (opts.remoteControl)
212
270
  rc = true;
@@ -222,7 +280,7 @@ export function resolveLaunchShape(opts, env = process.env) {
222
280
  }
223
281
  /**
224
282
  * Did anyone EXPLICITLY choose a launch shape — a `--remote-control`/`--server`
225
- * flag, or a non-empty `MNEMOM_CODE_REMOTE_CONTROL` (which config.launch also
283
+ * flag, or a non-empty `MNEMOM_AGENT_REMOTE_CONTROL` (which config.launch also
226
284
  * sets)? When false, the command is free to AUTO-select the best shape: prefer
227
285
  * Remote-Control-in-terminal when the machine can do it, else terminal-only. An
228
286
  * explicit `terminal` choice (config `launch = "terminal"` → env "0") is
@@ -231,12 +289,13 @@ export function resolveLaunchShape(opts, env = process.env) {
231
289
  export function launchPreferenceExplicit(opts, env = process.env) {
232
290
  if (opts.remoteControl || opts.server)
233
291
  return true;
234
- return (env.MNEMOM_CODE_REMOTE_CONTROL ?? "").trim() !== "";
292
+ return (env.MNEMOM_AGENT_REMOTE_CONTROL ?? "").trim() !== "";
235
293
  }
236
294
  /**
237
295
  * Build the `ANTHROPIC_CUSTOM_HEADERS` value for the terminal-only launch: the
238
296
  * real Anthropic key as `x-api-key`, plus the governed-identity headers and the
239
- * sealed contract when present. Newline-separated, exactly the wire shape Claude
297
+ * sealed contract when present (or the implicit goal-mode header when the launch
298
+ * is `--implicit` — never both). Newline-separated, exactly the wire shape Claude
240
299
  * Code sends to the /anthropic door. NB: the returned string contains the key —
241
300
  * callers must place it only in the child's env, never in a log line.
242
301
  */
@@ -248,5 +307,25 @@ export function buildCustomHeaders(opts) {
248
307
  ];
249
308
  if (opts.contractB64)
250
309
  lines.push(`x-mnemom-contract: ${opts.contractB64}`);
310
+ if (opts.goalMode)
311
+ lines.push(`${GOAL_MODE_HEADER}: ${opts.goalMode}`);
251
312
  return lines.join("\n");
252
313
  }
314
+ /**
315
+ * Is this launch implicit-mode? Precedence, highest wins:
316
+ * 1. an explicit flag — `--implicit` (true) or `--no-implicit` (false);
317
+ * 2. a `--goal` statement — a sealed contract is a per-launch choice that the
318
+ * standing config default must never silently override;
319
+ * 3. the config default `goal_mode = "implicit"`;
320
+ * 4. off.
321
+ * Pure so every arm is unit-tested; the caller passes the resolved value on to
322
+ * `assembleContract` / `buildGoalModeHeader` (which still refuse `--implicit`
323
+ * combined with a contract flag — that conflict is EXPLICIT and is reported).
324
+ */
325
+ export function resolveImplicit(opts) {
326
+ if (opts.implicit !== undefined)
327
+ return opts.implicit;
328
+ if (hasText(opts.goal))
329
+ return false;
330
+ return opts.configGoalMode === "implicit";
331
+ }
package/dist/lib/api.d.ts CHANGED
@@ -378,7 +378,7 @@ export declare function moveAgent(agentId: string, destOrgId: string, opts?: {
378
378
  */
379
379
  export declare function getMyPersonalOrg(): Promise<PersonalOrgRef>;
380
380
  /** The authenticated user's identity — `GET /v1/auth/me`. Carries the email the
381
- * stored OAuth token does not, so the CLI can derive a real `code-<handle>`. */
381
+ * stored OAuth token does not, so the CLI can derive a real `agent-<handle>`. */
382
382
  export interface MeResponse {
383
383
  user_id: string;
384
384
  email?: string | null;
@@ -1204,7 +1204,7 @@ export interface OrgUsageQuery {
1204
1204
  * the user "org not found" on what is most often just the flag being off.
1205
1205
  */
1206
1206
  export declare function getOrgUsage(orgId: string, opts?: OrgUsageQuery): Promise<OrgUsageResponse>;
1207
- /** `GET /v1/code/entitlement` 200 body — why Mnemom Code is enabled for the org. */
1207
+ /** `GET /v1/code/entitlement` 200 body — why Mnemom Agent is enabled for the org. */
1208
1208
  export interface CodeEntitlement {
1209
1209
  org_id: string;
1210
1210
  enabled: true;
@@ -1213,7 +1213,7 @@ export interface CodeEntitlement {
1213
1213
  /** The machine-readable code on the gate's 403 when the org is not enabled. */
1214
1214
  export declare const CODE_NOT_ENABLED = "code_not_enabled";
1215
1215
  /**
1216
- * Is Mnemom Code enabled for `orgId` (default: the caller's primary org)?
1216
+ * Is Mnemom Agent enabled for `orgId` (default: the caller's primary org)?
1217
1217
  * Resolves on 200; throws MnemomApiError with `.code === "code_not_enabled"`
1218
1218
  * (and `.details.hint`) when the org is not enabled, or a plain MnemomApiError
1219
1219
  * for membership/transport failures.
@@ -1229,6 +1229,6 @@ export interface AcceptProductInviteResult {
1229
1229
  products: string[];
1230
1230
  grant?: "fired" | "deferred";
1231
1231
  }
1232
- /** Redeem a `pinv_…` product invite (a Mnemom Code invitation) for the signed-in user. */
1232
+ /** Redeem a `pinv_…` product invite (a Mnemom Agent invitation) for the signed-in user. */
1233
1233
  export declare function acceptProductInvite(token: string): Promise<AcceptProductInviteResult>;
1234
1234
  export {};
package/dist/lib/api.js CHANGED
@@ -898,7 +898,7 @@ export async function resolveAgentId(agentName) {
898
898
  const name = agentName ?? process.env.MNEMOM_AGENT;
899
899
  if (!name) {
900
900
  console.error("\nAgent required. Use --agent <name> or set MNEMOM_AGENT.\n");
901
- console.error("List your agents with: mnemom agents\n");
901
+ console.error("List your agents with: mnemom agent list\n");
902
902
  process.exit(1);
903
903
  }
904
904
  // If it looks like an agent ID, use directly (no server call needed)
@@ -919,9 +919,9 @@ export async function resolveAgentId(agentName) {
919
919
  }
920
920
  }
921
921
  console.error(`\nAgent not found: ${name}`);
922
- console.error("List your agents with: mnemom agents\n");
922
+ console.error("List your agents with: mnemom agent list\n");
923
923
  console.error(`\nThis can happen when the agent was auto-created by the gateway and has not been claimed to your account yet.`);
924
- console.error(`To claim it, run: mnemom agents claim <agent-id> --name ${name} --key <your-provider-key>`);
924
+ console.error(`To claim it, run: mnemom agent claim <agent-id> --name ${name} --key <your-provider-key>`);
925
925
  console.error(`The <agent-id> (mnm-...) is the X-Mnemom-Agent value returned in the gateway response headers.`);
926
926
  console.error(`See: https://docs.mnemom.ai/quickstart/gateway\n`);
927
927
  process.exit(1);
@@ -1495,12 +1495,12 @@ export async function getOrgUsage(orgId, opts = {}) {
1495
1495
  const qs = params.toString() ? `?${params.toString()}` : "";
1496
1496
  return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/usage/by-person${qs}`, {}, { fallback: "Failed to fetch usage" });
1497
1497
  }
1498
- // ─── Mnemom Code — feature gate + invitation redemption ──────────────────────
1498
+ // ─── Mnemom Agent — feature gate + invitation redemption ──────────────────────
1499
1499
  //
1500
- // `mnemom code` is rolling out to an invited cohort. Two api calls back that:
1500
+ // `mnemom agent` is rolling out to an invited cohort. Two api calls back that:
1501
1501
  // the per-org gate (`GET /v1/code/entitlement`) the launch preamble checks, and
1502
1502
  // the product-invite accept (`POST /v1/invites/accept`) behind
1503
- // `mnemom code --invite <token>`. Both go through `codeFetch` rather than
1503
+ // `mnemom agent --invite <token>`. Both go through `codeFetch` rather than
1504
1504
  // `gFetch`: gFetch flattens EVERY 403 into "Permission denied (need org admin /
1505
1505
  // membership)", but the gate's 403 carries a machine-readable
1506
1506
  // `code_not_enabled` + a hint the command layer must print verbatim, and the
@@ -1529,16 +1529,16 @@ async function codeFetch(path, init = {}, fallback) {
1529
1529
  /** The machine-readable code on the gate's 403 when the org is not enabled. */
1530
1530
  export const CODE_NOT_ENABLED = "code_not_enabled";
1531
1531
  /**
1532
- * Is Mnemom Code enabled for `orgId` (default: the caller's primary org)?
1532
+ * Is Mnemom Agent enabled for `orgId` (default: the caller's primary org)?
1533
1533
  * Resolves on 200; throws MnemomApiError with `.code === "code_not_enabled"`
1534
1534
  * (and `.details.hint`) when the org is not enabled, or a plain MnemomApiError
1535
1535
  * for membership/transport failures.
1536
1536
  */
1537
1537
  export async function getCodeEntitlement(orgId) {
1538
1538
  const q = orgId ? `?org_id=${encodeURIComponent(orgId)}` : "";
1539
- return codeFetch(`/v1/code/entitlement${q}`, {}, "Could not check Mnemom Code access");
1539
+ return codeFetch(`/v1/code/entitlement${q}`, {}, "Could not check Mnemom Agent access");
1540
1540
  }
1541
- /** Redeem a `pinv_…` product invite (a Mnemom Code invitation) for the signed-in user. */
1541
+ /** Redeem a `pinv_…` product invite (a Mnemom Agent invitation) for the signed-in user. */
1542
1542
  export async function acceptProductInvite(token) {
1543
1543
  return codeFetch("/v1/invites/accept", { method: "POST", body: JSON.stringify({ token }) }, "Could not redeem the invitation");
1544
1544
  }