@everme/claude-code 0.4.0 → 0.4.2

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.4.0",
13
+ "version": "0.4.2",
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.4.0",
3
+ "version": "0.4.2",
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
@@ -11,7 +11,7 @@ Automatic memory recall + persistence for Claude Code, backed by the EverMe gate
11
11
 
12
12
  Plus:
13
13
 
14
- - **MCP server** exposing `everme_search` + `everme_context` tools for explicit recall.
14
+ - **MCP server** exposing the canonical `mem_search` / `mem_context` / `mem_save_fact` / `mem_save_turn` tools for explicit recall and saves.
15
15
  - **Slash commands** `/recall <q>` and `/everme-help`.
16
16
  - **Skill** (`memory-tools`) that tells Claude when/how to use the search tool.
17
17
 
@@ -76,7 +76,7 @@ In a new Claude Code session:
76
76
  /recall the postgres composite index decision
77
77
  ```
78
78
 
79
- …should call `everme_search`, summarise hits, and cite memory subjects.
79
+ …should call `mem_search`, summarise hits, and cite memory subjects.
80
80
 
81
81
  ## Files
82
82
 
@@ -89,7 +89,7 @@ hooks/scripts/inject-memories.js UserPromptSubmit handler
89
89
  hooks/scripts/store-memories.js Stop handler
90
90
  hooks/scripts/session-start.js SessionStart handler
91
91
  hooks/scripts/session-summary.js SessionEnd handler
92
- hooks/scripts/mcp-server.js MCP server (everme_search / everme_context tools)
92
+ hooks/scripts/mcp-server.js MCP server (canonical mem_* tools)
93
93
  hooks/scripts/lib/adapter.js Claude Code stdin/transcript/stdout adapter
94
94
  hooks/scripts/lib/run-hook.js thin shared-runtime entry helper
95
95
  hooks/scripts/lib/config.js Env-var resolution (emk vs evt)
@@ -14,7 +14,7 @@ Auth: EVERME_API_KEY (account emk_*) — recall-only mode
14
14
  Gateway: EVERME_API_BASE — defaults to https://api.everme.evermind.ai
15
15
 
16
16
  Hooks:
17
- SessionStart → loads recent context from past sessions
17
+ SessionStart → loads the durable Profile snapshot
18
18
  UserPromptSubmit → recalls relevant memories before each prompt
19
19
  Stop → saves the last raw turn through /mem/agent-memory
20
20
  SessionEnd → no persistence; Stop owns runtime writes
@@ -24,6 +24,8 @@ Slash:
24
24
  /everme-help — this card
25
25
 
26
26
  MCP tools:
27
- everme_search ranked search
28
- everme_context server-rendered context block
27
+ mem_search hybrid search across all memory buckets
28
+ mem_context durable Profile snapshot only
29
+ mem_save_fact — save a durable user fact to the Profile path
30
+ mem_save_turn — save a reusable task trajectory
29
31
  ```
@@ -8,10 +8,10 @@ arguments:
8
8
 
9
9
  # EverMe · recall
10
10
 
11
- You have access to two MCP tools backed by the EverMe gateway:
11
+ Use the canonical EverMe MCP tools:
12
12
 
13
- - `everme_search` — ranked search with subject + summary + score.
14
- - `everme_context` — server-rendered context block (profile + recent episodes).
13
+ - `mem_search` — hybrid search across episodic memories, profile entries, agent cases/skills, and the recent raw transcript.
14
+ - `mem_context` — durable Profile snapshot only; it does not search past conversations and is not needed for this command.
15
15
 
16
16
  ## Query
17
17
 
@@ -19,8 +19,9 @@ You have access to two MCP tools backed by the EverMe gateway:
19
19
 
20
20
  ## Instructions
21
21
 
22
- 1. Call `everme_search` with the query. Start with `topK: 10`.
23
- 2. If the top results are clearly relevant (score ≥ 0.3), summarize the matched memories briefly and use them to answer or guide the next action.
22
+ 1. Call `mem_search` with a short version of the query. Start with `topK: 10`.
23
+ 2. If the returned memories are clearly relevant, summarize them briefly and use them to answer or guide the next action.
24
24
  3. If results are weak or empty, retry once with broader keywords. If still nothing useful, say so explicitly — do not fabricate context.
25
- 4. When citing a memory, mention its subject (or session id) so the user can trace it back via `evercli` or the EverMe Web UI.
26
- 5. NEVER paste the entire raw memory body verbatim if it's long; quote the salient parts only.
25
+ 4. Treat rows under "Recent unextracted transcript" as provisional, not as established facts or confirmed decisions.
26
+ 5. When citing a memory, mention its subject or session id when available so the user can trace it through `evercli` or the EverMe Web UI.
27
+ 6. NEVER paste an entire long memory body verbatim; quote only the salient parts.
@@ -1,19 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * MCP server bundled with the Claude Code plugin. Exposes the
4
- * EverMe gateway's search + context endpoints as MCP tools so users
5
- * can ALSO recall memory manually via natural language ("search my
6
- * memory for the Postgres index thing"), even when the
7
- * UserPromptSubmit hook has already done implicit recall.
3
+ * MCP server bundled with the Claude Code plugin. Exposes the canonical
4
+ * EverMe four-tool catalogue (mem_search / mem_context / mem_save_turn /
5
+ * mem_save_fact the same public ABI as @everme/memory-mcp and the Go
6
+ * hosted /mcp surface).
8
7
  *
9
8
  * Wire format: MCP stdio transport (JSON-RPC 2.0 framed by line).
10
9
  * We hand-roll the tiny subset Claude Code uses rather than pulling
11
10
  * in @modelcontextprotocol/sdk — keeps the install fast (no npm
12
- * install required) and the dependency surface minimal.
13
- *
14
- * Tools:
15
- * everme_search — POST /api/v1/mem/search
16
- * everme_context — POST /api/v1/mem/context (server-rendered prompt block)
11
+ * install required) and the dependency surface minimal. The canonical
12
+ * tools are a thin adapter over @everme/agent-sdk helpers — this
13
+ * package must NOT import @everme/memory-mcp (host packages depend on
14
+ * agent-sdk only).
17
15
  */
18
16
 
19
17
  import { createInterface } from "readline";
@@ -21,8 +19,11 @@ import { createRequire } from "node:module";
21
19
  import {
22
20
  buildMemoryPrompt,
23
21
  createClient,
22
+ getContext,
24
23
  searchMemory,
25
- renderProfileBlock,
24
+ saveAgentMemory,
25
+ savePersonalMemory,
26
+ AGENT_MEMORY_ROLES,
26
27
  redactError,
27
28
  EvermeError,
28
29
  } from "@everme/agent-sdk";
@@ -52,29 +53,229 @@ function getClient() {
52
53
  return client;
53
54
  }
54
55
 
56
+ // Instructions returned on initialize — Claude Code splices them into the
57
+ // system prompt. Mirrors @everme/memory-mcp's EVERME_MCP_INSTRUCTIONS with
58
+ // the Claude-Code-specific note that native hooks already inject
59
+ // <everme_profile> / <everme_recall> and save turns automatically.
60
+ const INSTRUCTIONS = [
61
+ "EverMe memory is connected. This plugin's native hooks already inject a",
62
+ "<everme_profile> block at session start and a <everme_recall> block before",
63
+ "each prompt, and they save the conversation automatically — so the tools",
64
+ "below are for the cases the hooks do not cover. Call them AUTONOMOUSLY when",
65
+ "a trigger fires; never wait for the user to say \"remember\" or \"recall\".",
66
+ "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.",
67
+ "2. The user states a durable fact about themselves (a preference, habit, trait, long-term goal, or decision) — call `mem_save_fact` immediately. Only `extracted:true` / `profileUpdated:true` means the profile really updated; on `no_extraction` do NOT tell the user the fact was remembered, and do not auto-retry.",
68
+ "3. A task was solved in a way worth reusing and the trajectory is NOT already captured by the hooks — call `mem_save_turn` with the COMPLETE trajectory. It feeds episodic / case / skill extraction; chat-dual-write backends may also update the Profile, reported by profileUpdated.",
69
+ "4. No <everme_profile> block was injected this session — call `mem_context` once. It returns the durable Profile ONLY (no search, no episodes).",
70
+ ].join("\n");
71
+
55
72
  const TOOLS = [
56
73
  {
57
- name: "everme_search",
74
+ name: "mem_search",
58
75
  description:
59
- "Search EverMe memories from past sessions. Returns ranked memory items with subject, summary, and relevance score. Use when the user asks about previous work, decisions, or context. Params: query (required), topK (default 10, max 25).",
76
+ "Search EverMe memory for entries relevant to a free-text query. " +
77
+ "Returns the top-K matching entries (episodic, profile, agent " +
78
+ "cases/skills, recent raw transcript) rendered as markdown; rows " +
79
+ "under the provisional transcript header are not yet extracted and " +
80
+ "must not be quoted as established facts.\n\n" +
81
+ "Call this proactively — without being asked — whenever the user " +
82
+ "references prior conversations, earlier decisions, project " +
83
+ "conventions, or previously solved problems (\"what did we say " +
84
+ "about X\", \"remember when…\", \"like last time\", \"did we fix " +
85
+ "this before\", \"continue where we left off\").\n\n" +
86
+ "Skip the call when the host already injected a non-empty, relevant " +
87
+ "<everme_recall> block this turn, and do not repeat an identical " +
88
+ "query within the same turn.\n\n" +
89
+ "`query` is KEYWORDS ONLY — the topic, not the conversation. Two to " +
90
+ "eight words, under ~100 characters, no sentences copied from the " +
91
+ "transcript. Never pass the user's whole message, a file, a log, a " +
92
+ "diff, or your own reasoning: the search embeds whatever you send, " +
93
+ "so boilerplate crowds out the topic and the results get worse. " +
94
+ "Good: \"oauth token rotation\". Bad: the last three turns pasted " +
95
+ "in. Rely on the default topK of 10; only raise it if a first " +
96
+ "search genuinely missed.",
60
97
  inputSchema: {
61
98
  type: "object",
62
99
  properties: {
63
- query: { type: "string", description: "Search query — keywords or a question" },
64
- topK: { type: "number", description: "Max results (default 10, max 25)" },
100
+ query: {
101
+ type: "string",
102
+ description:
103
+ "Keywords naming the topic to recall — two to eight words, " +
104
+ "under ~100 characters. Not a sentence from the transcript, " +
105
+ "not the user's whole message, not a pasted file or log.",
106
+ },
107
+ topK: { type: "integer", description: "Max entries to return", default: 10 },
65
108
  },
66
109
  required: ["query"],
67
110
  },
68
111
  },
69
112
  {
70
- name: "everme_context",
113
+ name: "mem_context",
114
+ description:
115
+ "Read the current user's durable Profile snapshot ONLY. This tool " +
116
+ "never performs semantic search and never returns episodic memories, " +
117
+ "raw messages, agent cases, or agent skills.\n\n" +
118
+ "Call it ONCE at the start of a session, and only when no " +
119
+ "<everme_profile> block was injected. Do NOT use it as a fallback for " +
120
+ "recalling past decisions, old sessions, or task context — that is " +
121
+ "mem_search's job.",
122
+ inputSchema: {
123
+ type: "object",
124
+ properties: {
125
+ query: {
126
+ type: "string",
127
+ description:
128
+ "Deprecated and ignored — mem_context never performs semantic " +
129
+ "search. Kept for backwards compatibility only.",
130
+ },
131
+ forceRefresh: {
132
+ type: "boolean",
133
+ default: false,
134
+ description: "Bypass the server-side profile cache and re-read the upstream profile.",
135
+ },
136
+ },
137
+ },
138
+ },
139
+ {
140
+ name: "mem_save_turn",
141
+ description:
142
+ "Persist a conversation trajectory in realtime via /mem/agent-memory. " +
143
+ "Use sessionKey as conversationId.\n\n" +
144
+ "Call this when a task was solved in a way worth reusing AND the " +
145
+ "trajectory is not already captured by the plugin's automatic " +
146
+ "transcript save. Pass the COMPLETE round-trip — messages: [{role, " +
147
+ "content, timestamp?, toolCalls?, toolCallId?}] — EverOS only " +
148
+ "extracts agent_case / agent_skill from trajectories carrying the " +
149
+ "full tool round-trip.\n\n" +
150
+ "By default flush=true: extraction into episodic / agent_case / " +
151
+ "agent_skill runs right away. Pass flush=false for append-only " +
152
+ "accumulation. The primary trajectory path extracts episodic / case / " +
153
+ "skill memory; chat-dual-write backends may also update the user's " +
154
+ "Profile. Check profileUpdated for the derived profile verdict, and " +
155
+ "use mem_save_fact for deliberate durable user facts.",
156
+ inputSchema: {
157
+ type: "object",
158
+ properties: {
159
+ role: {
160
+ type: "string",
161
+ enum: ["user", "assistant", "tool"],
162
+ description: "Role for the single-message form. Ignored when messages[] is set.",
163
+ },
164
+ text: { type: "string", description: "Content for the single-message form. Ignored when messages[] is set." },
165
+ timestamp: { type: "integer", description: "Unix milliseconds; defaults to now. Single-message form only." },
166
+ toolCallId: { type: "string", description: "Required when role=tool. Single-message form only." },
167
+ toolCalls: {
168
+ type: "array",
169
+ description:
170
+ "Tool invocations made by an assistant message. Required when " +
171
+ "the assistant called tools — without this the tool round-trip " +
172
+ "can never be reconstructed downstream and EverOS will not " +
173
+ "produce agent_case / agent_skill from the turn.",
174
+ items: {
175
+ type: "object",
176
+ properties: {
177
+ id: { type: "string" },
178
+ name: { type: "string" },
179
+ arguments: { type: "string", description: "JSON-encoded tool arguments (stringified, not an object)." },
180
+ },
181
+ required: ["id", "name", "arguments"],
182
+ },
183
+ },
184
+ messages: {
185
+ type: "array",
186
+ description:
187
+ "Multi-message trajectory. Preferred for recording a complete " +
188
+ "user → assistant{tool_use} → tool{tool_result} → assistant cycle.",
189
+ items: {
190
+ type: "object",
191
+ properties: {
192
+ role: { type: "string", enum: ["user", "assistant", "tool"] },
193
+ content: { description: "String text, or array of content items (text / image / doc)." },
194
+ timestamp: { type: "integer" },
195
+ toolCallId: { type: "string", description: "Required when role=tool." },
196
+ toolCalls: {
197
+ type: "array",
198
+ items: {
199
+ type: "object",
200
+ properties: {
201
+ id: { type: "string" },
202
+ name: { type: "string" },
203
+ arguments: { type: "string" },
204
+ },
205
+ required: ["id", "name", "arguments"],
206
+ },
207
+ },
208
+ },
209
+ required: ["role"],
210
+ },
211
+ },
212
+ sessionKey: { type: "string", description: "Session id; defaults to 'default'." },
213
+ flush: {
214
+ type: "boolean",
215
+ default: true,
216
+ description:
217
+ "true (default) = trigger EverOS extraction into " +
218
+ "episodic_memory / agent_case / agent_skill after writing; " +
219
+ "false = append-only, messages are searchable as raw_messages " +
220
+ "only and extraction is deferred until a later flush. The " +
221
+ "default flipped from false to true because callers rarely " +
222
+ "issued an explicit follow-up flush, leaving trajectories " +
223
+ "permanently stuck as raw_messages with zero case/skill.",
224
+ },
225
+ },
226
+ },
227
+ },
228
+ {
229
+ name: "mem_save_fact",
71
230
  description:
72
- "Fetch the server-rendered context block (profile + recent episodes) the gateway uses for prompt injection. Useful when you want a single ready-to-paste summary. Params: query (optional), topK (default 10).",
231
+ "Persist a durable fact about the USER (a preference, habit, trait, " +
232
+ "or decision) via the long-term PROFILE write path — the block loaded " +
233
+ "at the start of every session.\n\n" +
234
+ "Call this proactively, without being asked, the moment the user " +
235
+ "states something true about themselves that should outlive this " +
236
+ "conversation (\"I love summer\", \"sign my docs as Alice\").\n\n" +
237
+ "This is the direct profile-producing sibling of mem_save_turn — " +
238
+ "mem_save_turn primarily records trajectories, though chat-dual-write " +
239
+ "backends may derive a profile update. With flush=true (default) " +
240
+ "the call runs the synchronous materialise path and returns the real " +
241
+ "EverOS verdict: only `extracted:true` / `profileUpdated:true` " +
242
+ "confirms the fact reached the profile. `status:\"no_extraction\"` " +
243
+ "means the profile did NOT update — never claim the fact was " +
244
+ "remembered in that case, and do not auto-retry.",
73
245
  inputSchema: {
74
246
  type: "object",
75
247
  properties: {
76
- query: { type: "string", description: "Optional query for relevance-biased context" },
77
- topK: { type: "number", description: "Max items to include (default 10)" },
248
+ fact: {
249
+ type: "string",
250
+ description:
251
+ "A single user-stated fact, recorded as one user-role message. " +
252
+ "Ignored when messages[] is set.",
253
+ },
254
+ messages: {
255
+ type: "array",
256
+ description:
257
+ "Explicit user/assistant turns. Tool roles are not accepted on " +
258
+ "this path. Preferred when you want to capture both the user's " +
259
+ "statement and your acknowledgement.",
260
+ items: {
261
+ type: "object",
262
+ properties: {
263
+ role: { type: "string", enum: ["user", "assistant"] },
264
+ content: { description: "String text, or array of content items." },
265
+ timestamp: { type: "integer", description: "Unix milliseconds; defaults to now." },
266
+ },
267
+ required: ["role"],
268
+ },
269
+ },
270
+ sessionKey: { type: "string", description: "Session id; defaults to 'default'." },
271
+ flush: {
272
+ type: "boolean",
273
+ default: true,
274
+ description:
275
+ "true (default) = issue EverOS flush and return its verdict; " +
276
+ "false = skip extraction entirely (fact accepted but not in the " +
277
+ "profile block).",
278
+ },
78
279
  },
79
280
  },
80
281
  },
@@ -90,6 +291,7 @@ const handlers = {
90
291
  protocolVersion,
91
292
  capabilities: { tools: { listChanged: false } },
92
293
  serverInfo: { name: "everme", version: PKG_VERSION },
294
+ instructions: INSTRUCTIONS,
93
295
  };
94
296
  },
95
297
  "tools/list": () => ({ tools: TOOLS }),
@@ -101,14 +303,8 @@ const handlers = {
101
303
  }
102
304
  try {
103
305
  switch (name) {
104
- case "everme_search": {
105
- // Render the SDK bundle through buildMemoryPrompt — same
106
- // path inject-memories.js uses for the auto-recall hook,
107
- // and the same shape @everme/memory-mcp's mem_search tool
108
- // returns. Earlier `JSON.stringify(res, null, 2)` forced the
109
- // host LLM to peel a JSON envelope and decode escaped
110
- // newlines before any of the section bullets were readable.
111
- const topK = Math.min(Number(args.topK) || 10, 25);
306
+ case "mem_search": {
307
+ const topK = Math.min(Number(args.topK) || 10, 50);
112
308
  const res = await searchMemory(getClient(), { query: String(args.query || ""), topK });
113
309
  const body = buildMemoryPrompt(res, { wrapInCodeBlock: false });
114
310
  const header = `## EverMe search results for "${String(args.query || "")}"`;
@@ -118,23 +314,86 @@ const handlers = {
118
314
  : `${header}\n\n_(no matching memories)_`;
119
315
  return ok(redactError(text));
120
316
  }
121
- case "everme_context": {
122
- // getContext returns the gateway's raw shape
123
- // {profile, cachedAt, generatedAt} — the markdown lives in
124
- // res.profile as a structured object, NOT a string. Render
125
- // via renderProfileBlock (same renderer session-start.js
126
- // uses for the SessionStart hook injection) so the Tools
127
- // path matches what users already see in the injected
128
- // <everme_profile> block.
129
- const res = await getClient().request("POST", "/mem/context", {});
130
- // renderProfileBlock returns "" when profile exists but has no
131
- // facts/traits yet (new account). Check the rendered output, not
132
- // just the wrapper object, so the empty case yields a fallback
133
- // message instead of an empty tool result.
134
- const rendered = res?.profile ? renderProfileBlock(res.profile) : "";
135
- const text = rendered || "_(no profile available — your EverMe account has no extracted memories yet)_";
317
+ case "mem_context": {
318
+ // Profile-only: `query` is accepted for compat but ignored.
319
+ const ctx = await getContext(
320
+ getClient(),
321
+ "",
322
+ { forceRefresh: args.forceRefresh === true },
323
+ );
324
+ const text = ctx?.context || "_(no profile available — your EverMe account has no extracted memories yet)_";
136
325
  return ok(redactError(text));
137
326
  }
327
+ case "mem_save_turn": {
328
+ let messages;
329
+ if (Array.isArray(args.messages) && args.messages.length) {
330
+ messages = args.messages.map(normaliseTurnMessage);
331
+ } else {
332
+ messages = [normaliseTurnMessage({
333
+ role: args.role || AGENT_MEMORY_ROLES.USER,
334
+ content: args.text,
335
+ timestamp: args.timestamp,
336
+ toolCallId: args.toolCallId,
337
+ toolCalls: args.toolCalls,
338
+ })];
339
+ }
340
+ const res = await saveAgentMemory(getClient(), {
341
+ conversationId: args.sessionKey || "default",
342
+ messages,
343
+ flush: args.flush !== false,
344
+ });
345
+ return okJson({
346
+ saved: !!res,
347
+ accepted: !!res,
348
+ status: res?.status || null,
349
+ messageCount: res?.messageCount || 0,
350
+ flushed: !!res?.flushed,
351
+ profileStatus: res?.personalStatus || null,
352
+ profileUpdated: !!res?.personalExtracted,
353
+ });
354
+ }
355
+ case "mem_save_fact": {
356
+ let messages;
357
+ if (Array.isArray(args.messages) && args.messages.length) {
358
+ const bad = args.messages.find(
359
+ (m) => m?.role !== AGENT_MEMORY_ROLES.USER && m?.role !== AGENT_MEMORY_ROLES.ASSISTANT,
360
+ );
361
+ if (bad) {
362
+ return errResp(
363
+ `mem_save_fact accepts only 'user' or 'assistant' roles; got ${JSON.stringify(bad?.role)}. ` +
364
+ "The personal-memory path does not record tool turns — use mem_save_turn for trajectories.",
365
+ );
366
+ }
367
+ messages = args.messages.map((m) => ({
368
+ role: m?.role,
369
+ content: m?.content !== undefined ? m.content : m?.text,
370
+ timestamp: Number(m?.timestamp) || Date.now(),
371
+ }));
372
+ } else if (typeof args.fact === "string" && args.fact.trim()) {
373
+ messages = [{ role: AGENT_MEMORY_ROLES.USER, content: args.fact, timestamp: Date.now() }];
374
+ } else {
375
+ return errResp("mem_save_fact requires either `fact` or a non-empty `messages` array");
376
+ }
377
+ const res = await savePersonalMemory(getClient(), {
378
+ conversationId: args.sessionKey || "default",
379
+ messages,
380
+ flush: args.flush !== false,
381
+ });
382
+ if (!res) {
383
+ return errResp("mem_save_fact wrote nothing — every message had empty content after normalization");
384
+ }
385
+ return okJson({
386
+ saved: true,
387
+ accepted: true,
388
+ status: res?.status || null,
389
+ messageCount: res?.messageCount || 0,
390
+ flushed: !!res?.flushed,
391
+ extracted: !!res?.extracted,
392
+ // profileUpdated aliases extracted — the only signal that the
393
+ // fact really materialised into the profile.
394
+ profileUpdated: !!res?.extracted,
395
+ });
396
+ }
138
397
  default:
139
398
  return errResp(`unknown tool: ${name}`);
140
399
  }
@@ -148,10 +407,30 @@ const handlers = {
148
407
  function ok(text) {
149
408
  return { content: [{ type: "text", text: String(text ?? "") }] };
150
409
  }
410
+ function okJson(data) {
411
+ return { content: [{ type: "text", text: JSON.stringify(data ?? {}, null, 2) }] };
412
+ }
151
413
  function errResp(msg) {
152
414
  return { isError: true, content: [{ type: "text", text: `error: ${msg}` }] };
153
415
  }
154
416
 
417
+ // normaliseTurnMessage coerces an LLM-provided message into the SDK
418
+ // agent-memory shape — accepts both legacy {role, text} and canonical
419
+ // {role, content, toolCalls, toolCallId} forms. Mirrors the equivalent
420
+ // helper in @everme/memory-mcp (kept local: host packages must not
421
+ // import each other).
422
+ function normaliseTurnMessage(m) {
423
+ const role = m?.role || AGENT_MEMORY_ROLES.USER;
424
+ const out = { role, timestamp: Number(m?.timestamp) || Date.now() };
425
+ if (m?.content !== undefined) out.content = m.content;
426
+ else if (m?.text !== undefined) out.content = String(m.text);
427
+ if (Array.isArray(m?.toolCalls) && m.toolCalls.length) out.toolCalls = m.toolCalls;
428
+ if (role === AGENT_MEMORY_ROLES.TOOL && (m?.toolCallId || m?.tool_call_id)) {
429
+ out.toolCallId = String(m.toolCallId || m.tool_call_id);
430
+ }
431
+ return out;
432
+ }
433
+
155
434
  const rl = createInterface({ input: process.stdin, terminal: false });
156
435
  rl.on("line", async (line) => {
157
436
  let req;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everme/claude-code",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
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",
@@ -8,7 +8,7 @@
8
8
  "node": ">=18.0.0"
9
9
  },
10
10
  "scripts": {
11
- "test": "node --test tests/redact.test.js tests/config.test.js tests/transcript.test.js tests/hooks.test.js tests/mcp-server.test.js"
11
+ "test": "node --test tests/redact.test.js tests/config.test.js tests/transcript.test.js tests/hooks.test.js tests/mcp-server.test.js tests/skills.test.js"
12
12
  },
13
13
  "files": [
14
14
  ".claude-plugin/",
@@ -21,7 +21,7 @@
21
21
  "README.md"
22
22
  ],
23
23
  "dependencies": {
24
- "@everme/agent-sdk": "^0.4.0"
24
+ "@everme/agent-sdk": "^0.4.2"
25
25
  },
26
26
  "keywords": [
27
27
  "evermind",
@@ -1,30 +1,32 @@
1
1
  ---
2
- description: How and when to use EverMe memory tools to bring past-session context into the current Claude Code conversation.
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.
3
3
  alwaysInclude: true
4
4
  ---
5
5
 
6
6
  # EverMe Memory Tools
7
7
 
8
- You have two MCP tools that surface memory persisted by EverMe across past Claude Code sessions:
8
+ You have four canonical MCP tools for memory EverMe persists across past Claude Code sessions:
9
9
 
10
- - `everme_search` — semantic + keyword hybrid search over the user's memory store. Returns ranked items with subject, summary, score.
11
- - `everme_context` — fetch a server-rendered context block (profile + recent episodes) ready to inject into the current turn.
10
+ Recall:
11
+ - `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.
12
+ - `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
13
 
13
- The plugin's UserPromptSubmit hook already injects relevant memory automatically before each prompt. You usually do NOT need to call these tools manually — they're for cases the auto-recall missed.
14
+ Write:
15
+ - `mem_save_fact` — save a durable user fact (preference, habit, trait, long-term decision). Call it proactively the moment the user states one ("以后文档签名用 Alice", "I hate Friday meetings") — do NOT wait for the user to say "remember this".
16
+ - `mem_save_turn` — persist a complete task trajectory worth reusing. Chat-dual-write backends may also update the user's Profile; check `profileUpdated`. Use `mem_save_fact` for a deliberate durable fact.
14
17
 
15
- ## When to use these tools
18
+ ## Dedupe protocol (hooks come first)
16
19
 
17
- **Do call** when:
18
- - The user references something they discussed before ("last time", "remember when", "we decided to use X")
19
- - The user asks about a project pattern, decision, or convention you have no inline context for
20
- - You're debugging an error message that may have been seen + resolved before
21
- - The auto-recall block (`<everme_recall>...</everme_recall>` in your context) is empty or clearly unrelated to the current task
22
- - The user explicitly asks you to "search my memory" / "recall" / "look up"
20
+ 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:
23
21
 
24
- **Do NOT call** when:
25
- - The current message is self-contained and you can answer from inline context
26
- - You already searched in the current turn (don't duplicate)
27
- - It's a general-knowledge question with no project history component
22
+ - If this turn already carries a non-empty, relevant `<everme_recall>` block — do NOT call `mem_search` for the same topic.
23
+ - 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.
24
+ - Never repeat an identical query within the same turn.
25
+ - Call `mem_context` only when no `<everme_profile>` block was injected this session.
26
+
27
+ ## Save honesty
28
+
29
+ `mem_save_fact` returns the real extraction verdict. Only `extracted: true` / `profileUpdated: true` means the profile updated — then you may tell the user the fact is remembered. On `status: "no_extraction"` the profile did NOT update: say so plainly, do not auto-retry, and do not claim success.
28
30
 
29
31
  ## Best practices
30
32