@jcyamacho/agent-memory 0.0.18 → 0.0.20

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.
Files changed (3) hide show
  1. package/README.md +15 -9
  2. package/dist/index.js +98 -46
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,10 +6,11 @@ Persistent memory for MCP-powered coding agents.
6
6
  by SQLite. It helps your agent remember preferences, project context, and prior
7
7
  decisions across sessions.
8
8
 
9
- It exposes four tools:
9
+ It exposes five tools:
10
10
 
11
11
  - `remember` -> save facts, decisions, preferences, and project context
12
12
  - `recall` -> retrieve the most relevant memories later
13
+ - `review` -> browse all memories for a workspace
13
14
  - `revise` -> update an existing memory when it becomes outdated
14
15
  - `forget` -> delete a memory that is no longer relevant
15
16
 
@@ -49,10 +50,13 @@ Optional LLM instructions to reinforce the MCP's built-in guidance:
49
50
  ## Agent Memory
50
51
 
51
52
  - Use `memory_recall` at conversation start and before design choices,
52
- conventions, or edge cases.
53
+ conventions, edge cases, or saving memory.
53
54
  - Query `memory_recall` with 2-5 short anchor-heavy terms or exact phrases,
54
55
  not full questions or sentences.
55
- - Pass `workspace` for project-scoped memory. Omit it only for truly global memories.
56
+ - Use `memory_review` to browse all memories for a workspace before bulk review
57
+ or cleanup.
58
+ - Pass `workspace` for project-scoped memory. Omit it only for facts that
59
+ apply across projects.
56
60
  - Use `memory_remember` to save one durable fact when the user states a stable
57
61
  preference, correction, or reusable project decision.
58
62
  - If the fact already exists, use `memory_revise` instead of creating a duplicate.
@@ -83,15 +87,15 @@ memories:
83
87
 
84
88
  1. **Text relevance** is the primary signal -- memories whose content best
85
89
  matches your search terms rank highest.
86
- 2. **Embedding similarity** is the next strongest signal. Recall builds an
87
- embedding from your normalized search terms and boosts memories whose stored
90
+ 2. **Workspace match** is the next strongest signal. When you pass
91
+ `workspace`, exact matches rank highest and all other scoped workspaces rank
92
+ below exact matches.
93
+ 3. **Embedding similarity** is a secondary signal. Recall builds an embedding
94
+ from your normalized search terms and boosts memories whose stored
88
95
  embeddings are most semantically similar.
89
- 3. **Workspace match** is a strong secondary signal. When you pass
90
- `workspace`, exact matches rank highest, sibling repositories get a small
91
- boost, and unrelated workspaces rank lowest.
92
96
  4. **Global memories** (saved without a workspace) are treated as relevant
93
97
  everywhere. When you pass `workspace`, they rank below exact workspace
94
- matches and above sibling or unrelated repositories.
98
+ matches and above memories from other workspaces.
95
99
  5. **Recency** is a minor tiebreaker -- newer memories rank slightly above older
96
100
  ones when other signals are equal.
97
101
 
@@ -102,6 +106,8 @@ memories without a workspace only when they apply across all projects.
102
106
  When you save a memory from a git worktree, `agent-memory` stores the main repo
103
107
  root as the workspace. `recall` applies the same normalization to incoming
104
108
  workspace queries so linked worktrees still match repo-scoped memories exactly.
109
+ When that happens, recall returns the queried workspace value so callers can
110
+ treat the match as belonging to their current worktree context.
105
111
 
106
112
  ## Configuration
107
113
 
package/dist/index.js CHANGED
@@ -12464,7 +12464,7 @@ class StdioServerTransport {
12464
12464
  }
12465
12465
  }
12466
12466
  // package.json
12467
- var version2 = "0.0.18";
12467
+ var version2 = "0.0.20";
12468
12468
 
12469
12469
  // src/config.ts
12470
12470
  import { homedir } from "node:os";
@@ -12533,14 +12533,20 @@ class PersistenceError extends MemoryError {
12533
12533
 
12534
12534
  // src/embedding/service.ts
12535
12535
  var DEFAULT_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2";
12536
+ function configureModelsCache(modelsCachePath) {
12537
+ mkdirSync(modelsCachePath, { recursive: true });
12538
+ transformersEnv.useFSCache = true;
12539
+ transformersEnv.cacheDir = modelsCachePath;
12540
+ }
12536
12541
 
12537
12542
  class EmbeddingService {
12538
12543
  options;
12539
12544
  extractorPromise;
12540
- modelsCachePath;
12541
12545
  constructor(options = {}) {
12542
12546
  this.options = options;
12543
- this.modelsCachePath = options.modelsCachePath ?? resolveModelsCachePath();
12547
+ }
12548
+ async warmup() {
12549
+ await this.getExtractor();
12544
12550
  }
12545
12551
  async createVector(text) {
12546
12552
  const normalizedText = text.trim();
@@ -12553,17 +12559,11 @@ class EmbeddingService {
12553
12559
  }
12554
12560
  getExtractor() {
12555
12561
  if (!this.extractorPromise) {
12556
- configureModelsCache(this.modelsCachePath);
12557
12562
  this.extractorPromise = (this.options.createExtractor ?? createDefaultExtractor)();
12558
12563
  }
12559
12564
  return this.extractorPromise;
12560
12565
  }
12561
12566
  }
12562
- function configureModelsCache(modelsCachePath) {
12563
- mkdirSync(modelsCachePath, { recursive: true });
12564
- transformersEnv.useFSCache = true;
12565
- transformersEnv.cacheDir = modelsCachePath;
12566
- }
12567
12567
  async function createDefaultExtractor() {
12568
12568
  const extractor = await pipeline("feature-extraction", DEFAULT_EMBEDDING_MODEL);
12569
12569
  return (text) => extractor(text, {
@@ -20086,15 +20086,14 @@ function registerForgetTool(server, memory) {
20086
20086
  var toNormalizedScore = (value) => value;
20087
20087
 
20088
20088
  // src/ranking.ts
20089
- var RETRIEVAL_SCORE_WEIGHT = 8;
20090
- var EMBEDDING_SIMILARITY_WEIGHT = 5;
20091
- var WORKSPACE_MATCH_WEIGHT = 4;
20092
- var RECENCY_WEIGHT = 2;
20089
+ var RETRIEVAL_SCORE_WEIGHT = 9;
20090
+ var EMBEDDING_SIMILARITY_WEIGHT = 4;
20091
+ var WORKSPACE_MATCH_WEIGHT = 5;
20092
+ var RECENCY_WEIGHT = 1;
20093
20093
  var MAX_COMPOSITE_SCORE = RETRIEVAL_SCORE_WEIGHT + EMBEDDING_SIMILARITY_WEIGHT + WORKSPACE_MATCH_WEIGHT + RECENCY_WEIGHT;
20094
20094
  var GLOBAL_WORKSPACE_SCORE = 0.5;
20095
- var SIBLING_WORKSPACE_SCORE = 0.25;
20096
20095
  function rerankSearchResults(results, workspace, queryEmbedding) {
20097
- if (results.length <= 1) {
20096
+ if (results.length === 0) {
20098
20097
  return results;
20099
20098
  }
20100
20099
  const normalizedQueryWs = workspace ? normalizeWorkspacePath(workspace) : undefined;
@@ -20132,14 +20131,7 @@ function computeWorkspaceScore(memoryWs, queryWs) {
20132
20131
  if (normalizedMemoryWs === queryWs) {
20133
20132
  return 1;
20134
20133
  }
20135
- const queryLastSlashIndex = queryWs.lastIndexOf("/");
20136
- const memoryLastSlashIndex = normalizedMemoryWs.lastIndexOf("/");
20137
- if (queryLastSlashIndex <= 0 || memoryLastSlashIndex <= 0) {
20138
- return 0;
20139
- }
20140
- const queryParent = queryWs.slice(0, queryLastSlashIndex);
20141
- const memoryParent = normalizedMemoryWs.slice(0, memoryLastSlashIndex);
20142
- return memoryParent === queryParent ? SIBLING_WORKSPACE_SCORE : 0;
20134
+ return 0;
20143
20135
  }
20144
20136
 
20145
20137
  // src/memory-service.ts
@@ -20193,6 +20185,7 @@ class MemoryService {
20193
20185
  return memory ? toPublicMemoryRecord(memory) : undefined;
20194
20186
  }
20195
20187
  async list(input) {
20188
+ const queryWorkspace = normalizeOptionalString(input.workspace);
20196
20189
  const workspace = await this.workspaceResolver.resolve(input.workspace);
20197
20190
  const page = await this.repository.list({
20198
20191
  workspace,
@@ -20200,7 +20193,10 @@ class MemoryService {
20200
20193
  offset: normalizeOffset(input.offset),
20201
20194
  limit: normalizeListLimit(input.limit)
20202
20195
  });
20203
- return toPublicMemoryPage(page);
20196
+ return {
20197
+ items: page.items.map((item) => toPublicMemoryRecord(remapWorkspace(item, workspace, queryWorkspace))),
20198
+ hasMore: page.hasMore
20199
+ };
20204
20200
  }
20205
20201
  async listWorkspaces() {
20206
20202
  return this.repository.listWorkspaces();
@@ -20211,6 +20207,7 @@ class MemoryService {
20211
20207
  throw new ValidationError("At least one search term is required.");
20212
20208
  }
20213
20209
  const requestedLimit = normalizeLimit(input.limit);
20210
+ const queryWorkspace = normalizeOptionalString(input.workspace);
20214
20211
  const workspace = await this.workspaceResolver.resolve(input.workspace);
20215
20212
  const normalizedQuery = {
20216
20213
  terms,
@@ -20222,7 +20219,7 @@ class MemoryService {
20222
20219
  this.repository.search(normalizedQuery),
20223
20220
  this.embeddingService.createVector(terms.join(" "))
20224
20221
  ]);
20225
- return rerankSearchResults(results, workspace, queryEmbedding).slice(0, requestedLimit).map(toPublicSearchResult);
20222
+ return rerankSearchResults(results, workspace, queryEmbedding).slice(0, requestedLimit).map((result) => toPublicSearchResult(remapWorkspace(result, workspace, queryWorkspace)));
20226
20223
  }
20227
20224
  }
20228
20225
  function toPublicMemoryRecord(memory) {
@@ -20244,12 +20241,6 @@ function toPublicSearchResult(result) {
20244
20241
  updatedAt: result.updatedAt
20245
20242
  };
20246
20243
  }
20247
- function toPublicMemoryPage(page) {
20248
- return {
20249
- items: page.items.map(toPublicMemoryRecord),
20250
- hasMore: page.hasMore
20251
- };
20252
- }
20253
20244
  function normalizeLimit(value) {
20254
20245
  if (value === undefined) {
20255
20246
  return DEFAULT_RECALL_LIMIT;
@@ -20272,12 +20263,22 @@ function normalizeTerms(terms) {
20272
20263
  const normalizedTerms = terms.map((term) => term.trim()).filter(Boolean);
20273
20264
  return [...new Set(normalizedTerms)];
20274
20265
  }
20266
+ function normalizeOptionalString(value) {
20267
+ const trimmed = value?.trim();
20268
+ return trimmed ? trimmed : undefined;
20269
+ }
20270
+ function remapWorkspace(record3, canonicalWorkspace, queryWorkspace) {
20271
+ if (record3.workspace !== canonicalWorkspace) {
20272
+ return record3;
20273
+ }
20274
+ return { ...record3, workspace: queryWorkspace };
20275
+ }
20275
20276
 
20276
20277
  // src/mcp/tools/recall.ts
20277
20278
  var recallInputSchema = {
20278
- terms: array(string2()).min(1).describe("Pass 2-5 short anchor-heavy terms or exact phrases as separate entries. Prefer identifiers, commands, file paths, package names, and conventions likely to appear verbatim. Avoid vague words, full questions, and repeating the workspace name."),
20279
+ terms: array(string2()).min(1).describe("2-5 short anchor-heavy terms or exact phrases. Prefer identifiers, commands, file paths, and exact wording likely to appear in the memory."),
20279
20280
  limit: number2().int().min(1).max(MAX_RECALL_LIMIT).optional().describe("Maximum matches to return. Keep this small when you only need the strongest hits."),
20280
- workspace: string2().optional().describe("Pass the current working directory to prefer memories from the active project. Git worktree paths are normalized to the main repo root for matching."),
20281
+ workspace: string2().optional().describe("Current working directory for project-scoped recall. Omit for cross-project recall."),
20281
20282
  updated_after: string2().optional().describe("Only return memories updated on or after this ISO 8601 timestamp."),
20282
20283
  updated_before: string2().optional().describe("Only return memories updated on or before this ISO 8601 timestamp.")
20283
20284
  };
@@ -20296,7 +20297,7 @@ function registerRecallTool(server, memory) {
20296
20297
  readOnlyHint: true,
20297
20298
  openWorldHint: false
20298
20299
  },
20299
- description: "Find memories relevant to the current task or check whether a fact already exists before saving. Use at conversation start and before design choices. Pass short anchor-heavy `terms` and `workspace` when available. Returns `<memories>...</memories>` or a no-match hint.",
20300
+ description: "Retrieve memories relevant to the current task or check whether a fact already exists before saving. Use at conversation start and before design choices. Pass short anchor-heavy `terms` and `workspace` when available. Results reflect the queried workspace context when applicable. Returns `<memories>...</memories>` or a no-match hint.",
20300
20301
  inputSchema: recallInputSchema
20301
20302
  }, async ({ terms, limit, workspace, updated_after, updated_before }) => {
20302
20303
  try {
@@ -20323,7 +20324,7 @@ ${results.map(toMemoryXml).join(`
20323
20324
  // src/mcp/tools/remember.ts
20324
20325
  var rememberInputSchema = {
20325
20326
  content: string2().describe("One new durable fact to save. Use a self-contained sentence or short note."),
20326
- workspace: string2().optional().describe("Pass the current working directory for project-scoped memory. Git worktree paths are saved as the main repo root. Omit for truly global memory.")
20327
+ workspace: string2().optional().describe("Current working directory for project-scoped memory. Omit for facts that apply across projects.")
20327
20328
  };
20328
20329
  function registerRememberTool(server, memory) {
20329
20330
  server.registerTool("remember", {
@@ -20333,7 +20334,7 @@ function registerRememberTool(server, memory) {
20333
20334
  idempotentHint: false,
20334
20335
  openWorldHint: false
20335
20336
  },
20336
- description: 'Save one new durable fact for later recall. Use for stable preferences, corrections, reusable decisions, and project context not obvious from code or git history. Save exactly one fact. If the memory already exists, use `revise` instead. Do not store secrets, temporary task state, or facts obvious from code or git history. Returns `<memory id="..." />`.',
20337
+ description: 'Save one new durable fact for later recall. Use for stable preferences, reusable decisions, and project context not obvious from code or git history. If the fact already exists, use `revise` instead. Returns `<memory id="..." />`.',
20337
20338
  inputSchema: rememberInputSchema
20338
20339
  }, async ({ content, workspace }) => {
20339
20340
  try {
@@ -20350,10 +20351,57 @@ function registerRememberTool(server, memory) {
20350
20351
  });
20351
20352
  }
20352
20353
 
20354
+ // src/mcp/tools/review.ts
20355
+ var REVIEW_PAGE_SIZE = 25;
20356
+ var reviewInputSchema = {
20357
+ workspace: string2().describe("Current working directory for project-scoped listing."),
20358
+ page: number2().int().min(0).optional().describe("Zero-based page number. Defaults to 0.")
20359
+ };
20360
+ function toMemoryXml2(record3) {
20361
+ const content = escapeXml(record3.content);
20362
+ return `<memory id="${record3.id}" updated_at="${record3.updatedAt.toISOString()}">
20363
+ ${content}
20364
+ </memory>`;
20365
+ }
20366
+ function registerReviewTool(server, memory) {
20367
+ server.registerTool("review", {
20368
+ annotations: {
20369
+ title: "Review",
20370
+ readOnlyHint: true,
20371
+ openWorldHint: false
20372
+ },
20373
+ description: 'Browse all memories for a workspace in creation order. Use before bulk review, cleanup, or when you need to scan memories without specific search terms. For targeted retrieval by topic, use `recall` instead. Returns `<memories workspace="..." has_more="true|false">...</memories>` with pagination support.',
20374
+ inputSchema: reviewInputSchema
20375
+ }, async ({ workspace, page }) => {
20376
+ try {
20377
+ const pageIndex = page ?? 0;
20378
+ const result = await memory.list({
20379
+ workspace,
20380
+ offset: pageIndex * REVIEW_PAGE_SIZE,
20381
+ limit: REVIEW_PAGE_SIZE
20382
+ });
20383
+ if (result.items.length === 0) {
20384
+ return {
20385
+ content: [{ type: "text", text: "No memories found for this workspace." }]
20386
+ };
20387
+ }
20388
+ const text = `<memories workspace="${escapeXml(workspace)}" has_more="${result.hasMore}">
20389
+ ${result.items.map(toMemoryXml2).join(`
20390
+ `)}
20391
+ </memories>`;
20392
+ return {
20393
+ content: [{ type: "text", text }]
20394
+ };
20395
+ } catch (error2) {
20396
+ throw toMcpError(error2);
20397
+ }
20398
+ });
20399
+ }
20400
+
20353
20401
  // src/mcp/tools/revise.ts
20354
20402
  var reviseInputSchema = {
20355
20403
  id: string2().describe("Memory id to update. Use an id returned by `recall`."),
20356
- content: string2().describe("Corrected replacement text for that memory. Keep it to one durable fact.")
20404
+ content: string2().describe("Corrected replacement text for that memory.")
20357
20405
  };
20358
20406
  function registerReviseTool(server, memory) {
20359
20407
  server.registerTool("revise", {
@@ -20363,7 +20411,7 @@ function registerReviseTool(server, memory) {
20363
20411
  idempotentHint: false,
20364
20412
  openWorldHint: false
20365
20413
  },
20366
- description: 'Update one existing memory when the same fact still applies but its wording or details changed. Use after `recall` when you already have the memory id. Keep it to one fact and do not merge unrelated facts. Returns `<memory id="..." updated_at="..." />`.',
20414
+ description: 'Update one existing memory when the same fact still applies but its wording or details changed. Use after `recall` when you already have the memory id. Returns `<memory id="..." updated_at="..." />`.',
20367
20415
  inputSchema: reviseInputSchema
20368
20416
  }, async ({ id, content }) => {
20369
20417
  try {
@@ -20385,11 +20433,12 @@ function registerReviseTool(server, memory) {
20385
20433
  // src/mcp/server.ts
20386
20434
  var SERVER_INSTRUCTIONS = [
20387
20435
  "Use this server only for durable memory that should survive across turns: stable preferences, corrections, reusable decisions, and project context not obvious from code or git history.",
20388
- "Use `recall` at conversation start, before design choices, and before saving memory.",
20389
- "Use `remember` only for a new durable fact. Use `revise` when the fact already exists but needs correction.",
20390
- "Use `forget` only when a memory is wrong or no longer relevant.",
20391
- "Pass workspace for project-scoped memory. Omit it only for truly global memory.",
20392
- "Do not store secrets or temporary task state in memory."
20436
+ "Use `recall` at conversation start, before design choices, and before saving or revising memory.",
20437
+ "Use `review` to browse all memories for a workspace before bulk review or cleanup.",
20438
+ "Use `remember` for one new durable fact. Use `revise` when the fact already exists but needs correction.",
20439
+ "Use `forget` only when a memory is wrong or obsolete.",
20440
+ "Pass workspace for project-scoped memory. Omit it only for facts that apply across projects.",
20441
+ "Do not store secrets, temporary task state, or facts obvious from current code or git history."
20393
20442
  ].join(" ");
20394
20443
  function createMcpServer(memory, version3) {
20395
20444
  const server = new McpServer({
@@ -20402,6 +20451,7 @@ function createMcpServer(memory, version3) {
20402
20451
  registerRecallTool(server, memory);
20403
20452
  registerReviseTool(server, memory);
20404
20453
  registerForgetTool(server, memory);
20454
+ registerReviewTool(server, memory);
20405
20455
  return server;
20406
20456
  }
20407
20457
 
@@ -24244,7 +24294,7 @@ function createGitWorkspaceResolver(options = {}) {
24244
24294
  const cache = new Map;
24245
24295
  return {
24246
24296
  async resolve(workspace) {
24247
- const trimmed = normalizeOptionalString(workspace);
24297
+ const trimmed = normalizeOptionalString2(workspace);
24248
24298
  if (!trimmed) {
24249
24299
  return;
24250
24300
  }
@@ -24275,18 +24325,20 @@ async function defaultGetGitCommonDir(cwd) {
24275
24325
  });
24276
24326
  return stdout.trim();
24277
24327
  }
24278
- function normalizeOptionalString(value) {
24328
+ function normalizeOptionalString2(value) {
24279
24329
  const trimmed = value?.trim();
24280
24330
  return trimmed ? trimmed : undefined;
24281
24331
  }
24282
24332
 
24283
24333
  // src/index.ts
24284
24334
  var config2 = resolveConfig();
24285
- var embeddingService = new EmbeddingService({ modelsCachePath: config2.modelsCachePath });
24335
+ configureModelsCache(config2.modelsCachePath);
24336
+ var embeddingService = new EmbeddingService;
24286
24337
  var workspaceResolver = createGitWorkspaceResolver();
24287
24338
  var database = await openMemoryDatabase(config2.databasePath, { embeddingService, workspaceResolver });
24288
24339
  var repository2 = new SqliteMemoryRepository(database);
24289
24340
  var memoryService = new MemoryService(repository2, embeddingService, workspaceResolver);
24341
+ embeddingService.warmup();
24290
24342
  if (config2.uiMode) {
24291
24343
  let shutdown = function() {
24292
24344
  server.close();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jcyamacho/agent-memory",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.18",
4
+ "version": "0.0.20",
5
5
  "bin": {
6
6
  "agent-memory": "dist/index.js"
7
7
  },