From 54d6ca25e4c7576d9b04e77cf407e673fb322fee Mon Sep 17 00:00:00 2001 From: Dhravya Shah Date: Sat, 12 Sep 2026 15:51:09 -0700 Subject: [PATCH] feat: advertise search_memory when a project has no memories yet; bump to 0.1.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The recall hook can only inject what it finds. On a miss it returned silently, and silence reads to the model as "there is nothing to search" — so it never reached for the tool, and history living in another container stayed invisible for the whole session. The guidance that would have fixed this already existed, but only inside formatRecall(), which runs when recall already found something. It was delivered in the case where it was least needed and withheld in the case where it mattered most. Emit a discovery block on the first genuinely-empty recall of a session: name the tool, point at ToolSearch to resolve it (the tool ships under three names depending on install shape, and deferred loading means the model holds a bare name with no schema), interpolate the repo containerTag, and note that read-only calls are auto-approved. Gated to fire once per session, and only when the container is truly empty rather than all-repeats — misses are the common case in an empty project, so repeating it would tax every prompt and invite searches on turns where memory is irrelevant. The marker is a file in the session dir next to recalled.json; writeState would have meant widening the EVENT_NAMES allowlist and touching the statusline renderer. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011qEtmi9chBJMgirVCTpSLu --- latest.json | 2 +- package.json | 2 +- plugin/.claude-plugin/plugin.json | 2 +- plugin/hooks/recall-directive.js | 58 +++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/latest.json b/latest.json index 839aa84..c084fbe 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "version": "0.1.6", + "version": "0.1.7", "updateCommand": "claude plugin update supermemory@supermemory-plugins" } diff --git a/package.json b/package.json index 8a88868..55efcbf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claude-supermemory-dev", - "version": "0.1.6", + "version": "0.1.7", "description": "Claude code plugin by Supermemory AI", "private": true, "type": "commonjs", diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index b6de6ea..679927a 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "supermemory", "displayName": "Supermemory", - "version": "0.1.6", + "version": "0.1.7", "description": "Persistent memory across Claude Code sessions using Supermemory", "author": { "name": "Supermemory", diff --git a/plugin/hooks/recall-directive.js b/plugin/hooks/recall-directive.js index 89d8cb6..c71c9b6 100644 --- a/plugin/hooks/recall-directive.js +++ b/plugin/hooks/recall-directive.js @@ -74,6 +74,50 @@ function readSeenHashes(sessionDir) { } } +// The hook can only inject what it finds. When a project has no stored memories +// the model gets silence — and silence reads as "there is nothing to search", +// so it never reaches for the tool and any history living in another container +// stays invisible. Say once per session that the tool exists, what to scope it +// to, and that the call is free. Once per session, because this fires on a miss +// and misses are the common case in an empty project: repeating it would tax +// every prompt and invite a search on turns where memory is irrelevant. +const DISCOVERY_MARKER = 'discovery.json'; + +function discoverySent(sessionDir) { + return fs.existsSync(path.join(sessionDir, DISCOVERY_MARKER)); +} + +function markDiscoverySent(sessionDir) { + try { + atomicWriteJson(path.join(sessionDir, DISCOVERY_MARKER), { + sentAt: new Date().toISOString(), + }); + } catch { + // Best effort: a failed marker only risks repeating the notice. + } +} + +// The tool is exposed under three names depending on install shape (see +// TOOL_NAME_RE in recall-approve.js), and under deferred tool loading the +// model holds a bare name with no schema — so point at the lookup, not at one +// hardcoded name. +function formatDiscovery(containerTag) { + return ` +No stored memories matched this prompt for this project. + +Deeper history may still exist. Search it with the supermemory search_memory +tool — exposed as mcp__…supermemory__search_memory; if its schema is not +loaded, resolve the exact name with ToolSearch("+supermemory search_memory"). + +- Scope to this project with containerTag: "${containerTag}" +- Omit containerTag to search the account's active/shared space instead. +- Read-only supermemory calls are auto-approved; they never prompt the user. + +Worth a call when the user refers to past decisions, earlier sessions, or says +"remember" / "we decided" / "last time". Skip it for self-contained tasks. +`; +} + function formatRecall(results, containerTag) { const lines = results.map((r) => { const text = resultText(r).replace(/\s+/g, ' ').slice(0, MAX_RESULT_CHARS); @@ -158,6 +202,20 @@ async function main() { }); if (fresh.length === 0) { + // Only when the container is genuinely empty: if results came back but + // were all repeats, formatRecall already delivered the same guidance + // earlier this session. + if (results.length === 0 && sessionDir && !discoverySent(sessionDir)) { + markDiscoverySent(sessionDir); + writeOutput({ + systemMessage: `${BRAND} ${gray('·')} no memories yet for this project`, + hookSpecificOutput: { + hookEventName: 'UserPromptSubmit', + additionalContext: formatDiscovery(containerTag), + }, + }); + return; + } writeOutput({ continue: true, suppressOutput: true }); return; }