ed5ee7b9ff
Hooks previously fell back to $USER when MEM0_USER_ID wasn't set,
producing a different user_id on every machine for the same person.
Result: a single account with memories scattered across many user
buckets, none of which can see each other.
Resolution priority (same in bash and python):
1. MEM0_USER_ID env var (explicit override)
2. ~/.mem0/identity.json cache (pinned to MEM0_API_KEY fingerprint)
3. Derived: "mem0-" + sha256(MEM0_API_KEY)[:12]
4. Fallback: $USER, else "default"
Same MEM0_API_KEY across machines now yields the same user_id without
the user having to set MEM0_USER_ID by hand on every laptop.
Resolver shipped as two tiny files instead of a shared module:
_identity.sh -- sourced by bash hooks, exports MEM0_RESOLVED_USER_ID
_identity.py -- imported by on_pre_compact.py, exposes resolve_user_id()
Hook integration:
on_user_prompt.sh -- sources resolver, USER_ID interpolated into rubric
on_session_start.sh -- emits an "Active user_id: <X>" header before the
bootstrap text, so the agent's MCP search_memories/add_memory calls
use the same bucket the hooks write to (closes the agent-side half
of the symptom)
on_pre_compact.py -- replaces inline env lookup with resolve_user_id()
Existing memories under previous $USER values are not auto-migrated.
The cache file regenerates on key rotation (fingerprint mismatch).
CI nudge in pyproject.toml because path filters in ci.yml exclude
plugin-only PRs but build_mem0/build_embedchain are required.
Manual verification: same key on different $USER values resolves to
identical user_id; MEM0_USER_ID override bypasses cache and key
derivation; cache invalidates on key change; bash and python
implementations produce identical output for all four priority levels.
60 lines
1.8 KiB
Python
60 lines
1.8 KiB
Python
"""Resolve mem0 user_id with deterministic priority.
|
|
|
|
Resolution priority:
|
|
1. MEM0_USER_ID env var (explicit override)
|
|
2. ~/.mem0/identity.json cache (pinned to current MEM0_API_KEY fingerprint)
|
|
3. Derived: "mem0-" + sha256(MEM0_API_KEY)[:12]
|
|
4. Fallback: $USER, else "default"
|
|
|
|
Same MEM0_API_KEY across machines yields the same user_id, which fixes
|
|
the "47 user buckets per account" symptom from running on multiple
|
|
laptops with different $USER values.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from datetime import datetime, timezone
|
|
|
|
_CACHE_PATH = os.path.expanduser("~/.mem0/identity.json")
|
|
|
|
|
|
def resolve_user_id() -> str:
|
|
explicit = os.environ.get("MEM0_USER_ID", "").strip()
|
|
if explicit:
|
|
return explicit
|
|
|
|
api_key = os.environ.get("MEM0_API_KEY", "").strip()
|
|
if api_key:
|
|
digest = hashlib.sha256(api_key.encode("utf-8")).hexdigest()
|
|
fingerprint = digest[:8]
|
|
|
|
try:
|
|
with open(_CACHE_PATH, "r") as f:
|
|
cached = json.load(f)
|
|
if cached.get("api_key_fingerprint") == fingerprint and cached.get("user_id"):
|
|
return cached["user_id"]
|
|
except (OSError, json.JSONDecodeError):
|
|
pass
|
|
|
|
derived = "mem0-" + digest[:12]
|
|
try:
|
|
os.makedirs(os.path.dirname(_CACHE_PATH), exist_ok=True)
|
|
with open(_CACHE_PATH, "w") as f:
|
|
json.dump(
|
|
{
|
|
"user_id": derived,
|
|
"source": "api_key",
|
|
"api_key_fingerprint": fingerprint,
|
|
"resolved_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
},
|
|
f,
|
|
)
|
|
except OSError:
|
|
pass
|
|
return derived
|
|
|
|
return os.environ.get("USER") or "default"
|