The desktop app never sources a shell rc, so a key that exists only as MEM0_API_KEY
in .zshrc is invisible there and the plugin silently no-ops. Claude Code injects the
userConfig value as CLAUDE_PLUGIN_OPTION_API_KEY (v1 read it, v2 did not).
- resolve_api_key() -> (key, source): env, plugin config, legacy plugin config, keychain
- health now reports WHERE the key came from, which is how you tell the two apart
- every logged event records the editor, and a new 'sessions' command lists which
surfaces have actually built packs and written memories
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An API key is already bound to one (org_id, project_id) server-side, so storing a
copy locally was redundant state that can go stale. Verified live that add,
get_all and feedback all succeed with no ids in the body at all.
- both ids are now purely an OVERRIDE, for pointing one key at a different
project in the same org; half an override is ignored rather than half-applied
- identity resolution no longer persists scope as a side effect
- the project-config endpoints (the only ones needing ids in the URL) resolve
them from /v1/ping/ and cache the result
- corrects an over-generalized finding: feedback does not require the ids; it
404'd earlier only because that memory lived outside the key's project
- onboarding now recommends a key issued for a dedicated project over an override
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SessionStart failed with 'pyenv: mem0-agent: command not found' in any repo whose
.python-version selects a Python the console script was not installed into. A
pip entry point is the wrong dependency for a plugin.
- bin/mem0-agent: POSIX launcher that picks the first CPython >= 3.10 it can find
and runs the package from source (there are no third-party dependencies)
- hooks now invoke ${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent, matching how the v1
plugin calls its own scripts; the generator enforces this
- app_id override renamed to MEM0_AGENT_APP_ID: v1 exports MEM0_PROJECT_ID, which
was silently overriding this plugin's project scope while both are installed
Verified: all six hooks exit 0 from a pyenv-3.11 repo, and app_id resolves per
repository again.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The generated manifest writes 'mem0-agent context --session-id $CLAUDE_SESSION_ID',
but the flag was only on the top-level parser, so argparse rejected it and every
SessionStart hook exited 2 -- the plugin would install and silently do nothing.
Both orders now work, and a new test runs every command line in the manifest
through the parser so a manifest/CLI mismatch fails the build.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The loader validates userConfig.<key>.title as a required string; without it the
install fails with 'invalid manifest file'. Manifest shape now matches the v1
plugin's exactly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude Code auto-discovers a plugin's hooks at <plugin>/hooks/hooks.json. The
generator was writing to hooks/generated/, so the manifest existed but was never
loaded and no hook would have fired on install.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Found by running the CLI live against a scratch project:
- hook_input() blocked forever on an open stdin pipe with no data. A real hook
writes JSON and closes the pipe, but any manual invocation hung; now polled
with select() and never blocks.
- the open-thread snapshot was filling with progress lines and file-modification
lists -- v1's exact failure -- so it now filters turns through the same
hard-drop rules the capture gate uses.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- triggers.py: hard-drop rules now filter PER TURN, so a durable fact sitting
between two progress lines survives instead of being dropped with them. The
eval harness caught this: 3 mixed-window fixtures were being destroyed
client-side before the extractor ever saw them.
- widened flag rules for 13 plainly durable windows the gate was missing
(standing preferences, X-over-Y decisions, stated rules, diagnoses, verified
procedures); verified procedures moved from aggressive to balanced
- eval/: 56 labeled fixtures from the audited v1 corpus plus offline and live
runners with a --check regression gate
- un-ignore integrations/mem0-agent/eval (root .gitignore excluded it silently)
Offline scorecard: hard_drop_recall 1.000, extract_recall 1.000,
flag_precision 1.000, 0 misclassified.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- pack.py: single-call context pack, budgeted, sanitized, cached, with
served-id tracking that turns a citation into POSITIVE feedback
- assist.py: error-signature lookup (never raw stdout, which returned 0 results in v1)
- maintain.py: transactional consolidation (add merged -> verify -> delete sources)
- transcript.py: JSONL parsing into non-overlapping windows via a cursor
- cli.py: the twelve commands the hooks invoke; detached assist queues a block
that the next prompt hook drains, keeping the hot path network-free
- hooks generated from one spec; plugin manifest, MCP wiring, five skills
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Encodes the platform contract validated against the live API:
- project_id/org_id pinned in request body (query params are ignored)
- latest_only=True enforced on every read
- metadata.type as the read-time taxonomy (categorization lags ~4h)
- NOT filters take a list; delete_all takes query params; fields repeated
Includes project config (write-gate instructions, 6 custom categories, decay),
verified filter recipes, circuit breaker, session-scoped state, keychain auth,
and identity/project resolution.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>