@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.
- package/README.md +41 -9
- package/dist/index.js +127 -46
- 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
|
|
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.
|
|
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.
|
|
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
|
|
138
|
-
|
|
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.
|
|
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
|
-
|
|
145
|
-
|
|
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.
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
-
|
|
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
|
-
|
|
20050
|
+
}
|
|
20051
|
+
function normalizeOptionalString(value) {
|
|
19992
20052
|
const trimmed = value?.trim();
|
|
19993
20053
|
return trimmed ? trimmed : undefined;
|
|
19994
|
-
}
|
|
19995
|
-
|
|
20054
|
+
}
|
|
20055
|
+
function normalizeTerms(terms) {
|
|
19996
20056
|
const normalizedTerms = terms.map((term) => term.trim()).filter(Boolean);
|
|
19997
20057
|
return [...new Set(normalizedTerms)];
|
|
19998
|
-
}
|
|
19999
|
-
|
|
20000
|
-
|
|
20001
|
-
|
|
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
|
-
|
|
20068
|
+
}
|
|
20069
|
+
const normalizedMemoryWs = normalizeWorkspacePath(memoryWs);
|
|
20070
|
+
if (normalizedMemoryWs === queryWs) {
|
|
20004
20071
|
return 1;
|
|
20005
|
-
|
|
20006
|
-
|
|
20007
|
-
|
|
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
|
|
20015
|
-
const workspaceScore = computeWorkspaceScore(result.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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
|