@jcyamacho/agent-memory 0.0.2 → 0.0.3

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 +35 -26
  2. package/dist/index.js +46 -83
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -13,6 +13,18 @@ decisions across sessions.
13
13
 
14
14
  ## Quick Start
15
15
 
16
+ Claude CLI:
17
+
18
+ ```bash
19
+ claude mcp add --scope user memory -- npx -y @jcyamacho/agent-memory
20
+ ```
21
+
22
+ Codex CLI:
23
+
24
+ ```bash
25
+ codex mcp add memory -- npx -y @jcyamacho/agent-memory
26
+ ```
27
+
16
28
  Example MCP server config:
17
29
 
18
30
  ```json
@@ -51,22 +63,22 @@ With a custom database path:
51
63
  Recommended LLM instructions to pair with this MCP:
52
64
 
53
65
  ```text
54
- Use `memory_recall` at the start of a task, when the user refers to previous
55
- context, or whenever prior preferences, project facts, or decisions may help.
56
-
57
- Use `memory_remember` to save durable context that will matter later, such as
58
- user preferences, project conventions, architecture decisions, constraints, and
59
- stable workflow habits.
60
-
61
- Store concise, self-contained facts or short notes. Include `source`,
62
- `workspace`, and `session` when available so future retrieval is better scoped.
63
-
64
- Do not store secrets, credentials, API keys, tokens, or temporary noise.
65
-
66
- When recalling, use short factual queries and keep `limit` small unless you
67
- need broader recall. Use `preferred_workspace` or `preferred_source` to bias
68
- ranking, and `filter_workspace` or `filter_source` only when exact scoping is
69
- required.
66
+ Use `memory_recall` at task start and whenever prior preferences, project facts,
67
+ or decisions may matter.
68
+
69
+ Use `memory_remember` only for durable, reusable context: preferences,
70
+ conventions, decisions, constraints, and stable workflow habits. Store one
71
+ concise, self-contained fact per memory. Include `workspace` when available. Do
72
+ not store secrets or temporary noise.
73
+
74
+ For `memory_recall`, pass `terms` as 2-5 distinctive strings that describe what
75
+ you are looking for. Prefer names, identifiers, package names, file names, and
76
+ short phrases. Each term is matched independently more terms cast a wider net,
77
+ and results matching multiple terms rank higher. Stemming is applied
78
+ automatically, so exact word forms are not required.
79
+
80
+ Use `preferred_workspace` to bias ranking. Use `filter_workspace` and
81
+ `created_*` only for exact scoping. Keep `limit` small.
70
82
  ```
71
83
 
72
84
  ## What It Stores
@@ -76,7 +88,7 @@ This MCP is useful for context that should survive across turns and sessions:
76
88
  - User preferences like response style, formatting, and workflow habits
77
89
  - Project facts like paths, architecture choices, and conventions
78
90
  - Important decisions and constraints that should not be rediscovered
79
- - Session-linked notes that still matter later
91
+ - Project-scoped notes that still matter later
80
92
 
81
93
  ## Tools
82
94
 
@@ -87,16 +99,12 @@ Save durable context for later recall.
87
99
  Inputs:
88
100
 
89
101
  - `content` -> fact, preference, decision, or context to store
90
- - `source` -> client, tool, or agent name
91
102
  - `workspace` -> repository or workspace path
92
- - `session` -> conversation or execution session identifier
93
103
 
94
104
  Output:
95
105
 
96
106
  - `id`
97
- - `source`
98
107
  - `workspace`
99
- - `session`
100
108
  - `created_at`
101
109
 
102
110
  ### `recall`
@@ -105,19 +113,17 @@ Retrieve relevant memories for the current task.
105
113
 
106
114
  Inputs:
107
115
 
108
- - `query` -> keywords, names, facts, or phrases to search for
116
+ - `terms` -> 2-5 distinctive terms or short phrases that should appear in the
117
+ memory content; avoid full natural-language questions
109
118
  - `limit` -> maximum results to return
110
- - `preferred_source` -> ranking hint for a source
111
119
  - `preferred_workspace` -> ranking hint for a workspace
112
- - `filter_source` -> exact source filter
113
120
  - `filter_workspace` -> exact workspace filter
114
121
  - `created_after` -> ISO 8601 lower bound
115
122
  - `created_before` -> ISO 8601 upper bound
116
123
 
117
124
  Output:
118
125
 
119
- - `results[]` with `id`, `content`, `score`, `source`, `workspace`, `session`,
120
- and `created_at`
126
+ - `results[]` with `id`, `content`, `score`, `workspace`, and `created_at`
121
127
 
122
128
  ## Setup
123
129
 
@@ -150,6 +156,9 @@ Set `AGENT_MEMORY_DB_PATH` when you want to:
150
156
  - share a memory DB across multiple clients
151
157
  - store the DB somewhere easier to back up or inspect
152
158
 
159
+ Beta note: schema changes are not migrated. If you are upgrading from an older
160
+ beta, delete the existing memory DB and let the server create a new one.
161
+
153
162
  ## Run from source
154
163
 
155
164
  If you are developing locally instead of using the published package:
package/dist/index.js CHANGED
@@ -12464,7 +12464,7 @@ class StdioServerTransport {
12464
12464
  }
12465
12465
  }
12466
12466
  // package.json
12467
- var version2 = "0.0.2";
12467
+ var version2 = "0.0.3";
12468
12468
 
12469
12469
  // src/config.ts
12470
12470
  import { homedir } from "node:os";
@@ -19932,11 +19932,9 @@ var parseOptionalDate = (value, fieldName) => {
19932
19932
 
19933
19933
  // src/tools/recall.ts
19934
19934
  var recallInputSchema = {
19935
- query: string2().describe("What to look for in memory. Use keywords, short phrases, names, decisions, or facts that should match previously remembered context."),
19935
+ terms: array(string2()).min(1).describe("Search terms to match against remembered content. Use distinctive keywords, IDs, names, file names, or short phrases as separate items."),
19936
19936
  limit: number2().int().min(1).max(20).optional().describe("Maximum number of memory results to return. Use a small number when you only need the best matches."),
19937
- preferred_source: string2().optional().describe("Preferred source to rank higher when relevant, such as a client, tool, or agent name. This does not exclude other sources."),
19938
19937
  preferred_workspace: string2().optional().describe("Preferred workspace or repository path to rank higher when relevant. This does not exclude other workspaces."),
19939
- filter_source: string2().optional().describe("Only return memories from this exact source."),
19940
19938
  filter_workspace: string2().optional().describe("Only return memories from this exact workspace or repository path."),
19941
19939
  created_after: string2().optional().describe("Only return memories created at or after this ISO 8601 timestamp."),
19942
19940
  created_before: string2().optional().describe("Only return memories created at or before this ISO 8601 timestamp.")
@@ -19944,11 +19942,9 @@ var recallInputSchema = {
19944
19942
  var recallOutputSchema = {
19945
19943
  results: array(object({
19946
19944
  id: string2().describe("Stable identifier for the remembered item."),
19947
- content: string2().describe("The remembered content that matched the query."),
19945
+ content: string2().describe("The remembered content that matched the search terms."),
19948
19946
  score: number2().describe("Relevance score for this result. Higher means a better match."),
19949
- source: string2().optional().describe("Source associated with the memory, if available."),
19950
19947
  workspace: string2().optional().describe("Workspace associated with the memory, if available."),
19951
- session: string2().optional().describe("Session associated with the memory, if available."),
19952
19948
  created_at: string2().describe("ISO 8601 timestamp showing when the memory was created.")
19953
19949
  }))
19954
19950
  };
@@ -19957,23 +19953,12 @@ var registerRecallTool = (server, memoryService) => {
19957
19953
  description: "Retrieve previously remembered context that may help with the current task. Use it for user preferences, project facts, prior decisions, constraints, or earlier conversation details.",
19958
19954
  inputSchema: recallInputSchema,
19959
19955
  outputSchema: recallOutputSchema
19960
- }, async ({
19961
- query,
19962
- limit,
19963
- preferred_source,
19964
- preferred_workspace,
19965
- filter_source,
19966
- filter_workspace,
19967
- created_after,
19968
- created_before
19969
- }) => {
19956
+ }, async ({ terms, limit, preferred_workspace, filter_workspace, created_after, created_before }) => {
19970
19957
  try {
19971
19958
  const results = await memoryService.search({
19972
- query,
19959
+ terms,
19973
19960
  limit,
19974
- preferredSource: preferred_source,
19975
19961
  preferredWorkspace: preferred_workspace,
19976
- filterSource: filter_source,
19977
19962
  filterWorkspace: filter_workspace,
19978
19963
  createdAfter: parseOptionalDate(created_after, "created_after"),
19979
19964
  createdBefore: parseOptionalDate(created_before, "created_before")
@@ -19983,17 +19968,17 @@ var registerRecallTool = (server, memoryService) => {
19983
19968
  id: result.id,
19984
19969
  content: result.content,
19985
19970
  score: result.score,
19986
- source: result.source,
19987
19971
  workspace: result.workspace,
19988
- session: result.session,
19989
19972
  created_at: result.createdAt.toISOString()
19990
19973
  }))
19991
19974
  };
19975
+ const matchCount = structuredContent.results.length;
19976
+ const summary = matchCount === 1 ? "Found 1 matching memory." : `Found ${matchCount} matching memories.`;
19992
19977
  return {
19993
19978
  content: [
19994
19979
  {
19995
19980
  type: "text",
19996
- text: JSON.stringify(structuredContent, null, 2)
19981
+ text: summary
19997
19982
  }
19998
19983
  ],
19999
19984
  structuredContent
@@ -20007,15 +19992,11 @@ var registerRecallTool = (server, memoryService) => {
20007
19992
  // src/tools/remember.ts
20008
19993
  var rememberInputSchema = {
20009
19994
  content: string2().describe("The exact fact, preference, decision, or context to remember for future retrieval. Use a self-contained sentence or short note."),
20010
- source: string2().optional().describe("Where this memory came from, such as the client, tool, or agent name. Helps with filtering and ranking later."),
20011
- workspace: string2().optional().describe("Repository or workspace path this memory belongs to. Use it to keep memories scoped to a project."),
20012
- session: string2().optional().describe("Conversation or execution session identifier. Useful for tying memories back to one run or thread.")
19995
+ workspace: string2().optional().describe("Repository or workspace path this memory belongs to. Use it to keep memories scoped to a project.")
20013
19996
  };
20014
19997
  var rememberOutputSchema = {
20015
19998
  id: string2().describe("Stable identifier for the saved memory."),
20016
- source: string2().optional().describe("Source stored with the memory, if provided."),
20017
19999
  workspace: string2().optional().describe("Workspace stored with the memory, if provided."),
20018
- session: string2().optional().describe("Session stored with the memory, if provided."),
20019
20000
  created_at: string2().describe("ISO 8601 timestamp showing when the memory was created.")
20020
20001
  };
20021
20002
  var registerRememberTool = (server, memoryService) => {
@@ -20023,26 +20004,22 @@ var registerRememberTool = (server, memoryService) => {
20023
20004
  description: "Save durable context for later recall. Use this for user preferences, project facts, decisions, constraints, or other information worth remembering across turns and tools.",
20024
20005
  inputSchema: rememberInputSchema,
20025
20006
  outputSchema: rememberOutputSchema
20026
- }, async ({ content, source, workspace, session }) => {
20007
+ }, async ({ content, workspace }) => {
20027
20008
  try {
20028
20009
  const memory = await memoryService.save({
20029
20010
  content,
20030
- source,
20031
- workspace,
20032
- session
20011
+ workspace
20033
20012
  });
20034
20013
  const structuredContent = {
20035
20014
  id: memory.id,
20036
- source: memory.source,
20037
20015
  workspace: memory.workspace,
20038
- session: memory.session,
20039
20016
  created_at: memory.createdAt.toISOString()
20040
20017
  };
20041
20018
  return {
20042
20019
  content: [
20043
20020
  {
20044
20021
  type: "text",
20045
- text: JSON.stringify(structuredContent, null, 2)
20022
+ text: "Saved memory."
20046
20023
  }
20047
20024
  ],
20048
20025
  structuredContent
@@ -20068,8 +20045,6 @@ var createMcpServer = (memoryService, version3) => {
20068
20045
  import { randomUUID } from "node:crypto";
20069
20046
  var DEFAULT_LIMIT = 5;
20070
20047
  var MAX_LIMIT = 20;
20071
- var SOURCE_BIAS = 0.15;
20072
- var WORKSPACE_BIAS = 0.1;
20073
20048
 
20074
20049
  class MemoryService {
20075
20050
  repository;
@@ -20085,34 +20060,27 @@ class MemoryService {
20085
20060
  const memory = {
20086
20061
  id: randomUUID(),
20087
20062
  content,
20088
- source: normalizeOptionalString(input.source),
20089
20063
  workspace: normalizeOptionalString(input.workspace),
20090
- session: normalizeOptionalString(input.session),
20091
20064
  createdAt: now,
20092
20065
  updatedAt: now
20093
20066
  };
20094
20067
  return this.repository.save(memory);
20095
20068
  }
20096
20069
  async search(input) {
20097
- const query = input.query.trim();
20098
- if (!query) {
20099
- throw new ValidationError("Search query is required.");
20070
+ const terms = normalizeTerms(input.terms);
20071
+ if (terms.length === 0) {
20072
+ throw new ValidationError("At least one search term is required.");
20100
20073
  }
20101
20074
  const normalizedQuery = {
20102
- query,
20075
+ terms,
20103
20076
  limit: normalizeLimit(input.limit),
20104
- preferredSource: normalizeOptionalString(input.preferredSource),
20105
20077
  preferredWorkspace: normalizeOptionalString(input.preferredWorkspace),
20106
- filterSource: normalizeOptionalString(input.filterSource),
20107
20078
  filterWorkspace: normalizeOptionalString(input.filterWorkspace),
20108
20079
  createdAfter: input.createdAfter,
20109
20080
  createdBefore: input.createdBefore
20110
20081
  };
20111
20082
  const results = await this.repository.search(normalizedQuery);
20112
- return results.map((result) => ({
20113
- ...result,
20114
- score: rankResult(result, normalizedQuery)
20115
- })).sort((left, right) => right.score - left.score).slice(0, normalizedQuery.limit);
20083
+ return results.slice(0, normalizedQuery.limit);
20116
20084
  }
20117
20085
  }
20118
20086
  var normalizeLimit = (value) => {
@@ -20128,15 +20096,9 @@ var normalizeOptionalString = (value) => {
20128
20096
  const trimmed = value?.trim();
20129
20097
  return trimmed ? trimmed : undefined;
20130
20098
  };
20131
- var rankResult = (result, query) => {
20132
- let score = result.score;
20133
- if (query.preferredSource && result.source === query.preferredSource) {
20134
- score += SOURCE_BIAS;
20135
- }
20136
- if (query.preferredWorkspace && result.workspace === query.preferredWorkspace) {
20137
- score += WORKSPACE_BIAS;
20138
- }
20139
- return Number(score.toFixed(6));
20099
+ var normalizeTerms = (terms) => {
20100
+ const normalizedTerms = terms.map((term) => term.trim()).filter(Boolean);
20101
+ return [...new Set(normalizedTerms)];
20140
20102
  };
20141
20103
 
20142
20104
  // src/sqlite-db.ts
@@ -20147,22 +20109,19 @@ var MEMORY_SCHEMA = `
20147
20109
  CREATE TABLE IF NOT EXISTS memories (
20148
20110
  id TEXT PRIMARY KEY,
20149
20111
  content TEXT NOT NULL,
20150
- source TEXT,
20151
20112
  workspace TEXT,
20152
- session TEXT,
20153
20113
  created_at INTEGER NOT NULL,
20154
20114
  updated_at INTEGER NOT NULL
20155
20115
  );
20156
20116
 
20157
20117
  CREATE INDEX IF NOT EXISTS idx_memories_created_at ON memories(created_at);
20158
- CREATE INDEX IF NOT EXISTS idx_memories_source ON memories(source);
20159
20118
  CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace);
20160
20119
 
20161
20120
  CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
20162
20121
  content,
20163
20122
  content = 'memories',
20164
20123
  content_rowid = 'rowid',
20165
- tokenize = 'unicode61'
20124
+ tokenize = 'porter unicode61'
20166
20125
  );
20167
20126
 
20168
20127
  CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
@@ -20211,6 +20170,7 @@ var initializeMemoryDatabase = (database) => {
20211
20170
  var CANDIDATE_MULTIPLIER = 5;
20212
20171
  var MIN_CANDIDATES = 25;
20213
20172
  var MAX_CANDIDATES = 100;
20173
+ var WORKSPACE_BIAS = 0.1;
20214
20174
 
20215
20175
  class SqliteMemoryRepository {
20216
20176
  database;
@@ -20221,9 +20181,7 @@ class SqliteMemoryRepository {
20221
20181
  INSERT INTO memories (
20222
20182
  id,
20223
20183
  content,
20224
- source,
20225
20184
  workspace,
20226
- session,
20227
20185
  created_at,
20228
20186
  updated_at
20229
20187
  ) VALUES (
@@ -20231,15 +20189,13 @@ class SqliteMemoryRepository {
20231
20189
  ?,
20232
20190
  ?,
20233
20191
  ?,
20234
- ?,
20235
- ?,
20236
20192
  ?
20237
20193
  )
20238
20194
  `);
20239
20195
  }
20240
20196
  async save(memory) {
20241
20197
  try {
20242
- this.insertStatement.run(memory.id, memory.content, memory.source, memory.workspace, memory.session, memory.createdAt.getTime(), memory.updatedAt.getTime());
20198
+ this.insertStatement.run(memory.id, memory.content, memory.workspace, memory.createdAt.getTime(), memory.updatedAt.getTime());
20243
20199
  return memory;
20244
20200
  } catch (error2) {
20245
20201
  throw new PersistenceError("Failed to save memory.", { cause: error2 });
@@ -20247,48 +20203,48 @@ class SqliteMemoryRepository {
20247
20203
  }
20248
20204
  async search(query) {
20249
20205
  try {
20250
- const whereClauses = ["memories_fts MATCH ?"];
20251
- const params = [toFtsQuery(query.query)];
20252
- if (query.filterSource) {
20253
- whereClauses.push("m.source = ?");
20254
- params.push(query.filterSource);
20206
+ const selectParams = [];
20207
+ const whereParams = [toFtsQuery(query.terms)];
20208
+ let scoreExpr;
20209
+ if (query.preferredWorkspace) {
20210
+ scoreExpr = "MAX(0, -bm25(memories_fts) + CASE WHEN m.workspace = ? THEN ? ELSE 0.0 END)";
20211
+ selectParams.push(query.preferredWorkspace, WORKSPACE_BIAS);
20212
+ } else {
20213
+ scoreExpr = "MAX(0, -bm25(memories_fts))";
20255
20214
  }
20215
+ const whereClauses = ["memories_fts MATCH ?"];
20256
20216
  if (query.filterWorkspace) {
20257
20217
  whereClauses.push("m.workspace = ?");
20258
- params.push(query.filterWorkspace);
20218
+ whereParams.push(query.filterWorkspace);
20259
20219
  }
20260
20220
  if (query.createdAfter) {
20261
20221
  whereClauses.push("m.created_at >= ?");
20262
- params.push(query.createdAfter.getTime());
20222
+ whereParams.push(query.createdAfter.getTime());
20263
20223
  }
20264
20224
  if (query.createdBefore) {
20265
20225
  whereClauses.push("m.created_at <= ?");
20266
- params.push(query.createdBefore.getTime());
20226
+ whereParams.push(query.createdBefore.getTime());
20267
20227
  }
20228
+ const params = [...selectParams, ...whereParams, toCandidateLimit(query.limit)];
20268
20229
  const statement = this.database.prepare(`
20269
20230
  SELECT
20270
20231
  m.id,
20271
20232
  m.content,
20272
- m.source,
20273
20233
  m.workspace,
20274
- m.session,
20275
20234
  m.created_at,
20276
- MAX(0, -bm25(memories_fts)) AS score
20235
+ ${scoreExpr} AS score
20277
20236
  FROM memories_fts
20278
20237
  INNER JOIN memories AS m ON m.rowid = memories_fts.rowid
20279
20238
  WHERE ${whereClauses.join(" AND ")}
20280
- ORDER BY bm25(memories_fts)
20239
+ ORDER BY score DESC
20281
20240
  LIMIT ?
20282
20241
  `);
20283
- params.push(toCandidateLimit(query.limit));
20284
20242
  const rows = statement.all(...params);
20285
20243
  return rows.map((row) => ({
20286
20244
  id: row.id,
20287
20245
  content: row.content,
20288
20246
  score: row.score,
20289
- source: row.source ?? undefined,
20290
20247
  workspace: row.workspace ?? undefined,
20291
- session: row.session ?? undefined,
20292
20248
  createdAt: new Date(row.created_at)
20293
20249
  }));
20294
20250
  } catch (error2) {
@@ -20299,7 +20255,14 @@ class SqliteMemoryRepository {
20299
20255
  }
20300
20256
  }
20301
20257
  var toCandidateLimit = (limit) => Math.min(Math.max(limit * CANDIDATE_MULTIPLIER, MIN_CANDIDATES), MAX_CANDIDATES);
20302
- var toFtsQuery = (query) => query.trim().split(/\s+/).filter(Boolean).map((term) => `"${term.replaceAll('"', '""')}"`).join(" ");
20258
+ var toFtsQuery = (terms) => terms.map(toFtsTerm).join(" OR ");
20259
+ var toFtsTerm = (term) => {
20260
+ const escaped = term.replaceAll('"', '""');
20261
+ if (term.includes(" ")) {
20262
+ return `"${escaped}"`;
20263
+ }
20264
+ return `"${escaped}"*`;
20265
+ };
20303
20266
 
20304
20267
  // src/index.ts
20305
20268
  var { databasePath } = resolveConfig();
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.2",
4
+ "version": "0.0.3",
5
5
  "bin": {
6
6
  "agent-memory": "dist/index.js"
7
7
  },
@@ -23,7 +23,7 @@
23
23
  "build": "bun build src/index.ts --target=node --external better-sqlite3 --outfile dist/index.js --banner \"#!/usr/bin/env node\"",
24
24
  "start": "node dist/index.js",
25
25
  "test": "bun test",
26
- "lint": "biome check --write",
26
+ "lint": "biome check --write && tsc --noEmit",
27
27
  "release:patch": "npm version patch && git push origin main --follow-tags",
28
28
  "release:minor": "npm version minor && git push origin main --follow-tags",
29
29
  "release:major": "npm version major && git push origin main --follow-tags"