@jcyamacho/agent-memory 0.0.10 → 0.0.12

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 +41 -9
  2. package/dist/index.js +127 -46
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -3,10 +3,12 @@
3
3
  Persistent memory for MCP-powered coding agents.
4
4
 
5
5
  `agent-memory` is a stdio MCP server that gives your LLM durable memory backed
6
- by SQLite. It exposes two tools:
6
+ by SQLite. It exposes four tools:
7
7
 
8
8
  - `remember` -> save facts, decisions, preferences, and project context
9
9
  - `recall` -> retrieve the most relevant memories later
10
+ - `revise` -> update an existing memory when it becomes outdated
11
+ - `forget` -> delete a memory that is no longer relevant
10
12
 
11
13
  Use it when your agent should remember preferences, project facts, and prior
12
14
  decisions across sessions.
@@ -66,7 +68,10 @@ Optional LLM instructions to reinforce the MCP's built-in guidance:
66
68
  Use `recall` at the start of every conversation and again mid-task before
67
69
  making design choices or picking conventions. Use `remember` when the user
68
70
  corrects your approach, a key decision is established, or you learn project
69
- context not obvious from the code. Always pass workspace.
71
+ context not obvious from the code. Before saving, recall to check whether a
72
+ memory about the same fact already exists -- if so, use `revise` to update
73
+ it instead of creating a duplicate. Use `forget` to remove memories that
74
+ are wrong or no longer relevant. Always pass workspace.
70
75
  ```
71
76
 
72
77
  ## What It Stores
@@ -92,8 +97,7 @@ Opens at `http://localhost:6580`. Use `--port` to change:
92
97
  npx -y @jcyamacho/agent-memory --ui --port 9090
93
98
  ```
94
99
 
95
- The web UI uses the same database as the MCP server. LLM tools remain
96
- append-only; the web UI is the only way to edit or delete memories.
100
+ The web UI uses the same database as the MCP server.
97
101
 
98
102
  ## Tools
99
103
 
@@ -127,6 +131,31 @@ Output:
127
131
 
128
132
  - `results[]` with `id`, `content`, `score`, `workspace`, and `updated_at`
129
133
 
134
+ ### `revise`
135
+
136
+ Update the content of an existing memory.
137
+
138
+ Inputs:
139
+
140
+ - `id` -> the memory id from a previous recall result
141
+ - `content` -> replacement content for the memory
142
+
143
+ Output:
144
+
145
+ - `id`, `updated_at`
146
+
147
+ ### `forget`
148
+
149
+ Permanently delete a memory.
150
+
151
+ Inputs:
152
+
153
+ - `id` -> the memory id from a previous recall result
154
+
155
+ Output:
156
+
157
+ - `id`, `deleted`
158
+
130
159
  ## How Ranking Works
131
160
 
132
161
  `recall` uses a multi-signal ranking system to surface the most relevant
@@ -134,15 +163,18 @@ memories:
134
163
 
135
164
  1. **Text relevance** is the primary signal -- memories whose content best
136
165
  matches your search terms rank highest.
137
- 2. **Workspace match** is a strong secondary signal. When you pass `workspace`,
138
- memories saved with the same workspace rank above unrelated ones.
166
+ 2. **Workspace match** is a strong secondary signal. When you pass
167
+ `workspace`, exact matches rank highest, sibling repositories get a small
168
+ boost, and unrelated workspaces rank lowest.
139
169
  3. **Global memories** (saved without a workspace) are treated as relevant
140
- everywhere. They rank between workspace-matching and non-matching memories.
170
+ everywhere. When you pass `workspace`, they rank below exact workspace
171
+ matches and above sibling or unrelated repositories.
141
172
  4. **Recency** is a minor tiebreaker -- newer memories rank slightly above older
142
173
  ones when other signals are equal.
143
174
 
144
- For best results, always pass `workspace` when calling `recall`. Save memories
145
- without a workspace only when they apply across all projects.
175
+ If you omit `workspace`, recall falls back to text relevance and recency only.
176
+ For best results, pass `workspace` whenever you have one. Save memories without
177
+ a workspace only when they apply across all projects.
146
178
 
147
179
  ## Database location
148
180
 
package/dist/index.js CHANGED
@@ -12464,7 +12464,7 @@ class StdioServerTransport {
12464
12464
  }
12465
12465
  }
12466
12466
  // package.json
12467
- var version2 = "0.0.10";
12467
+ var version2 = "0.0.12";
12468
12468
 
12469
12469
  // src/config.ts
12470
12470
  import { homedir } from "node:os";
@@ -19895,9 +19895,6 @@ var EMPTY_COMPLETION_RESULT = {
19895
19895
  }
19896
19896
  };
19897
19897
 
19898
- // src/memory-service.ts
19899
- import { randomUUID } from "node:crypto";
19900
-
19901
19898
  // src/errors.ts
19902
19899
  class MemoryError extends Error {
19903
19900
  code;
@@ -19929,6 +19926,55 @@ class PersistenceError extends MemoryError {
19929
19926
  }
19930
19927
  }
19931
19928
 
19929
+ // src/tools/shared.ts
19930
+ var toMcpError = (error2) => {
19931
+ if (error2 instanceof McpError) {
19932
+ return error2;
19933
+ }
19934
+ if (error2 instanceof MemoryError) {
19935
+ if (error2.code === "VALIDATION_ERROR" || error2.code === "NOT_FOUND") {
19936
+ return new McpError(ErrorCode.InvalidParams, error2.message);
19937
+ }
19938
+ return new McpError(ErrorCode.InternalError, error2.message);
19939
+ }
19940
+ const message = error2 instanceof Error ? error2.message : "Unknown server error.";
19941
+ return new McpError(ErrorCode.InternalError, message);
19942
+ };
19943
+ var escapeXml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
19944
+ var parseOptionalDate = (value, fieldName) => {
19945
+ if (!value) {
19946
+ return;
19947
+ }
19948
+ const date4 = new Date(value);
19949
+ if (Number.isNaN(date4.getTime())) {
19950
+ throw new MemoryError("VALIDATION_ERROR", `${fieldName} must be a valid ISO 8601 datetime.`);
19951
+ }
19952
+ return date4;
19953
+ };
19954
+
19955
+ // src/tools/forget.ts
19956
+ var forgetInputSchema = {
19957
+ id: string2().describe("The id of the memory to delete. Use the id returned by a previous recall result.")
19958
+ };
19959
+ var registerForgetTool = (server, memoryService) => {
19960
+ server.registerTool("forget", {
19961
+ description: "Permanently delete a memory that is wrong, obsolete, or no longer relevant. Pass the memory id from a previous recall result.",
19962
+ inputSchema: forgetInputSchema
19963
+ }, async ({ id }) => {
19964
+ try {
19965
+ await memoryService.forget({ id });
19966
+ return {
19967
+ content: [{ type: "text", text: `<memory id="${id.trim()}" deleted="true" />` }]
19968
+ };
19969
+ } catch (error2) {
19970
+ throw toMcpError(error2);
19971
+ }
19972
+ });
19973
+ };
19974
+
19975
+ // src/memory-service.ts
19976
+ import { randomUUID } from "node:crypto";
19977
+
19932
19978
  // src/memory.ts
19933
19979
  var toNormalizedScore = (value) => value;
19934
19980
 
@@ -19940,6 +19986,8 @@ var RETRIEVAL_SCORE_WEIGHT = 8;
19940
19986
  var WORKSPACE_MATCH_WEIGHT = 4;
19941
19987
  var RECENCY_WEIGHT = 1;
19942
19988
  var MAX_COMPOSITE_SCORE = RETRIEVAL_SCORE_WEIGHT + WORKSPACE_MATCH_WEIGHT + RECENCY_WEIGHT;
19989
+ var GLOBAL_WORKSPACE_SCORE = 0.5;
19990
+ var SIBLING_WORKSPACE_SCORE = 0.25;
19943
19991
 
19944
19992
  class MemoryService {
19945
19993
  repository;
@@ -19961,6 +20009,18 @@ class MemoryService {
19961
20009
  };
19962
20010
  return this.repository.save(memory);
19963
20011
  }
20012
+ async revise(input) {
20013
+ const content = input.content.trim();
20014
+ if (!content)
20015
+ throw new ValidationError("Memory content is required.");
20016
+ return this.repository.update(input.id, content);
20017
+ }
20018
+ async forget(input) {
20019
+ const id = input.id.trim();
20020
+ if (!id)
20021
+ throw new ValidationError("Memory id is required.");
20022
+ return this.repository.delete(id);
20023
+ }
19964
20024
  async search(input) {
19965
20025
  const terms = normalizeTerms(input.terms);
19966
20026
  if (terms.length === 0) {
@@ -19979,7 +20039,7 @@ class MemoryService {
19979
20039
  return reranked.sort((a, b) => b.score - a.score).slice(0, requestedLimit);
19980
20040
  }
19981
20041
  }
19982
- var normalizeLimit = (value) => {
20042
+ function normalizeLimit(value) {
19983
20043
  if (value === undefined) {
19984
20044
  return DEFAULT_LIMIT;
19985
20045
  }
@@ -19987,32 +20047,48 @@ var normalizeLimit = (value) => {
19987
20047
  throw new ValidationError(`Limit must be an integer between 1 and ${MAX_LIMIT}.`);
19988
20048
  }
19989
20049
  return value;
19990
- };
19991
- var normalizeOptionalString = (value) => {
20050
+ }
20051
+ function normalizeOptionalString(value) {
19992
20052
  const trimmed = value?.trim();
19993
20053
  return trimmed ? trimmed : undefined;
19994
- };
19995
- var normalizeTerms = (terms) => {
20054
+ }
20055
+ function normalizeTerms(terms) {
19996
20056
  const normalizedTerms = terms.map((term) => term.trim()).filter(Boolean);
19997
20057
  return [...new Set(normalizedTerms)];
19998
- };
19999
- var GLOBAL_WORKSPACE_SCORE = 0.5;
20000
- var computeWorkspaceScore = (memoryWs, queryWs) => {
20001
- if (!memoryWs)
20058
+ }
20059
+ function normalizeWorkspacePath(value) {
20060
+ return value.trim().replaceAll("\\", "/").replace(/\/+/g, "/").split("/").filter(Boolean).join("/");
20061
+ }
20062
+ function computeWorkspaceScore(memoryWs, queryWs) {
20063
+ if (!queryWs) {
20064
+ return 0;
20065
+ }
20066
+ if (!memoryWs) {
20002
20067
  return GLOBAL_WORKSPACE_SCORE;
20003
- if (queryWs && memoryWs === queryWs)
20068
+ }
20069
+ const normalizedMemoryWs = normalizeWorkspacePath(memoryWs);
20070
+ if (normalizedMemoryWs === queryWs) {
20004
20071
  return 1;
20005
- return 0;
20006
- };
20007
- var rerankSearchResults = (results, workspace) => {
20072
+ }
20073
+ const queryLastSlashIndex = queryWs.lastIndexOf("/");
20074
+ const memoryLastSlashIndex = normalizedMemoryWs.lastIndexOf("/");
20075
+ if (queryLastSlashIndex <= 0 || memoryLastSlashIndex <= 0) {
20076
+ return 0;
20077
+ }
20078
+ const queryParent = queryWs.slice(0, queryLastSlashIndex);
20079
+ const memoryParent = normalizedMemoryWs.slice(0, memoryLastSlashIndex);
20080
+ return memoryParent === queryParent ? SIBLING_WORKSPACE_SCORE : 0;
20081
+ }
20082
+ function rerankSearchResults(results, workspace) {
20008
20083
  if (results.length <= 1) {
20009
20084
  return results;
20010
20085
  }
20086
+ const normalizedQueryWs = workspace ? normalizeWorkspacePath(workspace) : undefined;
20011
20087
  const updatedAtTimes = results.map((result) => result.updatedAt.getTime());
20012
20088
  const minUpdatedAt = Math.min(...updatedAtTimes);
20013
20089
  const maxUpdatedAt = Math.max(...updatedAtTimes);
20014
- return [...results].map((result) => {
20015
- const workspaceScore = computeWorkspaceScore(result.workspace, workspace);
20090
+ return results.map((result) => {
20091
+ const workspaceScore = computeWorkspaceScore(result.workspace, normalizedQueryWs);
20016
20092
  const recencyScore = maxUpdatedAt === minUpdatedAt ? 0 : (result.updatedAt.getTime() - minUpdatedAt) / (maxUpdatedAt - minUpdatedAt);
20017
20093
  const combinedScore = (result.score * RETRIEVAL_SCORE_WEIGHT + workspaceScore * WORKSPACE_MATCH_WEIGHT + recencyScore * RECENCY_WEIGHT) / MAX_COMPOSITE_SCORE;
20018
20094
  return {
@@ -20020,33 +20096,7 @@ var rerankSearchResults = (results, workspace) => {
20020
20096
  score: toNormalizedScore(combinedScore)
20021
20097
  };
20022
20098
  });
20023
- };
20024
-
20025
- // src/tools/shared.ts
20026
- var toMcpError = (error2) => {
20027
- if (error2 instanceof McpError) {
20028
- return error2;
20029
- }
20030
- if (error2 instanceof MemoryError) {
20031
- if (error2.code === "VALIDATION_ERROR") {
20032
- return new McpError(ErrorCode.InvalidParams, error2.message);
20033
- }
20034
- return new McpError(ErrorCode.InternalError, error2.message);
20035
- }
20036
- const message = error2 instanceof Error ? error2.message : "Unknown server error.";
20037
- return new McpError(ErrorCode.InternalError, message);
20038
- };
20039
- var escapeXml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
20040
- var parseOptionalDate = (value, fieldName) => {
20041
- if (!value) {
20042
- return;
20043
- }
20044
- const date4 = new Date(value);
20045
- if (Number.isNaN(date4.getTime())) {
20046
- throw new MemoryError("VALIDATION_ERROR", `${fieldName} must be a valid ISO 8601 datetime.`);
20047
- }
20048
- return date4;
20049
- };
20099
+ }
20050
20100
 
20051
20101
  // src/tools/recall.ts
20052
20102
  var recallInputSchema = {
@@ -20113,11 +20163,40 @@ var registerRememberTool = (server, memoryService) => {
20113
20163
  });
20114
20164
  };
20115
20165
 
20166
+ // src/tools/revise.ts
20167
+ var reviseInputSchema = {
20168
+ id: string2().describe("The id of the memory to update. Use the id returned by a previous recall result."),
20169
+ content: string2().describe("The replacement content for the memory. Use a single self-contained sentence or short note. One fact per memory.")
20170
+ };
20171
+ var registerReviseTool = (server, memoryService) => {
20172
+ server.registerTool("revise", {
20173
+ description: "Update the content of an existing memory. Use when a previously saved memory is outdated or inaccurate and needs correction rather than deletion. Pass the memory id from a previous recall result.",
20174
+ inputSchema: reviseInputSchema
20175
+ }, async ({ id, content }) => {
20176
+ try {
20177
+ const memory = await memoryService.revise({ id, content });
20178
+ return {
20179
+ content: [
20180
+ {
20181
+ type: "text",
20182
+ text: `<memory id="${memory.id}" updated_at="${memory.updatedAt.toISOString()}" />`
20183
+ }
20184
+ ]
20185
+ };
20186
+ } catch (error2) {
20187
+ throw toMcpError(error2);
20188
+ }
20189
+ });
20190
+ };
20191
+
20116
20192
  // src/mcp-server.ts
20117
20193
  var SERVER_INSTRUCTIONS = [
20118
20194
  "Stores decisions, corrections, and context that cannot be derived from code or git history.",
20119
20195
  "Use `recall` at the start of every conversation and again mid-task before making design choices or picking conventions the user may have guided before.",
20120
20196
  "Use `remember` when the user corrects your approach, states a preference, a key decision is established, or you learn project context not obvious from the code.",
20197
+ "Before saving a new memory, recall to check whether a memory about the same fact already exists. If so, use `revise` to update it instead of creating a duplicate.",
20198
+ "Use `revise` when a previously saved memory is outdated or inaccurate and needs correction rather than deletion.",
20199
+ "Use `forget` to remove memories that are wrong, obsolete, or no longer relevant.",
20121
20200
  "Always pass workspace (the current working directory) to scope results to the active project.",
20122
20201
  "Omit workspace only when saving a memory that applies across all projects."
20123
20202
  ].join(" ");
@@ -20130,6 +20209,8 @@ var createMcpServer = (memoryService, version3) => {
20130
20209
  });
20131
20210
  registerRememberTool(server, memoryService);
20132
20211
  registerRecallTool(server, memoryService);
20212
+ registerReviseTool(server, memoryService);
20213
+ registerForgetTool(server, memoryService);
20133
20214
  return server;
20134
20215
  };
20135
20216
 
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.10",
4
+ "version": "0.0.12",
5
5
  "bin": {
6
6
  "agent-memory": "dist/index.js"
7
7
  },