feat(cli): pass detected agent_caller to backend on Agent Mode bootstrap

The CLI's detect_agent_caller() already canonicalizes the caller (env-var
sniff returning "claude-code", "cursor", etc.) and reports it to PostHog —
but the value never reached the backend, so APIKey.agent_caller stayed
NULL. Join: send it in the bootstrap request body so the platform can
persist it (see mem0ai/platform#2784).

Wire-up:
- bootstrap_via_backend / bootstrapViaBackend gain an agent_caller kwarg,
  pass it as request body field "agent_caller"
- init_cmd / init.ts call detect_agent_caller() once and forward
- platform.agent_caller persisted to ~/.mem0/config.json so the local
  view matches what the backend stored

No change to created_via — that stays the channel enum ("agent_mode" /
"email" / "api_key"). agent_caller is the orthogonal "who started this".
This commit is contained in:
Mgeeeek
2026-05-14 17:24:12 +05:30
parent bac961bc8d
commit d54dad265d
6 changed files with 29 additions and 4 deletions
+6 -1
View File
@@ -26,7 +26,10 @@ export interface BootstrapEnvelope {
export async function bootstrapViaBackend( export async function bootstrapViaBackend(
config: Mem0Config, config: Mem0Config,
{ source }: { source?: string | null } = {}, {
source,
agentCaller,
}: { source?: string | null; agentCaller?: string | null } = {},
): Promise<void> { ): Promise<void> {
const baseUrl = (config.platform.baseUrl || "https://api.mem0.ai").replace( const baseUrl = (config.platform.baseUrl || "https://api.mem0.ai").replace(
/\/+$/, /\/+$/,
@@ -34,6 +37,7 @@ export async function bootstrapViaBackend(
); );
const body: Record<string, unknown> = {}; const body: Record<string, unknown> = {};
if (source) body.source = source; if (source) body.source = source;
if (agentCaller) body.agent_caller = agentCaller;
let resp: Response; let resp: Response;
try { try {
@@ -89,6 +93,7 @@ export async function bootstrapViaBackend(
config.platform.baseUrl = baseUrl; config.platform.baseUrl = baseUrl;
config.platform.agentMode = true; config.platform.agentMode = true;
config.platform.createdVia = "agent_mode"; config.platform.createdVia = "agent_mode";
config.platform.agentCaller = agentCaller ?? "";
config.platform.claimedAt = ""; config.platform.claimedAt = "";
config.platform.defaultUserId = envelope.default_user_id; config.platform.defaultUserId = envelope.default_user_id;
// Adopt the slug-derived user_id as the default scope for memory ops. // Adopt the slug-derived user_id as the default scope for memory ops.
+4 -1
View File
@@ -367,7 +367,10 @@ export async function runInit(
return; return;
} }
// Rule 3: mint a fresh shadow (no valid key to reuse). // Rule 3: mint a fresh shadow (no valid key to reuse).
await bootstrapViaBackend(config, { source: opts.source ?? null }); await bootstrapViaBackend(config, {
source: opts.source ?? null,
agentCaller: detectAgentCaller(),
});
fireInit("agent"); fireInit("agent");
return; return;
} }
+4
View File
@@ -24,6 +24,7 @@ export interface PlatformConfig {
// Agent Mode (unclaimed-shadow signup) // Agent Mode (unclaimed-shadow signup)
agentMode: boolean; // true while the key is an unclaimed agent-mode key agentMode: boolean; // true while the key is an unclaimed agent-mode key
createdVia: string; // "agent_mode" | "email" | "api_key" | "existing_key" createdVia: string; // "agent_mode" | "email" | "api_key" | "existing_key"
agentCaller: string; // canonical agent name when createdVia === "agent_mode" (e.g. "claude-code")
claimedAt: string; // ISO timestamp once the agent has been claimed claimedAt: string; // ISO timestamp once the agent has been claimed
defaultUserId: string; // `user_<slug>` returned by bootstrap; auto-default scope defaultUserId: string; // `user_<slug>` returned by bootstrap; auto-default scope
} }
@@ -61,6 +62,7 @@ export function createDefaultConfig(): Mem0Config {
userEmail: "", userEmail: "",
agentMode: false, agentMode: false,
createdVia: "", createdVia: "",
agentCaller: "",
claimedAt: "", claimedAt: "",
defaultUserId: "", defaultUserId: "",
}, },
@@ -90,6 +92,7 @@ export function loadConfig(): Mem0Config {
config.platform.userEmail = plat.user_email ?? ""; config.platform.userEmail = plat.user_email ?? "";
config.platform.agentMode = Boolean(plat.agent_mode ?? false); config.platform.agentMode = Boolean(plat.agent_mode ?? false);
config.platform.createdVia = plat.created_via ?? ""; config.platform.createdVia = plat.created_via ?? "";
config.platform.agentCaller = plat.agent_caller ?? "";
config.platform.claimedAt = plat.claimed_at ?? ""; config.platform.claimedAt = plat.claimed_at ?? "";
config.platform.defaultUserId = plat.default_user_id ?? ""; config.platform.defaultUserId = plat.default_user_id ?? "";
@@ -133,6 +136,7 @@ export function saveConfig(config: Mem0Config): void {
user_email: config.platform.userEmail, user_email: config.platform.userEmail,
agent_mode: config.platform.agentMode, agent_mode: config.platform.agentMode,
created_via: config.platform.createdVia, created_via: config.platform.createdVia,
agent_caller: config.platform.agentCaller,
claimed_at: config.platform.claimedAt, claimed_at: config.platform.claimedAt,
default_user_id: config.platform.defaultUserId, default_user_id: config.platform.defaultUserId,
}, },
@@ -32,16 +32,25 @@ def bootstrap_via_backend(
config: Mem0Config, config: Mem0Config,
*, *,
source: str | None = None, source: str | None = None,
agent_caller: str | None = None,
) -> None: ) -> None:
"""POST /api/v1/auth/agent_mode/ and mutate config in place. """POST /api/v1/auth/agent_mode/ and mutate config in place.
Returns nothing — the caller saves the config and prints follow-up messages. Args:
config: Mem0Config mutated in place with the new platform values.
source: ``--source`` flag passthrough (analytics tag, free-form).
agent_caller: Canonical agent name detected from env vars
(claude-code, cursor, ...). Persisted on the backend APIKey and
saved into ``platform.agent_caller`` for local introspection.
Raises typer.Exit(1) on failure. Raises typer.Exit(1) on failure.
""" """
base_url = (config.platform.base_url or "https://api.mem0.ai").rstrip("/") base_url = (config.platform.base_url or "https://api.mem0.ai").rstrip("/")
body: dict[str, Any] = {} body: dict[str, Any] = {}
if source: if source:
body["source"] = source body["source"] = source
if agent_caller:
body["agent_caller"] = agent_caller
try: try:
with httpx.Client(timeout=30.0) as client: with httpx.Client(timeout=30.0) as client:
@@ -84,6 +93,7 @@ def bootstrap_via_backend(
config.platform.base_url = base_url config.platform.base_url = base_url
config.platform.agent_mode = True config.platform.agent_mode = True
config.platform.created_via = "agent_mode" config.platform.created_via = "agent_mode"
config.platform.agent_caller = agent_caller or ""
config.platform.claimed_at = "" config.platform.claimed_at = ""
config.platform.default_user_id = envelope["default_user_id"] config.platform.default_user_id = envelope["default_user_id"]
# Adopt the slug-derived user_id as the default scope for memory ops. # Adopt the slug-derived user_id as the default scope for memory ops.
+1 -1
View File
@@ -295,7 +295,7 @@ def run_init(
_fire_init("existing_key") _fire_init("existing_key")
return return
# Rule 3: mint a fresh shadow (no valid key to reuse). # Rule 3: mint a fresh shadow (no valid key to reuse).
bootstrap_via_backend(config, source=source) bootstrap_via_backend(config, source=source, agent_caller=detect_agent_caller())
_fire_init("agent") _fire_init("agent")
return return
+3
View File
@@ -31,6 +31,7 @@ class PlatformConfig:
# Agent Mode (unclaimed-shadow signup) # Agent Mode (unclaimed-shadow signup)
agent_mode: bool = False # True while the key is an unclaimed agent-mode key agent_mode: bool = False # True while the key is an unclaimed agent-mode key
created_via: str = "" # "agent_mode" | "email" | "api_key" | "existing_key" created_via: str = "" # "agent_mode" | "email" | "api_key" | "existing_key"
agent_caller: str = "" # canonical agent name when created_via == "agent_mode" (e.g. "claude-code")
claimed_at: str = "" # ISO timestamp once the agent has been claimed by a human claimed_at: str = "" # ISO timestamp once the agent has been claimed by a human
default_user_id: str = "" # `user_<slug>` returned by bootstrap; used as auto-default default_user_id: str = "" # `user_<slug>` returned by bootstrap; used as auto-default
@@ -90,6 +91,7 @@ def load_config() -> Mem0Config:
config.platform.user_email = plat.get("user_email", "") config.platform.user_email = plat.get("user_email", "")
config.platform.agent_mode = bool(plat.get("agent_mode", False)) config.platform.agent_mode = bool(plat.get("agent_mode", False))
config.platform.created_via = plat.get("created_via", "") config.platform.created_via = plat.get("created_via", "")
config.platform.agent_caller = plat.get("agent_caller", "")
config.platform.claimed_at = plat.get("claimed_at", "") config.platform.claimed_at = plat.get("claimed_at", "")
config.platform.default_user_id = plat.get("default_user_id", "") config.platform.default_user_id = plat.get("default_user_id", "")
@@ -147,6 +149,7 @@ def save_config(config: Mem0Config) -> None:
"user_email": config.platform.user_email, "user_email": config.platform.user_email,
"agent_mode": config.platform.agent_mode, "agent_mode": config.platform.agent_mode,
"created_via": config.platform.created_via, "created_via": config.platform.created_via,
"agent_caller": config.platform.agent_caller,
"claimed_at": config.platform.claimed_at, "claimed_at": config.platform.claimed_at,
"default_user_id": config.platform.default_user_id, "default_user_id": config.platform.default_user_id,
}, },