You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Stamp source_client server-side from MCP clientInfo — explicit MCP saves are surface-indistinguishable (NULL provenance)
Problem
documents.source_client is populated only when the caller passes it: the hooks pass claude-code-hook, the mirror passes mnemon-mirror, the CLI passes cli — but a plain memory_save MCP tool call leaves it NULL. Live-vault audit 2026-07-18: of the 100 most recent decision docs, 59 are NULL — meaning an explicit save from claude.ai chat, Claude Desktop, Cursor, or a Claude Code session are mutually indistinguishable. This made a real surface-coverage investigation ("has any claude.ai chat session ever written to mnemon?") unanswerable from the data.
Fix (the robust one — no delta)
Derive provenance at the transport layer instead of trusting an optional model-supplied param:
In the MCP server path, capture the client identity from the initialize handshake's clientInfo.name/version (FastMCP exposes the session/context object to tool handlers) and/or, on the remote HTTP path, the authenticated OAuth client identity (auth.py — DCR-registered client) as a fallback.
In memory_save (src/mnemon/server.py) and the profile-save path (source_client="mcp-profile" today), when the caller does NOT pass source_client, stamp the derived value (e.g. mcp:claude-ai, mcp:claude-code, mcp:cursor). An explicitly passed value still wins (hooks/mirror/CLI keep their existing tags; the transitional Profile-snippet source_client: "claude-ai" from claude.ai chat surface never writes to mnemon — decide autonomous-save posture (Profile snippet) or document explicit-only #273 keeps working).
Backward compatible: pure column-value change, no schema migration; existing NULL rows stay NULL (optionally note in the issue-closing comment that history before the cutover is unattributable).
Acceptance criteria (closes when)
A memory_save call from a claude.ai chat session with no explicit source_client lands with a non-NULL derived value distinguishable from a Claude Code MCP session's, verified via memory_timeline.
Unit test covering: explicit param wins; derived stamp applied when absent; graceful None when handshake metadata is unavailable (stdio clients that omit clientInfo).
README.md tools table / docstring for memory_save updated to describe the derivation.
src/mnemon/store.py — Document.source_client, HOOK_SOURCE_CLIENTS (search-layer boost keys on these values — verify the new mcp:* values do NOT accidentally join the hook-boost set).
src/mnemon/server_remote.py / auth.py — remote HTTP session + OAuth client identity for the fallback path.
src/mnemon/api.pymemory_save — REST path should get the same default-stamping treatment (or an explicit rest default).
Gotchas
search.py Layer 4 demotes results whose source_client is in HOOK_SOURCE_CLIENTS — new derived values must be chosen so explicit saves keep full ranking weight.
clientInfo.name strings are client-controlled free text; normalize (lowercase, prefix mcp:) and never branch security behavior on them — provenance/observability only.
Stamp
source_clientserver-side from MCP clientInfo — explicit MCP saves are surface-indistinguishable (NULL provenance)Problem
documents.source_clientis populated only when the caller passes it: the hooks passclaude-code-hook, the mirror passesmnemon-mirror, the CLI passescli— but a plainmemory_saveMCP tool call leaves it NULL. Live-vault audit 2026-07-18: of the 100 most recentdecisiondocs, 59 are NULL — meaning an explicit save from claude.ai chat, Claude Desktop, Cursor, or a Claude Code session are mutually indistinguishable. This made a real surface-coverage investigation ("has any claude.ai chat session ever written to mnemon?") unanswerable from the data.Fix (the robust one — no delta)
Derive provenance at the transport layer instead of trusting an optional model-supplied param:
initializehandshake'sclientInfo.name/version(FastMCP exposes the session/context object to tool handlers) and/or, on the remote HTTP path, the authenticated OAuth client identity (auth.py— DCR-registered client) as a fallback.memory_save(src/mnemon/server.py) and the profile-save path (source_client="mcp-profile"today), when the caller does NOT passsource_client, stamp the derived value (e.g.mcp:claude-ai,mcp:claude-code,mcp:cursor). An explicitly passed value still wins (hooks/mirror/CLI keep their existing tags; the transitional Profile-snippetsource_client: "claude-ai"from claude.ai chat surface never writes to mnemon — decide autonomous-save posture (Profile snippet) or document explicit-only #273 keeps working).Acceptance criteria (closes when)
memory_savecall from a claude.ai chat session with no explicitsource_clientlands with a non-NULL derived value distinguishable from a Claude Code MCP session's, verified viamemory_timeline.Nonewhen handshake metadata is unavailable (stdio clients that omit clientInfo).README.mdtools table / docstring formemory_saveupdated to describe the derivation.Anchors
src/mnemon/server.py—memory_save(source_client param),profile_update(source_client="mcp-profile").src/mnemon/store.py—Document.source_client,HOOK_SOURCE_CLIENTS(search-layer boost keys on these values — verify the newmcp:*values do NOT accidentally join the hook-boost set).src/mnemon/server_remote.py/auth.py— remote HTTP session + OAuth client identity for the fallback path.src/mnemon/api.pymemory_save— REST path should get the same default-stamping treatment (or an explicitrestdefault).Gotchas
search.pyLayer 4 demotes results whosesource_clientis inHOOK_SOURCE_CLIENTS— new derived values must be chosen so explicit saves keep full ranking weight.clientInfo.namestrings are client-controlled free text; normalize (lowercase, prefixmcp:) and never branch security behavior on them — provenance/observability only.