@jcyamacho/agent-memory 0.0.17 → 0.0.19

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 +47 -68
  2. package/dist/index.js +67 -41
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -3,16 +3,16 @@
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 four tools:
6
+ by SQLite. It helps your agent remember preferences, project context, and prior
7
+ decisions across sessions.
8
+
9
+ It exposes four tools:
7
10
 
8
11
  - `remember` -> save facts, decisions, preferences, and project context
9
12
  - `recall` -> retrieve the most relevant memories later
10
13
  - `revise` -> update an existing memory when it becomes outdated
11
14
  - `forget` -> delete a memory that is no longer relevant
12
15
 
13
- Use it when your agent should remember preferences, project facts, and prior
14
- decisions across sessions.
15
-
16
16
  ## Quick Start
17
17
 
18
18
  Claude CLI:
@@ -27,67 +27,72 @@ Codex CLI:
27
27
  codex mcp add memory -- npx -y @jcyamacho/agent-memory
28
28
  ```
29
29
 
30
- Optional LLM instructions to reinforce the MCP's built-in guidance:
30
+ OpenCode:
31
31
 
32
- ```text
33
- Use `memory_recall` at conversation start and before design choices,
34
- conventions, or edge cases. Query with 2-5 short anchor-heavy terms or exact
35
- phrases, not
36
- questions or sentences. `memory_recall` is lexical-first; if it misses, retry once
37
- with overlapping alternate terms. Use `memory_remember` for one durable fact, then
38
- use `memory_revise` instead of duplicates and `memory_forget` for wrong or obsolete
39
- memories. Always pass workspace unless the memory is truly global. Git worktree
40
- paths are canonicalized to the main repo root on save and recall.
32
+ ```jsonc
33
+ {
34
+ "$schema": "https://opencode.ai/config.json",
35
+ "mcp": {
36
+ "memory": {
37
+ "type": "local",
38
+ "command": ["npx", "-y", "@jcyamacho/agent-memory"]
39
+ }
40
+ }
41
+ }
41
42
  ```
42
43
 
43
- ## What It Stores
44
+ ## Optional LLM Instructions
44
45
 
45
- This MCP is useful for context that should survive across turns and sessions:
46
+ Optional LLM instructions to reinforce the MCP's built-in guidance:
46
47
 
47
- - User preferences like response style, formatting, and workflow habits
48
- - Project facts like paths, architecture choices, and conventions
49
- - Important decisions and constraints that should not be rediscovered
50
- - Project-scoped notes that still matter later
48
+ ```md
49
+ ## Agent Memory
50
+
51
+ - Use `memory_recall` at conversation start and before design choices,
52
+ conventions, edge cases, or saving memory.
53
+ - Query `memory_recall` with 2-5 short anchor-heavy terms or exact phrases,
54
+ not full questions or sentences.
55
+ - Pass `workspace` for project-scoped memory. Omit it only for facts that
56
+ apply across projects.
57
+ - Use `memory_remember` to save one durable fact when the user states a stable
58
+ preference, correction, or reusable project decision.
59
+ - If the fact already exists, use `memory_revise` instead of creating a duplicate.
60
+ - Use `memory_forget` to remove a wrong or obsolete memory.
61
+ - Do not store secrets or temporary task state in memory.
62
+ ```
51
63
 
52
64
  ## Web UI
53
65
 
54
66
  Browse, edit, and delete memories in a local web interface:
55
67
 
56
68
  ```bash
57
- npx -y @jcyamacho/agent-memory --ui
69
+ npx -y @jcyamacho/agent-memory@latest --ui
58
70
  ```
59
71
 
60
72
  Opens at `http://localhost:6580`. Use `--port` to change:
61
73
 
62
74
  ```bash
63
- npx -y @jcyamacho/agent-memory --ui --port 9090
75
+ npx -y @jcyamacho/agent-memory@latest --ui --port 9090
64
76
  ```
65
77
 
66
78
  The web UI uses the same database as the MCP server.
67
79
 
68
- ## Tools
69
-
70
- - `remember` saves durable facts, preferences, decisions, and project context.
71
- - `recall` retrieves the most relevant saved memories.
72
- - `revise` updates an existing memory when it becomes outdated.
73
- - `forget` deletes a memory that is no longer relevant.
74
-
75
- ## How Ranking Works
80
+ ## How Recall Finds Memories
76
81
 
77
82
  `recall` uses a multi-signal ranking system to surface the most relevant
78
83
  memories:
79
84
 
80
85
  1. **Text relevance** is the primary signal -- memories whose content best
81
86
  matches your search terms rank highest.
82
- 2. **Embedding similarity** is the next strongest signal. Recall builds an
83
- embedding from your normalized search terms and boosts memories whose stored
87
+ 2. **Workspace match** is the next strongest signal. When you pass
88
+ `workspace`, exact matches rank highest and all other scoped workspaces rank
89
+ below exact matches.
90
+ 3. **Embedding similarity** is a secondary signal. Recall builds an embedding
91
+ from your normalized search terms and boosts memories whose stored
84
92
  embeddings are most semantically similar.
85
- 3. **Workspace match** is a strong secondary signal. When you pass
86
- `workspace`, exact matches rank highest, sibling repositories get a small
87
- boost, and unrelated workspaces rank lowest.
88
93
  4. **Global memories** (saved without a workspace) are treated as relevant
89
94
  everywhere. When you pass `workspace`, they rank below exact workspace
90
- matches and above sibling or unrelated repositories.
95
+ matches and above memories from other workspaces.
91
96
  5. **Recency** is a minor tiebreaker -- newer memories rank slightly above older
92
97
  ones when other signals are equal.
93
98
 
@@ -98,8 +103,12 @@ memories without a workspace only when they apply across all projects.
98
103
  When you save a memory from a git worktree, `agent-memory` stores the main repo
99
104
  root as the workspace. `recall` applies the same normalization to incoming
100
105
  workspace queries so linked worktrees still match repo-scoped memories exactly.
106
+ When that happens, recall returns the queried workspace value so callers can
107
+ treat the match as belonging to their current worktree context.
108
+
109
+ ## Configuration
101
110
 
102
- ## Database location
111
+ ### Database Location
103
112
 
104
113
  By default, the SQLite database is created at:
105
114
 
@@ -119,7 +128,7 @@ Set `AGENT_MEMORY_DB_PATH` when you want to:
119
128
  - share a memory DB across multiple clients
120
129
  - store the DB somewhere easier to back up or inspect
121
130
 
122
- ## Model cache location
131
+ ### Model Cache Location
123
132
 
124
133
  By default, downloaded embedding model files are cached at:
125
134
 
@@ -142,36 +151,6 @@ Set `AGENT_MEMORY_MODELS_CACHE_PATH` when you want to:
142
151
  Schema changes are migrated automatically, including workspace normalization for
143
152
  existing git worktree memories when the original path can still be resolved.
144
153
 
145
- ## Development
146
-
147
- For working on the project itself or running from source. Requires Bun and
148
- Node.js.
149
-
150
- ```bash
151
- bun install
152
- bun run build
153
- ```
154
-
155
- To use a local build as your MCP server:
156
-
157
- ```json
158
- {
159
- "mcpServers": {
160
- "memory": {
161
- "command": "node",
162
- "args": [
163
- "/absolute/path/to/agent-memory/dist/index.js"
164
- ]
165
- }
166
- }
167
- }
168
- ```
169
-
170
- ```bash
171
- bun lint
172
- bun test
173
- ```
174
-
175
154
  ## License
176
155
 
177
156
  MIT
package/dist/index.js CHANGED
@@ -12464,7 +12464,7 @@ class StdioServerTransport {
12464
12464
  }
12465
12465
  }
12466
12466
  // package.json
12467
- var version2 = "0.0.17";
12467
+ var version2 = "0.0.19";
12468
12468
 
12469
12469
  // src/config.ts
12470
12470
  import { homedir } from "node:os";
@@ -20058,11 +20058,17 @@ function parseOptionalDate(value, fieldName) {
20058
20058
 
20059
20059
  // src/mcp/tools/forget.ts
20060
20060
  var forgetInputSchema = {
20061
- id: string2().describe("The memory id to delete. Use an id returned by `recall`.")
20061
+ id: string2().describe("Memory id to delete. Use an id returned by `recall`.")
20062
20062
  };
20063
20063
  function registerForgetTool(server, memory) {
20064
20064
  server.registerTool("forget", {
20065
- description: 'Permanently delete a wrong or obsolete memory. Use `revise` instead when the fact still exists and only needs correction. Returns `<memory id="..." deleted="true" />`.',
20065
+ annotations: {
20066
+ title: "Forget",
20067
+ destructiveHint: true,
20068
+ idempotentHint: true,
20069
+ openWorldHint: false
20070
+ },
20071
+ description: 'Delete a memory that is wrong or obsolete. Use after `recall` when you have the memory id. Use `revise` instead if the fact should remain with corrected wording. Returns `<memory id="..." deleted="true" />`.',
20066
20072
  inputSchema: forgetInputSchema
20067
20073
  }, async ({ id }) => {
20068
20074
  try {
@@ -20080,15 +20086,14 @@ function registerForgetTool(server, memory) {
20080
20086
  var toNormalizedScore = (value) => value;
20081
20087
 
20082
20088
  // src/ranking.ts
20083
- var RETRIEVAL_SCORE_WEIGHT = 8;
20084
- var EMBEDDING_SIMILARITY_WEIGHT = 5;
20085
- var WORKSPACE_MATCH_WEIGHT = 4;
20086
- 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;
20087
20093
  var MAX_COMPOSITE_SCORE = RETRIEVAL_SCORE_WEIGHT + EMBEDDING_SIMILARITY_WEIGHT + WORKSPACE_MATCH_WEIGHT + RECENCY_WEIGHT;
20088
20094
  var GLOBAL_WORKSPACE_SCORE = 0.5;
20089
- var SIBLING_WORKSPACE_SCORE = 0.25;
20090
20095
  function rerankSearchResults(results, workspace, queryEmbedding) {
20091
- if (results.length <= 1) {
20096
+ if (results.length === 0) {
20092
20097
  return results;
20093
20098
  }
20094
20099
  const normalizedQueryWs = workspace ? normalizeWorkspacePath(workspace) : undefined;
@@ -20126,14 +20131,7 @@ function computeWorkspaceScore(memoryWs, queryWs) {
20126
20131
  if (normalizedMemoryWs === queryWs) {
20127
20132
  return 1;
20128
20133
  }
20129
- const queryLastSlashIndex = queryWs.lastIndexOf("/");
20130
- const memoryLastSlashIndex = normalizedMemoryWs.lastIndexOf("/");
20131
- if (queryLastSlashIndex <= 0 || memoryLastSlashIndex <= 0) {
20132
- return 0;
20133
- }
20134
- const queryParent = queryWs.slice(0, queryLastSlashIndex);
20135
- const memoryParent = normalizedMemoryWs.slice(0, memoryLastSlashIndex);
20136
- return memoryParent === queryParent ? SIBLING_WORKSPACE_SCORE : 0;
20134
+ return 0;
20137
20135
  }
20138
20136
 
20139
20137
  // src/memory-service.ts
@@ -20205,6 +20203,7 @@ class MemoryService {
20205
20203
  throw new ValidationError("At least one search term is required.");
20206
20204
  }
20207
20205
  const requestedLimit = normalizeLimit(input.limit);
20206
+ const queryWorkspace = normalizeOptionalString(input.workspace);
20208
20207
  const workspace = await this.workspaceResolver.resolve(input.workspace);
20209
20208
  const normalizedQuery = {
20210
20209
  terms,
@@ -20216,7 +20215,7 @@ class MemoryService {
20216
20215
  this.repository.search(normalizedQuery),
20217
20216
  this.embeddingService.createVector(terms.join(" "))
20218
20217
  ]);
20219
- return rerankSearchResults(results, workspace, queryEmbedding).slice(0, requestedLimit).map(toPublicSearchResult);
20218
+ return rerankSearchResults(results, workspace, queryEmbedding).slice(0, requestedLimit).map((result) => toPublicSearchResult(remapSearchResultWorkspace(result, workspace, queryWorkspace)));
20220
20219
  }
20221
20220
  }
20222
20221
  function toPublicMemoryRecord(memory) {
@@ -20266,14 +20265,27 @@ function normalizeTerms(terms) {
20266
20265
  const normalizedTerms = terms.map((term) => term.trim()).filter(Boolean);
20267
20266
  return [...new Set(normalizedTerms)];
20268
20267
  }
20268
+ function normalizeOptionalString(value) {
20269
+ const trimmed = value?.trim();
20270
+ return trimmed ? trimmed : undefined;
20271
+ }
20272
+ function remapSearchResultWorkspace(result, canonicalWorkspace, queryWorkspace) {
20273
+ if (!queryWorkspace || !canonicalWorkspace || result.workspace !== canonicalWorkspace) {
20274
+ return result;
20275
+ }
20276
+ return {
20277
+ ...result,
20278
+ workspace: queryWorkspace
20279
+ };
20280
+ }
20269
20281
 
20270
20282
  // src/mcp/tools/recall.ts
20271
20283
  var recallInputSchema = {
20272
- terms: array(string2()).min(1).describe("Search terms for lexical memory lookup. 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 in the memory. Avoid vague words, full sentences, and repeating the workspace name. If recall misses, retry once with overlapping alternate terms."),
20284
+ 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."),
20273
20285
  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."),
20274
- workspace: string2().optional().describe("Pass the current working directory. Git worktree paths are normalized to the main repo root for matching. This strongly boosts memories from the active project while still allowing global and cross-workspace matches."),
20275
- updated_after: string2().optional().describe("Only return memories updated at or after this ISO 8601 timestamp."),
20276
- updated_before: string2().optional().describe("Only return memories updated at or before this ISO 8601 timestamp.")
20286
+ workspace: string2().optional().describe("Current working directory for project-scoped recall. Omit for cross-project recall."),
20287
+ updated_after: string2().optional().describe("Only return memories updated on or after this ISO 8601 timestamp."),
20288
+ updated_before: string2().optional().describe("Only return memories updated on or before this ISO 8601 timestamp.")
20277
20289
  };
20278
20290
  function toMemoryXml(r) {
20279
20291
  const workspace = r.workspace ? ` workspace="${escapeXml(r.workspace)}"` : "";
@@ -20285,7 +20297,12 @@ ${content}
20285
20297
  }
20286
20298
  function registerRecallTool(server, memory) {
20287
20299
  server.registerTool("recall", {
20288
- description: "Retrieve relevant memories for the current task. Use at conversation start and before design choices, conventions, or edge cases. Query with 2-5 short anchor-heavy terms or exact phrases, not questions or full sentences. `recall` is lexical-first; semantic reranking only reorders lexical matches. If it misses, retry once with overlapping alternate terms. Pass workspace; git worktree paths are normalized to the main repo root for matching. Returns `<memories>...</memories>` or a no-match hint.",
20300
+ annotations: {
20301
+ title: "Recall",
20302
+ readOnlyHint: true,
20303
+ openWorldHint: false
20304
+ },
20305
+ 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.",
20289
20306
  inputSchema: recallInputSchema
20290
20307
  }, async ({ terms, limit, workspace, updated_after, updated_before }) => {
20291
20308
  try {
@@ -20296,7 +20313,7 @@ function registerRecallTool(server, memory) {
20296
20313
  updatedAfter: parseOptionalDate(updated_after, "updated_after"),
20297
20314
  updatedBefore: parseOptionalDate(updated_before, "updated_before")
20298
20315
  });
20299
- const text = results.length === 0 ? "No matching memories found. Retry once with 1-3 alternate overlapping terms or an exact phrase likely to appear in the memory text. Recall is lexical-first, so semantic reranking cannot rescue a query with no wording overlap." : `<memories>
20316
+ const text = results.length === 0 ? "No matching memories found. Retry once with 1-3 overlapping alternate terms or an exact identifier, command, file path, or phrase likely to appear in the memory." : `<memories>
20300
20317
  ${results.map(toMemoryXml).join(`
20301
20318
  `)}
20302
20319
  </memories>`;
@@ -20311,12 +20328,18 @@ ${results.map(toMemoryXml).join(`
20311
20328
 
20312
20329
  // src/mcp/tools/remember.ts
20313
20330
  var rememberInputSchema = {
20314
- content: string2().describe("One durable fact to save. Use a single self-contained sentence or short note with concrete nouns, identifiers, commands, file paths, or exact phrases the agent is likely to reuse."),
20315
- workspace: string2().optional().describe("Pass the current working directory for project-specific memories. Git worktree paths are saved as the main repo root automatically. Omit only for truly global memories.")
20331
+ content: string2().describe("One new durable fact to save. Use a self-contained sentence or short note."),
20332
+ workspace: string2().optional().describe("Current working directory for project-scoped memory. Omit for facts that apply across projects.")
20316
20333
  };
20317
20334
  function registerRememberTool(server, memory) {
20318
20335
  server.registerTool("remember", {
20319
- description: 'Save one durable memory for later recall. Use when the user states a stable preference, corrects you, or establishes reusable project context not obvious from code or git history. Save one fact per memory. Call `recall` first; use `revise` instead of creating duplicates. Do not store secrets, temporary task state, or codebase facts. Returns `<memory id="..." />`.',
20336
+ annotations: {
20337
+ title: "Remember",
20338
+ destructiveHint: false,
20339
+ idempotentHint: false,
20340
+ openWorldHint: false
20341
+ },
20342
+ 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="..." />`.',
20320
20343
  inputSchema: rememberInputSchema
20321
20344
  }, async ({ content, workspace }) => {
20322
20345
  try {
@@ -20335,12 +20358,18 @@ function registerRememberTool(server, memory) {
20335
20358
 
20336
20359
  // src/mcp/tools/revise.ts
20337
20360
  var reviseInputSchema = {
20338
- id: string2().describe("The memory id to update. Use an id returned by `recall`."),
20339
- content: string2().describe("The corrected replacement for that same fact. Keep it to one durable fact.")
20361
+ id: string2().describe("Memory id to update. Use an id returned by `recall`."),
20362
+ content: string2().describe("Corrected replacement text for that memory.")
20340
20363
  };
20341
20364
  function registerReviseTool(server, memory) {
20342
20365
  server.registerTool("revise", {
20343
- description: 'Replace one existing memory with corrected wording. Use after `recall` when the same fact still applies but details changed. Do not append unrelated facts or merge memories. Returns `<memory id="..." updated_at="..." />`.',
20366
+ annotations: {
20367
+ title: "Revise",
20368
+ destructiveHint: false,
20369
+ idempotentHint: false,
20370
+ openWorldHint: false
20371
+ },
20372
+ 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="..." />`.',
20344
20373
  inputSchema: reviseInputSchema
20345
20374
  }, async ({ id, content }) => {
20346
20375
  try {
@@ -20361,15 +20390,12 @@ function registerReviseTool(server, memory) {
20361
20390
 
20362
20391
  // src/mcp/server.ts
20363
20392
  var SERVER_INSTRUCTIONS = [
20364
- "Use this server for durable memory: user preferences, corrections, decisions, and project context not obvious from code or git history.",
20365
- "Use `recall` at conversation start and before design choices, conventions, or edge cases.",
20366
- "Query `recall` with 2-5 short anchor-heavy terms or exact phrases likely to appear verbatim in memory text: identifiers, commands, file paths, and conventions.",
20367
- "`recall` is lexical-first; semantic reranking only reorders lexical matches.",
20368
- "If `recall` misses, retry once with overlapping alternate terms.",
20369
- "Use `remember` for one durable fact when the user states a preference, corrects you, or a reusable project decision becomes clear.",
20370
- "Call `recall` before `remember`; if the fact already exists, use `revise` instead of creating a duplicate.",
20371
- "Use `revise` to correct an existing memory and `forget` to remove a wrong or obsolete one.",
20372
- "Pass workspace for project-scoped calls. Git worktree paths are canonicalized to the main repo root on save and recall. Omit workspace only for truly global memories."
20393
+ "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.",
20394
+ "Use `recall` at conversation start, before design choices, and before saving or revising memory.",
20395
+ "Use `remember` for one new durable fact. Use `revise` when the fact already exists but needs correction.",
20396
+ "Use `forget` only when a memory is wrong or obsolete.",
20397
+ "Pass workspace for project-scoped memory. Omit it only for facts that apply across projects.",
20398
+ "Do not store secrets, temporary task state, or facts obvious from current code or git history."
20373
20399
  ].join(" ");
20374
20400
  function createMcpServer(memory, version3) {
20375
20401
  const server = new McpServer({
@@ -24224,7 +24250,7 @@ function createGitWorkspaceResolver(options = {}) {
24224
24250
  const cache = new Map;
24225
24251
  return {
24226
24252
  async resolve(workspace) {
24227
- const trimmed = normalizeOptionalString(workspace);
24253
+ const trimmed = normalizeOptionalString2(workspace);
24228
24254
  if (!trimmed) {
24229
24255
  return;
24230
24256
  }
@@ -24255,7 +24281,7 @@ async function defaultGetGitCommonDir(cwd) {
24255
24281
  });
24256
24282
  return stdout.trim();
24257
24283
  }
24258
- function normalizeOptionalString(value) {
24284
+ function normalizeOptionalString2(value) {
24259
24285
  const trimmed = value?.trim();
24260
24286
  return trimmed ? trimmed : undefined;
24261
24287
  }
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.17",
4
+ "version": "0.0.19",
5
5
  "bin": {
6
6
  "agent-memory": "dist/index.js"
7
7
  },