SLOPSHOPPER

Supermemory

Persistent memory across Claude Code sessions using Supermemory

newband
★ 2,780v0.1.9MITupdated 2026-10-09supermemoryai/claude-supermemory/plugin
A shopper browsing a rack in a slop shop
README

claude-supermemory

Persistent memory for Claude Code, powered by Supermemory

version license

<img width="4000" height="2130" alt="Conceptual overview of Claude Code and Supermemory" src="https://github.com/user-attachments/assets/07e63ac4-b67d-457b-9029-1dc5d860e920" />

<sub>Conceptual overview. Some command names in the image predate the current plugin; see <a href="#commands">Commands</a> for what's available now.</sub>

A Claude Code plugin that gives your agent persistent memory across sessions using Supermemory. Your agent remembers what you worked on, across sessions and across projects.

Install · Features · How it works · Shared containers · Configuration · Commands · Privacy


Installation

Requires Node.js 18+ on your PATH. The memory hooks run as Node scripts.

/plugin marketplace add supermemoryai/claude-supermemory
/plugin install supermemory

Set your API key (get one at console.supermemory.ai), or just start a session and let browser login handle it:

export SUPERMEMORY_CC_API_KEY="sm_..."

That plugin was renamed to supermemory, so it won't update in place. Migrate with:

/plugin marketplace update supermemory-plugins
/plugin install supermemory@supermemory-plugins

Then, only if you still have the old plugin installed, remove it:

/plugin uninstall claude-supermemory@supermemory-plugins

Features

🧠 Direct recall<br>When authenticated, the hook searches substantive prompts before Claude sees them and injects fresh matches. No permission prompt or MCP tool call.🔎 Hosted MCP tools<br>search_memory, listSpaces, whoAmI, and more are available through the same credentials as the hooks, auto-approved when read-only.
💾 Auto capture<br>At the end of a session, the Stop hook saves new conversation content in the background. It asks Supermemory to retain durable context rather than transient Git state.🏷️ Shared repo memory<br>Automatic captures use the repository container shared with Codex and OpenCode and carry sm_scope: personal metadata.
🧭 Deep multi-container search<br>The context-gatherer subagent fans out several searches across a project's containers and returns a synthesized brief.⚙️ Project config<br>Per-repo settings, API keys, and container tag overrides via .claude/.supermemory-claude/config.json.
🗂️ Codebase index<br>/supermemory:index saves architecture, conventions, and how to run into this project's container.👋 Session context<br>Loads profile facts at session start and shows a welcome-back notice when you return to a project after 6+ hours.
  • Recall strip — On Claude Code 2.1.287+ in the terminal, press the changing memory headline above the prompt to browse the full returned facts one at a time

On Claude Code 2.1.250, local marketplace install/update and the SessionStart and UserPromptSubmit command hooks were verified, but claude plugin validate rejects the recall mod's classic.SessionStart event. The strip is not available on that version; other older versions and install sources have not been verified.

How it works

Claude Code supports hooks and MCP servers. supermemory registers four hooks, in lifecycle order:

SessionStart → UserPromptSubmit → PreToolUse → Stop

StepHookEventWhat it does
1session-startSessionStartBootstraps auth and loads profile context plus a welcome-back notice. It does not install a statusline; an old auto-installed one is removed.
2recall-directiveUserPromptSubmitSearches Supermemory directly with the prompt and injects fresh matches, deduplicated within the session.
3recall-approvePreToolUseAuto-allows read-only Supermemory MCP tools; writes still ask for permission.
4captureStopSaves the completed conversation delta in the background.

By default, recall is performed by the hook itself, not delegated to the model. It searches substantive prompts when authenticated, without waiting for Claude to choose a tool call. Setting recallDirective switches to advisory mode: the hook stops searching and instead tells Claude when it should decide to search on its own.

The hooks are tolerant: if Supermemory is unreachable, the API key is missing, or anything else fails, they exit cleanly without breaking your Claude Code session. A capture that fails is reported the next time a session starts.

Shared Agents memory

Claude Code, Codex, and OpenCode all generate the same container tag for a given repository, so new memories are shared:

repo_<project-name>__<remote-hash>   default container for capture and MCP memory tools
sm_scope: personal                   metadata on automatic captures

The hash is derived from the normalized Git remote, so clones share memory while same-named repositories do not collide. Repositories without a remote fall back to a local path identity. Set SUPERMEMORY_ISOLATE_WORKTREES=true to use the worktree path instead of the remote identity.

Unlike Codex, this plugin does not read older per-tool legacy containers (codex_user_*, opencode_project_*, and similar); it only ever uses the single unified tag above, generated fresh or overridden via repoContainerTag / SUPERMEMORY_REPO_TAG.

Configuration

Environment variables

VariablePurpose
SUPERMEMORY_CC_API_KEYYour Supermemory API key (browser auth is preferred).
SUPERMEMORY_API_URLOverride the Supermemory API base URL.
SUPERMEMORY_MCP_URLOverride the hosted MCP endpoint (default https://mcp.supermemory.ai/mcp).
SUPERMEMORY_AUTH_URLOverride the browser-auth base URL.
SUPERMEMORY_REPO_TAGProject-container override, used only when project config has no repoContainerTag.
SUPERMEMORY_ISOLATE_WORKTREESSet to true to key the project container on the worktree path instead of the Git remote.
SUPERMEMORY_DEBUGSet to true to enable debug logging.

Global settings (~/.supermemory-claude/settings.json)

{
  "maxProfileItems": 5,
  "signalExtraction": true,
  "signalKeywords": ["remember", "architecture", "decision", "bug", "fix"],
  "signalTurnsBefore": 3,
  "includeTools": ["Edit", "Write"]
}
OptionDescription
maxProfileItemsMax memories in context (default: 5).
recallDirectiveSet to switch prompt recall from direct hook search to an advisory instruction Claude reasons over.
signalExtractionOnly capture important turns (default: false).
signalKeywordsKeywords that trigger capture.
signalTurnsBeforeContext turns before signal (default: 3).
includeToolsTool calls to explicitly capture.
debugEnable debug logging (default: false).

Project config (.claude/.supermemory-claude/config.json)

Per-repo overrides, created manually or via the settings your team shares:

{
  "apiKey": "sm_...",
  "baseUrl": "https://api.supermemory.ai",
  "repoContainerTag": "my-team-project",
  "signalExtraction": true
}
OptionDescription
apiKeyProject-specific API key.
baseUrlSupermemory API URL.
repoContainerTagOverride the unified project container tag. Checked before SUPERMEMORY_REPO_TAG.

Commands

CommandDescription
/supermemory:indexIndex this repo's architecture, conventions, and how to run into the project container.
/supermemory:statusShow authentication status, API and MCP reachability, and the active project container.

Search does not have its own command. With a key, the prompt hook searches substantive prompts. For deeper history, use the context-gatherer agent or the MCP tools. /supermemory:index saves codebase structure. For a one-off save, ask Claude to use the add_memory MCP tool. Without containerTag, the proxy defaults it to this repository's container; pass a different tag to select another space. Conversation turns are saved when a session ends.

Privacy

For information about how Supermemory collects, uses, and retains data, see the Supermemory Privacy Policy.

License

MIT


<sub>◪ is the supermemory mark. Whenever you see it (the recall strip, notices, or Claude's answers), that information came from supermemory.</sub>

Source 1 files
hooks/recall-strip.js 167 lines
1function returnedFacts(contexts, tag) {
2  const facts = [];
3  for (const context of contexts || []) {
4    if (typeof context !== 'string') continue;
5    const block = context.match(
6      new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`),
7    )?.[1];
8    if (!block) continue;
9    let current = -1;
10    for (const line of block.split('\n')) {
11      if (
12        line.startsWith('## User Profile') ||
13        line.startsWith('## Recent Context')
14      ) {
15        current = -1;
16        continue;
17      }
18      const text = line.match(/^- ◪ (.+)$/)?.[1];
19      if (text) {
20        current = facts.length;
21        facts.push(text);
22      } else if (tag === 'supermemory-context' && current >= 0) {
23        facts[current] += `\n${line}`;
24      }
25    }
26  }
27  return facts.map((fact) => fact.trimEnd());
28}
29
30const headlines = [
31  (count, narrow) =>
32    narrow
33      ? `clarified ${count}`
34      : `supermemory clarified ${count} ${count === 1 ? 'thing' : 'things'}`,
35  (count, narrow) =>
36    narrow
37      ? `surfaced ${count}`
38      : `supermemory surfaced ${count} ${count === 1 ? 'detail' : 'details'}`,
39  (count, narrow) =>
40    narrow
41      ? `${count} brought back`
42      : `supermemory brought back ${count} ${count === 1 ? 'memory' : 'memories'}`,
43  (count, narrow) =>
44    narrow
45      ? `found ${count}`
46      : `supermemory found ${count} ${count === 1 ? 'useful detail' : 'useful details'}`,
47  (count, narrow) =>
48    narrow
49      ? `uncovered ${count}`
50      : `supermemory uncovered ${count} ${count === 1 ? 'detail' : 'details'}`,
51  (count, narrow) =>
52    narrow
53      ? `remembers ${count}`
54      : `supermemory remembered ${count} ${count === 1 ? 'thing' : 'things'}`,
55];
56
57export function register(on) {
58  let facts = [];
59  let expanded = false;
60  let active = 0;
61  let headlineIndex = Math.floor(Math.random() * headlines.length) - 1;
62
63  on('classic.SessionStart', async ($, e, next) => {
64    facts = [];
65    expanded = false;
66    active = 0;
67    try {
68      const result = await next(e);
69      facts = returnedFacts(result.additionalContext, 'supermemory-context');
70      if (facts.length) headlineIndex = (headlineIndex + 1) % headlines.length;
71      return result;
72    } finally {
73      $.ui.invalidate('ui.render');
74    }
75  });
76
77  on('classic.UserPromptSubmit', async ($, e, next) => {
78    try {
79      facts = [];
80      expanded = false;
81      active = 0;
82      const result = await next(e);
83      facts = returnedFacts(result.additionalContext, 'supermemory-recall');
84      if (facts.length) headlineIndex = (headlineIndex + 1) % headlines.length;
85      return result;
86    } finally {
87      $.ui.invalidate('ui.render');
88    }
89  });
90
91  on(
92    'ui.render',
93    { component: 'AbovePrompt', surface: 'terminal' },
94    async ($, e, next) => {
95      const original = await next(e);
96      if (e.props.hasSurvey || facts.length === 0) return original;
97
98      const { Box, Text, Button } = $.ui.resolve(e);
99      const width = Math.min(
100        e.props.bodyColumns,
101        Math.max(1, Math.min(64, e.props.bodyColumns - 4)),
102      );
103      const label = `◪ ${headlines[headlineIndex](facts.length, e.props.bodyColumns < 32)}`;
104
105      return Box({
106        flexDirection: 'column',
107        children: [
108          original,
109          Button({
110            key: 'recall-details',
111            label: `${label} ${expanded ? '▴' : '▾'}`,
112            plain: true,
113            dimColor: true,
114            onPress: () => {
115              expanded = !expanded;
116              if (expanded) active = 0;
117              $.ui.invalidate('ui.render');
118            },
119          }),
120          expanded
121            ? Box({
122                paddingX: 1,
123                width,
124                flexDirection: 'column',
125                children: [
126                  Text({ wrap: 'wrap', children: facts[active] }),
127                  facts.length > 1
128                    ? Box({
129                        flexDirection: 'row',
130                        children: [
131                          Button({
132                            key: 'recall-previous',
133                            label: '‹',
134                            plain: true,
135                            dimColor: true,
136                            onPress: () => {
137                              active =
138                                (active - 1 + facts.length) % facts.length;
139                              $.ui.invalidate('ui.render');
140                            },
141                          }),
142                          Text({
143                            dimColor: true,
144                            children: ` ${active + 1}/${facts.length} `,
145                          }),
146                          Button({
147                            key: 'recall-next',
148                            label: '›',
149                            plain: true,
150                            dimColor: true,
151                            onPress: () => {
152                              active = (active + 1) % facts.length;
153                              $.ui.invalidate('ui.render');
154                            },
155                          }),
156                        ],
157                      })
158                    : null,
159                ],
160              })
161            : null,
162        ],
163      });
164    },
165  );
166}
167