Persistent memory across Claude Code sessions using Supermemory

Persistent memory for Claude Code, powered by Supermemory
<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
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
| 🧠 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. |
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.
Claude Code supports hooks and MCP servers. supermemory registers four hooks, in lifecycle order:
SessionStart → UserPromptSubmit → PreToolUse → Stop
| Step | Hook | Event | What it does |
|---|---|---|---|
| 1 | session-start | SessionStart | Bootstraps auth and loads profile context plus a welcome-back notice. It does not install a statusline; an old auto-installed one is removed. |
| 2 | recall-directive | UserPromptSubmit | Searches Supermemory directly with the prompt and injects fresh matches, deduplicated within the session. |
| 3 | recall-approve | PreToolUse | Auto-allows read-only Supermemory MCP tools; writes still ask for permission. |
| 4 | capture | Stop | Saves 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.
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.
| Variable | Purpose |
|---|---|
SUPERMEMORY_CC_API_KEY | Your Supermemory API key (browser auth is preferred). |
SUPERMEMORY_API_URL | Override the Supermemory API base URL. |
SUPERMEMORY_MCP_URL | Override the hosted MCP endpoint (default https://mcp.supermemory.ai/mcp). |
SUPERMEMORY_AUTH_URL | Override the browser-auth base URL. |
SUPERMEMORY_REPO_TAG | Project-container override, used only when project config has no repoContainerTag. |
SUPERMEMORY_ISOLATE_WORKTREES | Set to true to key the project container on the worktree path instead of the Git remote. |
SUPERMEMORY_DEBUG | Set to true to enable debug logging. |
~/.supermemory-claude/settings.json){
"maxProfileItems": 5,
"signalExtraction": true,
"signalKeywords": ["remember", "architecture", "decision", "bug", "fix"],
"signalTurnsBefore": 3,
"includeTools": ["Edit", "Write"]
}
| Option | Description |
|---|---|
maxProfileItems | Max memories in context (default: 5). |
recallDirective | Set to switch prompt recall from direct hook search to an advisory instruction Claude reasons over. |
signalExtraction | Only capture important turns (default: false). |
signalKeywords | Keywords that trigger capture. |
signalTurnsBefore | Context turns before signal (default: 3). |
includeTools | Tool calls to explicitly capture. |
debug | Enable debug logging (default: false). |
.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
}
| Option | Description |
|---|---|
apiKey | Project-specific API key. |
baseUrl | Supermemory API URL. |
repoContainerTag | Override the unified project container tag. Checked before SUPERMEMORY_REPO_TAG. |
| Command | Description |
|---|---|
/supermemory:index | Index this repo's architecture, conventions, and how to run into the project container. |
/supermemory:status | Show 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.
For information about how Supermemory collects, uses, and retains data, see the Supermemory Privacy Policy.
MIT
<sub>◪ is the supermemory mark. Whenever you see it (the recall strip, notices, or Claude's answers), that information came from supermemory.</sub>
hooks/recall-strip.js 167 lines1function 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