@everme/claude-code 0.6.3 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@
10
10
  "name": "everme",
11
11
  "source": "./",
12
12
  "description": "Automatic memory recall for Claude Code through the EverMe gateway. Saves and recalls per-session context using your EverMe account credentials.",
13
- "version": "0.6.3",
13
+ "version": "0.6.5",
14
14
  "homepage": "https://everme.evermind.ai",
15
15
  "license": "Apache-2.0"
16
16
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "everme",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "EverMe — automatic memory recall for Claude Code. Recalls relevant context from past sessions before each prompt and saves new turns through the EverMe gateway.",
5
5
  "author": {
6
6
  "name": "EverMind AI",
package/README.md CHANGED
@@ -7,9 +7,20 @@ Automatic memory recall + persistence for Claude Code, backed by the EverMe gate
7
7
  - **SessionStart** → loads the profile snapshot from past sessions and injects it as `additionalContext` for the model + a one-line `🧠 EverMe loaded N memory items` system message for you.
8
8
  - **UserPromptSubmit** → searches your memory for content relevant to the prompt you just typed and injects it BEFORE the model sees the prompt. Silent when no relevant hit (no nag).
9
9
  - **Stop** → persists the just-finished raw turn (including tool calls/results) with `flush:false`; every fifth turn triggers extraction.
10
- - **SubagentStop** → persists the child transcript's assistant/tool trajectory under a stable child conversation id; internal task/user wrappers are excluded and a per-transcript checkpoint prevents repeats.
10
+ - **SubagentStop** → excludes internal task/user wrappers, then skips an independent child transcript without a real user (`subagent_without_user`). Admitted child trajectories keep their stable conversation ID and per-transcript checkpoint.
11
11
  - **SessionEnd** → sends a flush-only request so short sessions still extract memory without repeating messages.
12
12
 
13
+ Write hooks require a nonblank native string `session_id`; `SubagentStop` also requires a nonblank native string `agent_id`. Missing or invalid IDs produce a bounded diagnostic and skip the write, without generating a fallback ID. Valid child IDs retain the `session_id__agent_id` format. Read-only recall is unaffected.
14
+
15
+ Tool arguments must encode valid JSON. Invalid calls are skipped with aggregate diagnostics, together with results whose native ID identifies that call uniquely. Other text and valid calls remain unchanged; conflicting source IDs are not used to guess result ownership. Tool result text itself need not be JSON.
16
+
17
+ Tool names must be nonblank native strings. Missing or invalid names follow the same skip-and-diagnose policy, without substituting `unknown`; an actual native tool named `unknown` remains valid.
18
+
19
+ Native assistant `message.stop_reason: "end_turn"` is retained as local batching
20
+ metadata so complete turns take priority over tool-pair boundaries. Missing or
21
+ other stop reasons do not imply completion. This metadata is not sent to the
22
+ gateway; oversized turns still use the existing tool-pair/hard-limit fallback.
23
+
13
24
  Plus:
14
25
 
15
26
  - **MCP server** exposing the read-only `mem_search` / `mem_context` tools for explicit recall (saving is automatic via the hooks).
@@ -105,3 +116,27 @@ install.sh bash installer
105
116
  ## License
106
117
 
107
118
  Apache-2.0
119
+ # Task-aware ingestion
120
+
121
+ Claude Code enables the shared task-aware batching policy: preserve fitting
122
+ user tasks, compress only byte-oversized tool results with 4/3/2 KiB budgets,
123
+ then split remaining overflow at text, tool-pair, or hard boundaries. Existing
124
+ incremental reads, checkpoint commits and flush cadence are unchanged.
125
+
126
+ SubagentStop skips child transcripts without a real user after filtering,
127
+ reports `subagent_without_user`, and does not advance their checkpoints. Root
128
+ assistant-only deltas remain eligible. No Codex-specific goal envelope is
129
+ assumed for Claude's native transcript format.
130
+
131
+ ## Fork history and progress
132
+
133
+ Root write hooks exclude records explicitly belonging to another native
134
+ `sessionId` before selecting messages, resolving the turn ID, and calculating
135
+ checkpoint counts or `baseTurn`. The hook's `session_id` remains authoritative.
136
+ Records without a session ID retain the existing compatibility behavior;
137
+ subagent selection is unchanged. First capture still selects only the last turn.
138
+
139
+ This corrects new-state coordinates only. Existing checkpoints and server
140
+ watermarks are not migrated or cleared; previously contaminated progress can
141
+ still skip messages and requires a separate recovery decision. No historical
142
+ replay is triggered by this change.
@@ -1,8 +1,10 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
3
  import { readTranscript, extractAgentMessages } from "./transcript.js";
4
+ import { turnOrdinals } from "@everme/agent-sdk";
4
5
 
5
6
  const CONTEXT_EVENTS = new Set(["SessionStart", "UserPromptSubmit"]);
7
+ const WRITE_EVENTS = new Set(["Stop", "SubagentStop", "SessionEnd", "PreCompact"]);
6
8
 
7
9
  export const claudeCodeAdapter = {
8
10
  platform: "claude-code",
@@ -10,6 +12,7 @@ export const claudeCodeAdapter = {
10
12
  // every one of them, so the SDK claims channel="hook" for these writes and
11
13
  // they enter L1-2's denominator. Whole-session hosts leave this unset.
12
14
  turnBoundary: "stop",
15
+ taskBatching: true,
13
16
 
14
17
  envFile() {
15
18
  return process.env.EVERME_ENV_FILE_PATH || path.join(os.homedir(), ".claude", "everme.env");
@@ -17,10 +20,14 @@ export const claudeCodeAdapter = {
17
20
 
18
21
  normalizeInput(rawInput, event) {
19
22
  const isSubagent = event === "SubagentStop";
20
- const parentSessionId = rawInput?.session_id || "claude-code-session";
21
- const agentId = rawInput?.agent_id || "subagent";
23
+ const parentSessionId = nativeId(rawInput?.session_id);
24
+ const agentId = nativeId(rawInput?.agent_id);
25
+ const missingIdentity = !parentSessionId
26
+ ? "missing_session_id"
27
+ : isSubagent && !agentId ? "missing_agent_id" : "";
28
+ if (missingIdentity && WRITE_EVENTS.has(event)) logSkippedWrite(missingIdentity);
22
29
  return {
23
- sessionId: isSubagent ? `${parentSessionId}__${agentId}` : parentSessionId,
30
+ sessionId: missingIdentity ? "" : isSubagent ? `${parentSessionId}__${agentId}` : parentSessionId,
24
31
  transcriptPath: isSubagent
25
32
  ? rawInput?.agent_transcript_path || ""
26
33
  : rawInput?.transcript_path || "",
@@ -41,7 +48,7 @@ export const claudeCodeAdapter = {
41
48
  // "" (dedup disabled) when the transcript has no uuids.
42
49
  async resolveTurnId(input) {
43
50
  if (!input?.transcriptPath) return "";
44
- const lines = await readTranscript(input.transcriptPath);
51
+ const lines = await readSessionLines(input);
45
52
  for (let index = lines.length - 1; index >= 0; index -= 1) {
46
53
  try {
47
54
  const event = JSON.parse(lines[index]);
@@ -55,9 +62,11 @@ export const claudeCodeAdapter = {
55
62
 
56
63
  async readLastTurn(input) {
57
64
  if (!input?.transcriptPath) return [];
58
- const messages = extractAgentMessages(await readTranscript(input.transcriptPath), {
65
+ const messages = extractAgentMessages(await readSessionLines(input), {
59
66
  subagent: input.isSubagent,
67
+ diagnostic: (line) => console.error(line),
60
68
  });
69
+ if (skipUserlessChild(input, messages)) return [];
61
70
  for (let index = messages.length - 1; index >= 0; index -= 1) {
62
71
  if (messages[index]?.role === "user") return messages.slice(index);
63
72
  }
@@ -65,24 +74,40 @@ export const claudeCodeAdapter = {
65
74
  },
66
75
 
67
76
  async readStoreBatches(input, { checkpointStore } = {}) {
77
+ if (!nativeId(input?.sessionId)) {
78
+ logSkippedWrite("missing_session_id");
79
+ return [];
80
+ }
68
81
  if (!input?.transcriptPath) return [];
69
- const messages = extractAgentMessages(await readTranscript(input.transcriptPath), {
82
+ const messages = extractAgentMessages(await readSessionLines(input), {
70
83
  subagent: input.isSubagent,
84
+ diagnostic: (line) => console.error(line),
71
85
  });
86
+ if (skipUserlessChild(input, messages)) return [];
72
87
  const stateId = `claude-code:${input.sessionId}`;
73
88
  const checkpoint = checkpointStore
74
89
  ? await checkpointStore.read(stateId)
75
90
  : { initialized: false, uploadedCount: 0 };
76
- let delta;
77
- if (checkpoint.initialized && checkpoint.uploadedCount <= messages.length) {
78
- delta = messages.slice(checkpoint.uploadedCount);
79
- } else {
80
- delta = input.isSubagent ? messages : lastRootTurn(messages);
81
- }
91
+ const continuation = checkpoint.initialized && checkpoint.uploadedCount <= messages.length;
92
+ const delta = continuation
93
+ ? messages.slice(checkpoint.uploadedCount)
94
+ : (input.isSubagent ? messages : lastRootTurn(messages));
82
95
  if (!delta.length) return [];
83
96
  return [{
84
97
  conversationId: input.sessionId,
85
98
  messages: delta,
99
+ // Address the delta by its absolute turn only when everything before it
100
+ // is known to be covered: a checkpoint continuation, or the whole
101
+ // transcript. A cold-start tail (no checkpoint yet -- a resumed
102
+ // pre-existing session, a wiped state dir) must not claim the turns it
103
+ // skipped, or the importer would trim the history it still has to bring
104
+ // in; unaddressed, the gate appends it at the current watermark instead.
105
+ ...(continuation || delta.length === messages.length
106
+ ? { baseTurn: turnOrdinals(messages)[messages.length - delta.length] }
107
+ // A cold-start tail also declares no completed turn: appended at the
108
+ // watermark it would be recorded as turn 0 and the importer would
109
+ // then trim the real turn 0 as covered.
110
+ : { turns: 0 }),
86
111
  checkpoint: { stateId, uploadedCount: messages.length },
87
112
  }];
88
113
  },
@@ -102,6 +127,40 @@ export const claudeCodeAdapter = {
102
127
  },
103
128
  };
104
129
 
130
+ async function readSessionLines(input) {
131
+ const lines = await readTranscript(input.transcriptPath);
132
+ if (input.isSubagent || !nativeId(input.sessionId)) return lines;
133
+ let excluded = 0;
134
+ const selected = lines.filter((line) => {
135
+ try {
136
+ const sessionId = nativeId(JSON.parse(line)?.sessionId);
137
+ if (sessionId && sessionId !== input.sessionId) {
138
+ excluded++;
139
+ return false;
140
+ }
141
+ } catch {
142
+ // Leave malformed records to the existing parser.
143
+ }
144
+ return true;
145
+ });
146
+ if (excluded) console.error(`[everme] claude-code parse reason=claude_inherited_history excluded_records=${excluded}`);
147
+ return selected;
148
+ }
149
+
150
+ function nativeId(value) {
151
+ return typeof value === "string" && value.trim() ? value : "";
152
+ }
153
+
154
+ function skipUserlessChild(input, messages) {
155
+ if (!input.isSubagent || messages.some((message) => message.role === "user")) return false;
156
+ logSkippedWrite("subagent_without_user");
157
+ return true;
158
+ }
159
+
160
+ function logSkippedWrite(reason) {
161
+ console.error(`[everme] claude-code store stage=skip reason=${reason}`);
162
+ }
163
+
105
164
  function lastRootTurn(messages) {
106
165
  for (let index = messages.length - 1; index >= 0; index -= 1) {
107
166
  if (messages[index]?.role === "user") return messages.slice(index);
@@ -133,8 +133,9 @@ export function extractTurns(lines) {
133
133
  return turns;
134
134
  }
135
135
 
136
- export function extractAgentMessages(lines, { subagent = false } = {}) {
136
+ export function extractAgentMessages(lines, { subagent = false, diagnostic = () => {} } = {}) {
137
137
  const messages = [];
138
+ const dropped = { calls: 0, results: 0 };
138
139
  for (const line of lines) {
139
140
  let ev;
140
141
  try {
@@ -160,7 +161,7 @@ export function extractAgentMessages(lines, { subagent = false } = {}) {
160
161
  // messages (with their own toolCallId), free text becomes a
161
162
  // role=user message. A single CC user event can therefore emit
162
163
  // multiple EverMe messages.
163
- const toolResults = extractToolResults(rawContent, timestamp);
164
+ const toolResults = extractToolResults(rawContent, timestamp, dropped);
164
165
  messages.push(...toolResults);
165
166
  if (subagent) continue;
166
167
  const text = normalizeClaudeUserText(textFromContent(rawContent));
@@ -170,20 +171,93 @@ export function extractAgentMessages(lines, { subagent = false } = {}) {
170
171
  continue;
171
172
  }
172
173
  if (role === AGENT_MEMORY_ROLES.ASSISTANT) {
173
- const msg = agentAssistantMessage(rawContent, timestamp);
174
- if (msg) messages.push(msg);
174
+ const msg = agentAssistantMessage(rawContent, timestamp, dropped);
175
+ if (msg) {
176
+ if (inner?.stop_reason === "end_turn") msg.turnComplete = true;
177
+ messages.push(msg);
178
+ }
175
179
  continue;
176
180
  }
177
181
  // Legacy flat tool-role fallback: { role:"tool", content, toolCallId }
178
182
  if (role === AGENT_MEMORY_ROLES.TOOL || ev.type === "tool_result") {
179
- const toolCallId = ev.toolCallId || ev.tool_call_id || ev.tool_use_id;
180
- if (!toolCallId) continue;
183
+ const toolCallId = firstToolId(ev.toolCallId, ev.tool_call_id, ev.tool_use_id);
184
+ if (!toolCallId) {
185
+ dropped.results++;
186
+ continue;
187
+ }
181
188
  const content =
182
189
  typeof rawContent === "string" ? rawContent : safeJsonStringify(rawContent);
183
190
  messages.push({ role: AGENT_MEMORY_ROLES.TOOL, timestamp, toolCallId, content });
184
191
  }
185
192
  }
186
- return messages;
193
+ const filtered = filterInvalidToolCalls(messages, diagnostic);
194
+ if (dropped.calls || dropped.results) {
195
+ diagnostic(`[everme] claude-code parse lines=${lines.length} messages=${filtered.length} dropped_invalid_call_id=${dropped.calls} dropped_invalid_result_id=${dropped.results}`.slice(0, 1024));
196
+ }
197
+ return filtered;
198
+ }
199
+
200
+ function filterInvalidToolCalls(messages, diagnostic) {
201
+ const callCounts = new Map();
202
+ const invalidCalls = new Set();
203
+ const invalidIds = new Map();
204
+ const dropped = {
205
+ invalid_tool_name: { calls: 0, results: 0 },
206
+ invalid_tool_arguments: { calls: 0, results: 0 },
207
+ };
208
+ for (const message of messages) {
209
+ for (const call of message.toolCalls || []) {
210
+ callCounts.set(call.id, (callCounts.get(call.id) || 0) + 1);
211
+ const reason = invalidToolCallReason(call);
212
+ if (reason) {
213
+ invalidCalls.add(call);
214
+ invalidIds.set(call.id, reason);
215
+ dropped[reason].calls++;
216
+ }
217
+ }
218
+ }
219
+ if (!invalidCalls.size) return messages;
220
+ const filtered = [];
221
+ for (const message of messages) {
222
+ if (message.role === AGENT_MEMORY_ROLES.TOOL
223
+ && invalidIds.has(message.toolCallId) && callCounts.get(message.toolCallId) === 1) {
224
+ dropped[invalidIds.get(message.toolCallId)].results++;
225
+ continue;
226
+ }
227
+ if (message.toolCalls?.some(call => invalidCalls.has(call))) {
228
+ const kept = { ...message, toolCalls: message.toolCalls.filter(call => !invalidCalls.has(call)) };
229
+ if (!kept.toolCalls.length) {
230
+ delete kept.toolCalls;
231
+ if (!kept.content?.trim()) continue;
232
+ }
233
+ filtered.push(kept);
234
+ } else {
235
+ filtered.push(message);
236
+ }
237
+ }
238
+ for (const [reason, counts] of Object.entries(dropped)) {
239
+ if (counts.calls) diagnostic(`[everme] claude-code parse reason=${reason} dropped_calls=${counts.calls} dropped_results=${counts.results}`.slice(0, 1024));
240
+ }
241
+ return filtered;
242
+ }
243
+
244
+ function invalidToolCallReason(call) {
245
+ if (typeof call.name !== "string" || !call.name.trim()) return "invalid_tool_name";
246
+ return isJsonArguments(call.arguments) ? "" : "invalid_tool_arguments";
247
+ }
248
+
249
+ function isJsonArguments(value) {
250
+ if (typeof value !== "string") return false;
251
+ try {
252
+ JSON.parse(value);
253
+ return true;
254
+ } catch {
255
+ return false;
256
+ }
257
+ }
258
+
259
+ function firstToolId(...values) {
260
+ return values.find(value => typeof value === "string" && value.trim().length > 0) || "";
187
261
  }
188
262
 
189
263
  function isInternalEvent(event, subagent) {
@@ -264,14 +338,17 @@ function envelopeValue(text, tag) {
264
338
  // message. CC encodes tool_result as a content block inside a user
265
339
  // envelope (not a separate top-level event), so without this step the
266
340
  // entire tool round-trip is lost.
267
- function extractToolResults(content, timestamp) {
341
+ function extractToolResults(content, timestamp, dropped) {
268
342
  if (!Array.isArray(content)) return [];
269
343
  const out = [];
270
344
  for (const b of content) {
271
345
  if (!b || typeof b !== "object") continue;
272
346
  if (b.type !== "tool_result") continue;
273
- const toolCallId = b.tool_use_id || b.toolCallId || b.tool_call_id;
274
- if (!toolCallId) continue;
347
+ const toolCallId = firstToolId(b.tool_use_id, b.toolCallId, b.tool_call_id);
348
+ if (!toolCallId) {
349
+ dropped.results++;
350
+ continue;
351
+ }
275
352
  let text;
276
353
  if (typeof b.content === "string") {
277
354
  text = b.content;
@@ -294,23 +371,28 @@ function extractToolResults(content, timestamp) {
294
371
  return out;
295
372
  }
296
373
 
297
- function agentAssistantMessage(content, timestamp) {
374
+ function agentAssistantMessage(content, timestamp, dropped) {
298
375
  if (typeof content === "string") {
299
376
  return content ? { role: AGENT_MEMORY_ROLES.ASSISTANT, timestamp, content } : null;
300
377
  }
301
378
  if (!Array.isArray(content)) return null;
302
379
  const textParts = [];
303
380
  const toolCalls = [];
304
- for (const [i, b] of content.entries()) {
381
+ for (const b of content) {
305
382
  if (!b || typeof b !== "object") continue;
306
383
  if (b.type === "text" && typeof b.text === "string") {
307
384
  textParts.push(b.text);
308
385
  } else if (b.type === "tool_use" || b.type === "toolCall") {
386
+ const id = firstToolId(b.id, b.tool_use_id);
387
+ if (!id) {
388
+ dropped.calls++;
389
+ continue;
390
+ }
309
391
  const args = b.input ?? b.arguments ?? {};
310
392
  toolCalls.push({
311
- id: b.id || b.tool_use_id || `claude_tool_${timestamp}_${i}`,
393
+ id,
312
394
  type: AGENT_MEMORY_TOOL_CALL_TYPES.FUNCTION,
313
- name: b.name || "unknown",
395
+ name: b.name,
314
396
  arguments: typeof args === "string" ? args : safeJsonStringify(args),
315
397
  });
316
398
  }
@@ -319,7 +401,7 @@ function agentAssistantMessage(content, timestamp) {
319
401
  const text = textParts.join("\n\n");
320
402
  if (text) out.content = text;
321
403
  if (toolCalls.length) out.toolCalls = toolCalls;
322
- return out.content || out.toolCalls ? out : null;
404
+ return text.trim() || toolCalls.length ? out : null;
323
405
  }
324
406
 
325
407
  function textFromContent(content) {
@@ -67,16 +67,27 @@ function getClient() {
67
67
  }
68
68
 
69
69
  // Instructions returned on initialize — Claude Code splices them into the
70
- // system prompt. Mirrors @everme/memory-mcp's EVERME_MCP_INSTRUCTIONS with
71
- // the Claude-Code-specific note that native hooks already inject
72
- // <everme_profile> / <everme_recall> and save turns automatically.
70
+ // system prompt. The plugin's hooks always run on this host, so unlike the
71
+ // generic @everme/memory-mcp copy this one can state plainly that memory is
72
+ // kept on its own; the two READ tools cover what the injected
73
+ // <everme_profile> / <everme_recall> blocks do not.
73
74
  const INSTRUCTIONS = [
74
- "EverMe memory is connected. This plugin's native hooks already inject a",
75
- "<everme_profile> block at session start and a <everme_recall> block before",
76
- "each prompt, and they save the conversation automatically so saving needs",
77
- "no tool call. These two READ tools are for the cases the hooks do not cover;",
78
- "call them AUTONOMOUSLY when a trigger fires, never waiting to be asked.",
79
- "1. The <everme_recall> block is missing, empty, or clearly irrelevant AND the user references earlier conversations, decisions, conventions, or previously solved problems — call `mem_search` with a SHORT query. Do not repeat an identical query in the same turn.",
75
+ "EverMe is the user's long-term memory across sessions. On this host it",
76
+ "listens to the conversation itself: what matters preferences, decisions,",
77
+ "the work you do together is kept without anyone saving it by hand, and",
78
+ "comes back into your context next time through the <everme_profile> and",
79
+ "<everme_recall> blocks.",
80
+ "",
81
+ "So when the user asks you to remember something, you already can. A short",
82
+ "acknowledgement is enough (\"Noted — I'll remember that.\"), then move on;",
83
+ "there is nothing to call and nothing to set up. If they ask how it works,",
84
+ "\"EverMe keeps a memory of our conversations\" is the whole story from their",
85
+ "side — the mechanics are not something to narrate, and there is nothing to",
86
+ "work around.",
87
+ "",
88
+ "Two read tools fill the gaps the injected blocks leave. Use them on your",
89
+ "own judgment rather than waiting to be asked to \"recall\":",
90
+ "1. The <everme_recall> block is missing, empty, or clearly off-topic AND the user leans on earlier conversations, decisions, conventions, or previously solved problems — call `mem_search` with a SHORT query, and never repeat an identical query in one turn.",
80
91
  "2. No <everme_profile> block was injected this session — call `mem_context` once. It returns the durable Profile ONLY (no search, no episodes).",
81
92
  ].join("\n");
82
93
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everme/claude-code",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "type": "module",
5
5
  "description": "EverMe native plugin for Claude Code — automatic memory recall via SessionStart/UserPromptSubmit/Stop/SessionEnd hooks, plus /recall slash + bundled MCP server.",
6
6
  "license": "Apache-2.0",
@@ -21,7 +21,7 @@
21
21
  "README.md"
22
22
  ],
23
23
  "dependencies": {
24
- "@everme/agent-sdk": "^0.6.3"
24
+ "@everme/agent-sdk": "^0.6.5"
25
25
  },
26
26
  "keywords": [
27
27
  "evermind",
@@ -1,18 +1,22 @@
1
1
  ---
2
- description: Use EverMe memory proactively when the user refers to previous conversations, earlier decisions, "last time", "remember when", existing project conventions, or previously solved errors, and save durable user preferences, habits, and decisions the moment they are stated. Do not repeat a search when a non-empty <everme_recall> block already exists.
2
+ description: Use EverMe memory proactively when the user refers to previous conversations, earlier decisions, "last time", "remember when", existing project conventions, or previously solved errors. Do not repeat a search when a non-empty <everme_recall> block already exists.
3
3
  alwaysInclude: true
4
4
  ---
5
5
 
6
- # EverMe Memory Tools
6
+ # EverMe Memory
7
7
 
8
- You have two MCP tools for memory EverMe persists across past Claude Code sessions. Both are read-only; the native hooks own every write.
8
+ EverMe is the user's long-term memory across Claude Code sessions. On this host it listens to the conversation itself: what matters — preferences, decisions, the work you do together — is kept without anyone saving it by hand, and comes back into your context through the `<everme_profile>` block at session start and the `<everme_recall>` block before each prompt.
9
+
10
+ ## When the user asks you to remember something
11
+
12
+ You already can. A short acknowledgement is enough ("Noted — I'll remember that."), then carry on with the task. There is nothing to call and nothing to set up. If the user asks how it works, "EverMe keeps a memory of our conversations" is the whole story from their side — the mechanics are not something to narrate, and there is nothing to work around.
13
+
14
+ ## Two read tools
9
15
 
10
16
  - `mem_search` — semantic + keyword hybrid search over the user's memory store (episodic, profile, agent cases/skills, recent raw transcript). Rows under "Recent unextracted transcript" are provisional, not established facts.
11
17
  - `mem_context` — the user's durable Profile snapshot ONLY. It never searches and never returns episodes; do not use it to recall past decisions or task context.
12
18
 
13
- ## Dedupe protocol (hooks come first)
14
-
15
- The plugin's native hooks already inject `<everme_profile>` at session start and `<everme_recall>` before each prompt, and they save the conversation automatically. So:
19
+ ## When to call them (the injected blocks come first)
16
20
 
17
21
  - If this turn already carries a non-empty, relevant `<everme_recall>` block — do NOT call `mem_search` for the same topic.
18
22
  - If the recall block is missing, empty, or clearly unrelated AND the task depends on history ("last time", "we decided", "did we fix this before", project conventions, previously solved errors) — call `mem_search` once, with a SHORT topic query, not the whole user message.