@jcyamacho/agent-memory 0.0.13 → 0.0.15

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 +58 -67
  2. package/dist/index.js +466 -138
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -62,16 +62,34 @@ With a custom database path:
62
62
  }
63
63
  ```
64
64
 
65
+ With a custom model cache path:
66
+
67
+ ```json
68
+ {
69
+ "mcpServers": {
70
+ "memory": {
71
+ "command": "npx",
72
+ "args": [
73
+ "-y",
74
+ "@jcyamacho/agent-memory"
75
+ ],
76
+ "env": {
77
+ "AGENT_MEMORY_MODELS_CACHE_PATH": "/absolute/path/to/models"
78
+ }
79
+ }
80
+ }
81
+ }
82
+ ```
83
+
65
84
  Optional LLM instructions to reinforce the MCP's built-in guidance:
66
85
 
67
86
  ```text
68
- Use `recall` at the start of every conversation and again mid-task before
69
- making design choices or picking conventions. Use `remember` when the user
70
- corrects your approach, a key decision is established, or you learn project
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.
87
+ Use `recall` at conversation start and before design choices, conventions, or
88
+ edge cases. Query with 2-5 short anchor-heavy terms or exact phrases, not
89
+ questions or sentences. `recall` is lexical-first; if it misses, retry once
90
+ with overlapping alternate terms. Use `remember` for one durable fact, then
91
+ use `revise` instead of duplicates and `forget` for wrong or obsolete
92
+ memories. Always pass workspace unless the memory is truly global.
75
93
  ```
76
94
 
77
95
  ## What It Stores
@@ -101,60 +119,10 @@ The web UI uses the same database as the MCP server.
101
119
 
102
120
  ## Tools
103
121
 
104
- ### `remember`
105
-
106
- Save durable context for later recall.
107
-
108
- Inputs:
109
-
110
- - `content` -> fact, preference, decision, or context to store
111
- - `workspace` -> repository or workspace path
112
-
113
- Output:
114
-
115
- - `id`
116
-
117
- ### `recall`
118
-
119
- Retrieve relevant memories for the current task.
120
-
121
- Inputs:
122
-
123
- - `terms` -> 2-5 distinctive terms or short phrases that should appear in the
124
- memory content; avoid full natural-language questions
125
- - `limit` -> maximum results to return
126
- - `workspace` -> workspace or repo path; biases ranking toward this workspace
127
- - `updated_after` -> ISO 8601 lower bound
128
- - `updated_before` -> ISO 8601 upper bound
129
-
130
- Output:
131
-
132
- - `results[]` with `id`, `content`, `score`, `workspace`, and `updated_at`
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`
122
+ - `remember` saves durable facts, preferences, decisions, and project context.
123
+ - `recall` retrieves the most relevant saved memories.
124
+ - `revise` updates an existing memory when it becomes outdated.
125
+ - `forget` deletes a memory that is no longer relevant.
158
126
 
159
127
  ## How Ranking Works
160
128
 
@@ -163,18 +131,21 @@ memories:
163
131
 
164
132
  1. **Text relevance** is the primary signal -- memories whose content best
165
133
  matches your search terms rank highest.
166
- 2. **Workspace match** is a strong secondary signal. When you pass
134
+ 2. **Embedding similarity** is the next strongest signal. Recall builds an
135
+ embedding from your normalized search terms and boosts memories whose stored
136
+ embeddings are most semantically similar.
137
+ 3. **Workspace match** is a strong secondary signal. When you pass
167
138
  `workspace`, exact matches rank highest, sibling repositories get a small
168
139
  boost, and unrelated workspaces rank lowest.
169
- 3. **Global memories** (saved without a workspace) are treated as relevant
140
+ 4. **Global memories** (saved without a workspace) are treated as relevant
170
141
  everywhere. When you pass `workspace`, they rank below exact workspace
171
142
  matches and above sibling or unrelated repositories.
172
- 4. **Recency** is a minor tiebreaker -- newer memories rank slightly above older
143
+ 5. **Recency** is a minor tiebreaker -- newer memories rank slightly above older
173
144
  ones when other signals are equal.
174
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
+ If you omit `workspace`, recall still uses text relevance, embedding similarity,
147
+ and recency. For best results, pass `workspace` whenever you have one. Save
148
+ memories without a workspace only when they apply across all projects.
178
149
 
179
150
  ## Database location
180
151
 
@@ -196,6 +167,26 @@ Set `AGENT_MEMORY_DB_PATH` when you want to:
196
167
  - share a memory DB across multiple clients
197
168
  - store the DB somewhere easier to back up or inspect
198
169
 
170
+ ## Model cache location
171
+
172
+ By default, downloaded embedding model files are cached at:
173
+
174
+ ```text
175
+ ~/.config/agent-memory/models
176
+ ```
177
+
178
+ Override it with:
179
+
180
+ ```bash
181
+ AGENT_MEMORY_MODELS_CACHE_PATH=/absolute/path/to/models
182
+ ```
183
+
184
+ Set `AGENT_MEMORY_MODELS_CACHE_PATH` when you want to:
185
+
186
+ - keep model artifacts out of `node_modules`
187
+ - share the model cache across reinstalls or multiple clients
188
+ - store model downloads somewhere easier to inspect or manage
189
+
199
190
  Beta note: schema changes are not migrated. If you are upgrading from an older
200
191
  beta, delete the existing memory DB and let the server create a new one.
201
192
 
package/dist/index.js CHANGED
@@ -2431,9 +2431,9 @@ var require_validate = __commonJS((exports) => {
2431
2431
  }
2432
2432
  }
2433
2433
  function returnResults(it) {
2434
- const { gen, schemaEnv, validateName, ValidationError, opts } = it;
2434
+ const { gen, schemaEnv, validateName, ValidationError: ValidationError2, opts } = it;
2435
2435
  if (schemaEnv.$async) {
2436
- gen.if((0, codegen_1._)`${names_1.default.errors} === 0`, () => gen.return(names_1.default.data), () => gen.throw((0, codegen_1._)`new ${ValidationError}(${names_1.default.vErrors})`));
2436
+ gen.if((0, codegen_1._)`${names_1.default.errors} === 0`, () => gen.return(names_1.default.data), () => gen.throw((0, codegen_1._)`new ${ValidationError2}(${names_1.default.vErrors})`));
2437
2437
  } else {
2438
2438
  gen.assign((0, codegen_1._)`${validateName}.errors`, names_1.default.vErrors);
2439
2439
  if (opts.unevaluated)
@@ -2783,14 +2783,14 @@ var require_validate = __commonJS((exports) => {
2783
2783
  var require_validation_error = __commonJS((exports) => {
2784
2784
  Object.defineProperty(exports, "__esModule", { value: true });
2785
2785
 
2786
- class ValidationError extends Error {
2786
+ class ValidationError2 extends Error {
2787
2787
  constructor(errors3) {
2788
2788
  super("validation failed");
2789
2789
  this.errors = errors3;
2790
2790
  this.ajv = this.validation = true;
2791
2791
  }
2792
2792
  }
2793
- exports.default = ValidationError;
2793
+ exports.default = ValidationError2;
2794
2794
  });
2795
2795
 
2796
2796
  // node_modules/ajv/dist/compile/ref_error.js
@@ -12464,13 +12464,14 @@ class StdioServerTransport {
12464
12464
  }
12465
12465
  }
12466
12466
  // package.json
12467
- var version2 = "0.0.13";
12467
+ var version2 = "0.0.15";
12468
12468
 
12469
12469
  // src/config.ts
12470
12470
  import { homedir } from "node:os";
12471
12471
  import { join } from "node:path";
12472
12472
  import { parseArgs } from "node:util";
12473
12473
  var AGENT_MEMORY_DB_PATH_ENV = "AGENT_MEMORY_DB_PATH";
12474
+ var AGENT_MEMORY_MODELS_CACHE_PATH_ENV = "AGENT_MEMORY_MODELS_CACHE_PATH";
12474
12475
  var DEFAULT_UI_PORT = 6580;
12475
12476
  function resolveConfig(environment = process.env, argv = process.argv.slice(2)) {
12476
12477
  const { values } = parseArgs({
@@ -12482,12 +12483,146 @@ function resolveConfig(environment = process.env, argv = process.argv.slice(2))
12482
12483
  strict: false
12483
12484
  });
12484
12485
  return {
12485
- databasePath: environment[AGENT_MEMORY_DB_PATH_ENV] || join(homedir(), ".config", "agent-memory", "memory.db"),
12486
+ databasePath: resolveDatabasePath(environment),
12487
+ modelsCachePath: resolveModelsCachePath(environment),
12486
12488
  uiMode: Boolean(values.ui),
12487
12489
  uiPort: Number(values.port) || DEFAULT_UI_PORT
12488
12490
  };
12489
12491
  }
12492
+ function resolveDatabasePath(environment = process.env) {
12493
+ return environment[AGENT_MEMORY_DB_PATH_ENV] || join(homedir(), ".config", "agent-memory", "memory.db");
12494
+ }
12495
+ function resolveModelsCachePath(environment = process.env) {
12496
+ return environment[AGENT_MEMORY_MODELS_CACHE_PATH_ENV] || join(homedir(), ".config", "agent-memory", "models");
12497
+ }
12498
+
12499
+ // src/embedding/service.ts
12500
+ import { mkdirSync } from "node:fs";
12501
+ import { pipeline, env as transformersEnv } from "@huggingface/transformers";
12502
+
12503
+ // src/errors.ts
12504
+ class MemoryError extends Error {
12505
+ code;
12506
+ constructor(code, message, options) {
12507
+ super(message, options);
12508
+ this.name = "MemoryError";
12509
+ this.code = code;
12510
+ }
12511
+ }
12512
+
12513
+ class ValidationError extends MemoryError {
12514
+ constructor(message) {
12515
+ super("VALIDATION_ERROR", message);
12516
+ this.name = "ValidationError";
12517
+ }
12518
+ }
12490
12519
 
12520
+ class NotFoundError extends MemoryError {
12521
+ constructor(message) {
12522
+ super("NOT_FOUND", message);
12523
+ this.name = "NotFoundError";
12524
+ }
12525
+ }
12526
+
12527
+ class PersistenceError extends MemoryError {
12528
+ constructor(message, options) {
12529
+ super("PERSISTENCE_ERROR", message, options);
12530
+ this.name = "PersistenceError";
12531
+ }
12532
+ }
12533
+
12534
+ // src/embedding/service.ts
12535
+ var DEFAULT_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2";
12536
+
12537
+ class EmbeddingService {
12538
+ options;
12539
+ extractorPromise;
12540
+ modelsCachePath;
12541
+ constructor(options = {}) {
12542
+ this.options = options;
12543
+ this.modelsCachePath = options.modelsCachePath ?? resolveModelsCachePath();
12544
+ }
12545
+ async createVector(text) {
12546
+ const normalizedText = text.trim();
12547
+ if (!normalizedText) {
12548
+ throw new ValidationError("Text is required.");
12549
+ }
12550
+ const extractor = await this.getExtractor();
12551
+ const embedding = await extractor(normalizedText);
12552
+ return normalizeVector(embedding.tolist());
12553
+ }
12554
+ getExtractor() {
12555
+ if (!this.extractorPromise) {
12556
+ configureModelsCache(this.modelsCachePath);
12557
+ this.extractorPromise = (this.options.createExtractor ?? createDefaultExtractor)();
12558
+ }
12559
+ return this.extractorPromise;
12560
+ }
12561
+ }
12562
+ function configureModelsCache(modelsCachePath) {
12563
+ mkdirSync(modelsCachePath, { recursive: true });
12564
+ transformersEnv.useFSCache = true;
12565
+ transformersEnv.cacheDir = modelsCachePath;
12566
+ }
12567
+ async function createDefaultExtractor() {
12568
+ const extractor = await pipeline("feature-extraction", DEFAULT_EMBEDDING_MODEL);
12569
+ return (text) => extractor(text, {
12570
+ pooling: "mean",
12571
+ normalize: true
12572
+ });
12573
+ }
12574
+ function normalizeVector(value) {
12575
+ if (value.length === 0) {
12576
+ throw new ValidationError("Embedding model returned an empty vector.");
12577
+ }
12578
+ const [firstItem] = value;
12579
+ if (typeof firstItem === "number") {
12580
+ return value.map((item) => {
12581
+ if (typeof item !== "number" || !Number.isFinite(item)) {
12582
+ throw new ValidationError("Embedding model returned a non-numeric vector.");
12583
+ }
12584
+ return item;
12585
+ });
12586
+ }
12587
+ if (Array.isArray(firstItem)) {
12588
+ return normalizeVector(firstItem);
12589
+ }
12590
+ throw new ValidationError("Embedding model returned an unexpected vector shape.");
12591
+ }
12592
+ // src/embedding/similarity.ts
12593
+ function compareVectors(left, right) {
12594
+ validateVector(left, "Left vector");
12595
+ validateVector(right, "Right vector");
12596
+ if (left.length !== right.length) {
12597
+ throw new ValidationError("Vectors must have the same length.");
12598
+ }
12599
+ let dotProduct = 0;
12600
+ let leftMagnitude = 0;
12601
+ let rightMagnitude = 0;
12602
+ for (const [index, leftValue] of left.entries()) {
12603
+ const rightValue = right[index];
12604
+ if (rightValue === undefined) {
12605
+ throw new ValidationError("Vectors must have the same length.");
12606
+ }
12607
+ dotProduct += leftValue * rightValue;
12608
+ leftMagnitude += leftValue * leftValue;
12609
+ rightMagnitude += rightValue * rightValue;
12610
+ }
12611
+ if (leftMagnitude === 0 || rightMagnitude === 0) {
12612
+ throw new ValidationError("Vectors must not have zero magnitude.");
12613
+ }
12614
+ return dotProduct / (Math.sqrt(leftMagnitude) * Math.sqrt(rightMagnitude));
12615
+ }
12616
+ function validateVector(vector, label) {
12617
+ if (vector.length === 0) {
12618
+ throw new ValidationError(`${label} must not be empty.`);
12619
+ }
12620
+ for (const value of vector) {
12621
+ if (!Number.isFinite(value)) {
12622
+ throw new ValidationError(`${label} must contain only finite numbers.`);
12623
+ }
12624
+ }
12625
+ }
12491
12626
  // node_modules/zod/v3/helpers/util.js
12492
12627
  var util;
12493
12628
  (function(util2) {
@@ -19895,37 +20030,6 @@ var EMPTY_COMPLETION_RESULT = {
19895
20030
  }
19896
20031
  };
19897
20032
 
19898
- // src/errors.ts
19899
- class MemoryError extends Error {
19900
- code;
19901
- constructor(code, message, options) {
19902
- super(message, options);
19903
- this.name = "MemoryError";
19904
- this.code = code;
19905
- }
19906
- }
19907
-
19908
- class ValidationError extends MemoryError {
19909
- constructor(message) {
19910
- super("VALIDATION_ERROR", message);
19911
- this.name = "ValidationError";
19912
- }
19913
- }
19914
-
19915
- class NotFoundError extends MemoryError {
19916
- constructor(message) {
19917
- super("NOT_FOUND", message);
19918
- this.name = "NotFoundError";
19919
- }
19920
- }
19921
-
19922
- class PersistenceError extends MemoryError {
19923
- constructor(message, options) {
19924
- super("PERSISTENCE_ERROR", message, options);
19925
- this.name = "PersistenceError";
19926
- }
19927
- }
19928
-
19929
20033
  // src/mcp/tools/shared.ts
19930
20034
  function toMcpError(error2) {
19931
20035
  if (error2 instanceof McpError) {
@@ -19954,11 +20058,11 @@ function parseOptionalDate(value, fieldName) {
19954
20058
 
19955
20059
  // src/mcp/tools/forget.ts
19956
20060
  var forgetInputSchema = {
19957
- id: string2().describe("The id of the memory to delete. Use the id returned by a previous recall result.")
20061
+ id: string2().describe("The memory id to delete. Use an id returned by `recall`.")
19958
20062
  };
19959
20063
  function registerForgetTool(server, memory) {
19960
20064
  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.",
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" />`.',
19962
20066
  inputSchema: forgetInputSchema
19963
20067
  }, async ({ id }) => {
19964
20068
  try {
@@ -19977,12 +20081,13 @@ var toNormalizedScore = (value) => value;
19977
20081
 
19978
20082
  // src/ranking.ts
19979
20083
  var RETRIEVAL_SCORE_WEIGHT = 8;
20084
+ var EMBEDDING_SIMILARITY_WEIGHT = 5;
19980
20085
  var WORKSPACE_MATCH_WEIGHT = 4;
19981
- var RECENCY_WEIGHT = 1;
19982
- var MAX_COMPOSITE_SCORE = RETRIEVAL_SCORE_WEIGHT + WORKSPACE_MATCH_WEIGHT + RECENCY_WEIGHT;
20086
+ var RECENCY_WEIGHT = 2;
20087
+ var MAX_COMPOSITE_SCORE = RETRIEVAL_SCORE_WEIGHT + EMBEDDING_SIMILARITY_WEIGHT + WORKSPACE_MATCH_WEIGHT + RECENCY_WEIGHT;
19983
20088
  var GLOBAL_WORKSPACE_SCORE = 0.5;
19984
20089
  var SIBLING_WORKSPACE_SCORE = 0.25;
19985
- function rerankSearchResults(results, workspace) {
20090
+ function rerankSearchResults(results, workspace, queryEmbedding) {
19986
20091
  if (results.length <= 1) {
19987
20092
  return results;
19988
20093
  }
@@ -19991,18 +20096,25 @@ function rerankSearchResults(results, workspace) {
19991
20096
  const minUpdatedAt = Math.min(...updatedAtTimes);
19992
20097
  const maxUpdatedAt = Math.max(...updatedAtTimes);
19993
20098
  return results.map((result) => {
20099
+ const embeddingSimilarityScore = computeEmbeddingSimilarityScore(result, queryEmbedding);
19994
20100
  const workspaceScore = computeWorkspaceScore(result.workspace, normalizedQueryWs);
19995
20101
  const recencyScore = maxUpdatedAt === minUpdatedAt ? 0 : (result.updatedAt.getTime() - minUpdatedAt) / (maxUpdatedAt - minUpdatedAt);
19996
- const combinedScore = (result.score * RETRIEVAL_SCORE_WEIGHT + workspaceScore * WORKSPACE_MATCH_WEIGHT + recencyScore * RECENCY_WEIGHT) / MAX_COMPOSITE_SCORE;
20102
+ const combinedScore = (result.score * RETRIEVAL_SCORE_WEIGHT + embeddingSimilarityScore * EMBEDDING_SIMILARITY_WEIGHT + workspaceScore * WORKSPACE_MATCH_WEIGHT + recencyScore * RECENCY_WEIGHT) / MAX_COMPOSITE_SCORE;
19997
20103
  return {
19998
20104
  ...result,
19999
20105
  score: toNormalizedScore(combinedScore)
20000
20106
  };
20001
20107
  }).sort((a, b) => b.score - a.score);
20002
20108
  }
20109
+ function computeEmbeddingSimilarityScore(result, queryEmbedding) {
20110
+ return normalizeCosineSimilarity(compareVectors(result.embedding, queryEmbedding));
20111
+ }
20003
20112
  function normalizeWorkspacePath(value) {
20004
20113
  return value.trim().replaceAll("\\", "/").replace(/\/+/g, "/").split("/").filter(Boolean).join("/");
20005
20114
  }
20115
+ function normalizeCosineSimilarity(value) {
20116
+ return (value + 1) / 2;
20117
+ }
20006
20118
  function computeWorkspaceScore(memoryWs, queryWs) {
20007
20119
  if (!queryWs) {
20008
20120
  return 0;
@@ -20033,24 +20145,33 @@ var MAX_LIST_LIMIT = 100;
20033
20145
 
20034
20146
  class MemoryService {
20035
20147
  repository;
20036
- constructor(repository) {
20148
+ embeddingService;
20149
+ constructor(repository, embeddingService) {
20037
20150
  this.repository = repository;
20151
+ this.embeddingService = embeddingService;
20038
20152
  }
20039
20153
  async create(input) {
20040
20154
  const content = input.content.trim();
20041
20155
  if (!content) {
20042
20156
  throw new ValidationError("Memory content is required.");
20043
20157
  }
20044
- return this.repository.create({
20158
+ const memory = await this.repository.create({
20045
20159
  content,
20160
+ embedding: await this.embeddingService.createVector(content),
20046
20161
  workspace: normalizeOptionalString(input.workspace)
20047
20162
  });
20163
+ return toPublicMemoryRecord(memory);
20048
20164
  }
20049
20165
  async update(input) {
20050
20166
  const content = input.content.trim();
20051
20167
  if (!content)
20052
20168
  throw new ValidationError("Memory content is required.");
20053
- return this.repository.update({ id: input.id, content });
20169
+ const memory = await this.repository.update({
20170
+ id: input.id,
20171
+ content,
20172
+ embedding: await this.embeddingService.createVector(content)
20173
+ });
20174
+ return toPublicMemoryRecord(memory);
20054
20175
  }
20055
20176
  async delete(input) {
20056
20177
  const id = input.id.trim();
@@ -20059,16 +20180,18 @@ class MemoryService {
20059
20180
  return this.repository.delete({ id });
20060
20181
  }
20061
20182
  async get(id) {
20062
- return this.repository.get(id);
20183
+ const memory = await this.repository.get(id);
20184
+ return memory ? toPublicMemoryRecord(memory) : undefined;
20063
20185
  }
20064
20186
  async list(input) {
20065
20187
  const workspace = normalizeOptionalString(input.workspace);
20066
- return this.repository.list({
20188
+ const page = await this.repository.list({
20067
20189
  workspace,
20068
20190
  workspaceIsNull: workspace ? false : Boolean(input.workspaceIsNull),
20069
20191
  offset: normalizeOffset(input.offset),
20070
20192
  limit: normalizeListLimit(input.limit)
20071
20193
  });
20194
+ return toPublicMemoryPage(page);
20072
20195
  }
20073
20196
  async listWorkspaces() {
20074
20197
  return this.repository.listWorkspaces();
@@ -20086,10 +20209,38 @@ class MemoryService {
20086
20209
  updatedAfter: input.updatedAfter,
20087
20210
  updatedBefore: input.updatedBefore
20088
20211
  };
20089
- const results = await this.repository.search(normalizedQuery);
20090
- return rerankSearchResults(results, workspace).slice(0, requestedLimit);
20212
+ const [results, queryEmbedding] = await Promise.all([
20213
+ this.repository.search(normalizedQuery),
20214
+ this.embeddingService.createVector(terms.join(" "))
20215
+ ]);
20216
+ return rerankSearchResults(results, workspace, queryEmbedding).slice(0, requestedLimit).map(toPublicSearchResult);
20091
20217
  }
20092
20218
  }
20219
+ function toPublicMemoryRecord(memory) {
20220
+ return {
20221
+ id: memory.id,
20222
+ content: memory.content,
20223
+ workspace: memory.workspace,
20224
+ createdAt: memory.createdAt,
20225
+ updatedAt: memory.updatedAt
20226
+ };
20227
+ }
20228
+ function toPublicSearchResult(result) {
20229
+ return {
20230
+ id: result.id,
20231
+ content: result.content,
20232
+ score: result.score,
20233
+ workspace: result.workspace,
20234
+ createdAt: result.createdAt,
20235
+ updatedAt: result.updatedAt
20236
+ };
20237
+ }
20238
+ function toPublicMemoryPage(page) {
20239
+ return {
20240
+ items: page.items.map(toPublicMemoryRecord),
20241
+ hasMore: page.hasMore
20242
+ };
20243
+ }
20093
20244
  function normalizeLimit(value) {
20094
20245
  if (value === undefined) {
20095
20246
  return DEFAULT_RECALL_LIMIT;
@@ -20119,22 +20270,23 @@ function normalizeTerms(terms) {
20119
20270
 
20120
20271
  // src/mcp/tools/recall.ts
20121
20272
  var recallInputSchema = {
20122
- terms: array(string2()).min(1).describe("Search terms used to find relevant memories. Pass 2-5 short, distinctive items as separate array entries. Be specific: instead of 'preferences' or 'context', name the actual topic -- e.g. 'error handling', 'commit format', 'testing strategy'. Do not repeat the project or workspace name here -- use the workspace parameter for project scoping. Avoid full sentences."),
20123
- limit: number2().int().min(1).max(MAX_RECALL_LIMIT).optional().describe("Maximum number of matches to return. Keep this small when you only need the strongest hits."),
20124
- workspace: string2().optional().describe("Always pass the current working directory. Biases ranking toward the active project while still allowing cross-workspace matches. Memories saved without a workspace are treated as global and rank between matching and non-matching results."),
20125
- updated_after: string2().optional().describe("Only return memories updated at or after this ISO 8601 timestamp. Use it when you need to narrow recall to newer context."),
20126
- updated_before: string2().optional().describe("Only return memories updated at or before this ISO 8601 timestamp. Use it when you need to narrow recall to older context.")
20273
+ 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."),
20274
+ 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."),
20275
+ workspace: string2().optional().describe("Pass the current working directory. This strongly boosts memories from the active project while still allowing global and cross-workspace matches."),
20276
+ updated_after: string2().optional().describe("Only return memories updated at or after this ISO 8601 timestamp."),
20277
+ updated_before: string2().optional().describe("Only return memories updated at or before this ISO 8601 timestamp.")
20127
20278
  };
20128
20279
  function toMemoryXml(r) {
20129
20280
  const workspace = r.workspace ? ` workspace="${escapeXml(r.workspace)}"` : "";
20130
20281
  const content = escapeXml(r.content);
20131
- return `<memory id="${r.id}" score="${r.score}"${workspace} updated_at="${r.updatedAt.toISOString()}">
20282
+ const score = Number(r.score.toFixed(3)).toString();
20283
+ return `<memory id="${r.id}" score="${score}"${workspace} updated_at="${r.updatedAt.toISOString()}">
20132
20284
  ${content}
20133
20285
  </memory>`;
20134
20286
  }
20135
20287
  function registerRecallTool(server, memory) {
20136
20288
  server.registerTool("recall", {
20137
- description: "Search memories for prior decisions, corrections, and context that cannot be derived from code or git history. Call at the start of every conversation and again mid-task when you are about to make a design choice, pick a convention, or handle an edge case that the user may have guided before. Always pass workspace.",
20289
+ 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. Returns `<memories>...</memories>` or a no-match hint.",
20138
20290
  inputSchema: recallInputSchema
20139
20291
  }, async ({ terms, limit, workspace, updated_after, updated_before }) => {
20140
20292
  try {
@@ -20145,7 +20297,7 @@ function registerRecallTool(server, memory) {
20145
20297
  updatedAfter: parseOptionalDate(updated_after, "updated_after"),
20146
20298
  updatedBefore: parseOptionalDate(updated_before, "updated_before")
20147
20299
  });
20148
- const text = results.length === 0 ? "No matching memories found." : `<memories>
20300
+ 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>
20149
20301
  ${results.map(toMemoryXml).join(`
20150
20302
  `)}
20151
20303
  </memories>`;
@@ -20160,12 +20312,12 @@ ${results.map(toMemoryXml).join(`
20160
20312
 
20161
20313
  // src/mcp/tools/remember.ts
20162
20314
  var rememberInputSchema = {
20163
- content: string2().describe("The fact, preference, decision, or context to remember. Use a single self-contained sentence or short note. One fact per memory."),
20164
- workspace: string2().optional().describe("Always pass the current working directory to scope this memory to a project. Omit only when the memory applies across all projects (global preference).")
20315
+ 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."),
20316
+ workspace: string2().optional().describe("Pass the current working directory for project-specific memories. Omit only for truly global memories.")
20165
20317
  };
20166
20318
  function registerRememberTool(server, memory) {
20167
20319
  server.registerTool("remember", {
20168
- description: "Save durable context for later recall. Use this when the user corrects your approach, states a preference, a key decision or convention is established, or you learn project context not obvious from the code. Store one concise fact per memory. Do not store secrets, ephemeral task state, or information already in the codebase.",
20320
+ 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="..." />`.',
20169
20321
  inputSchema: rememberInputSchema
20170
20322
  }, async ({ content, workspace }) => {
20171
20323
  try {
@@ -20184,12 +20336,12 @@ function registerRememberTool(server, memory) {
20184
20336
 
20185
20337
  // src/mcp/tools/revise.ts
20186
20338
  var reviseInputSchema = {
20187
- id: string2().describe("The id of the memory to update. Use the id returned by a previous recall result."),
20188
- content: string2().describe("The replacement content for the memory. Use a single self-contained sentence or short note. One fact per memory.")
20339
+ id: string2().describe("The memory id to update. Use an id returned by `recall`."),
20340
+ content: string2().describe("The corrected replacement for that same fact. Keep it to one durable fact.")
20189
20341
  };
20190
20342
  function registerReviseTool(server, memory) {
20191
20343
  server.registerTool("revise", {
20192
- 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.",
20344
+ 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="..." />`.',
20193
20345
  inputSchema: reviseInputSchema
20194
20346
  }, async ({ id, content }) => {
20195
20347
  try {
@@ -20210,14 +20362,15 @@ function registerReviseTool(server, memory) {
20210
20362
 
20211
20363
  // src/mcp/server.ts
20212
20364
  var SERVER_INSTRUCTIONS = [
20213
- "Stores decisions, corrections, and context that cannot be derived from code or git history.",
20214
- "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.",
20215
- "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.",
20216
- "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.",
20217
- "Use `revise` when a previously saved memory is outdated or inaccurate and needs correction rather than deletion.",
20218
- "Use `forget` to remove memories that are wrong, obsolete, or no longer relevant.",
20219
- "Always pass workspace (the current working directory) to scope results to the active project.",
20220
- "Omit workspace only when saving a memory that applies across all projects."
20365
+ "Use this server for durable memory: user preferences, corrections, decisions, and project context not obvious from code or git history.",
20366
+ "Use `recall` at conversation start and before design choices, conventions, or edge cases.",
20367
+ "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.",
20368
+ "`recall` is lexical-first; semantic reranking only reorders lexical matches.",
20369
+ "If `recall` misses, retry once with overlapping alternate terms.",
20370
+ "Use `remember` for one durable fact when the user states a preference, corrects you, or a reusable project decision becomes clear.",
20371
+ "Call `recall` before `remember`; if the fact already exists, use `revise` instead of creating a duplicate.",
20372
+ "Use `revise` to correct an existing memory and `forget` to remove a wrong or obsolete one.",
20373
+ "Pass workspace for project-scoped calls. Omit it only for truly global memories."
20221
20374
  ].join(" ");
20222
20375
  function createMcpServer(memory, version3) {
20223
20376
  const server = new McpServer({
@@ -20233,72 +20386,242 @@ function createMcpServer(memory, version3) {
20233
20386
  return server;
20234
20387
  }
20235
20388
 
20236
- // src/sqlite-db.ts
20237
- import { mkdirSync } from "node:fs";
20389
+ // src/sqlite/db.ts
20390
+ import { mkdirSync as mkdirSync2 } from "node:fs";
20238
20391
  import { dirname } from "node:path";
20239
20392
  import Database from "better-sqlite3";
20240
- var MEMORY_SCHEMA = `
20241
- CREATE TABLE IF NOT EXISTS memories (
20242
- id TEXT PRIMARY KEY,
20243
- content TEXT NOT NULL,
20244
- workspace TEXT,
20245
- created_at INTEGER NOT NULL,
20246
- updated_at INTEGER NOT NULL
20247
- );
20248
-
20249
- CREATE INDEX IF NOT EXISTS idx_memories_created_at ON memories(created_at);
20250
- CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace);
20251
-
20252
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
20253
- content,
20254
- content = 'memories',
20255
- content_rowid = 'rowid',
20256
- tokenize = 'porter unicode61'
20257
- );
20258
-
20259
- CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
20260
- INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
20261
- END;
20262
-
20263
- CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
20264
- INSERT INTO memories_fts(memories_fts, rowid, content)
20265
- VALUES ('delete', old.rowid, old.content);
20266
- END;
20267
-
20268
- CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
20269
- INSERT INTO memories_fts(memories_fts, rowid, content)
20270
- VALUES ('delete', old.rowid, old.content);
20271
- INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
20272
- END;
20273
- `;
20274
- function openMemoryDatabase(databasePath) {
20393
+
20394
+ // src/sqlite/memory-schema.ts
20395
+ function createMemoriesTable(database, options) {
20396
+ const embeddingColumn = getEmbeddingColumnSql(options.embeddingColumn);
20397
+ database.exec(`
20398
+ CREATE TABLE IF NOT EXISTS memories (
20399
+ id TEXT PRIMARY KEY,
20400
+ content TEXT NOT NULL,
20401
+ workspace TEXT,
20402
+ ${embeddingColumn}
20403
+ created_at INTEGER NOT NULL,
20404
+ updated_at INTEGER NOT NULL
20405
+ );
20406
+ `);
20407
+ }
20408
+ function createMemoryIndexes(database) {
20409
+ database.exec(`
20410
+ CREATE INDEX IF NOT EXISTS idx_memories_created_at ON memories(created_at);
20411
+ CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace);
20412
+ `);
20413
+ }
20414
+ function createMemorySearchArtifacts(database, rebuild = false) {
20415
+ database.exec(`
20416
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
20417
+ content,
20418
+ content = 'memories',
20419
+ content_rowid = 'rowid',
20420
+ tokenize = 'porter unicode61'
20421
+ );
20422
+
20423
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
20424
+ INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
20425
+ END;
20426
+
20427
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
20428
+ INSERT INTO memories_fts(memories_fts, rowid, content)
20429
+ VALUES ('delete', old.rowid, old.content);
20430
+ END;
20431
+
20432
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
20433
+ INSERT INTO memories_fts(memories_fts, rowid, content)
20434
+ VALUES ('delete', old.rowid, old.content);
20435
+ INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
20436
+ END;
20437
+ `);
20438
+ if (rebuild) {
20439
+ database.exec("INSERT INTO memories_fts(memories_fts) VALUES ('rebuild')");
20440
+ }
20441
+ }
20442
+ function dropMemorySearchArtifacts(database) {
20443
+ database.exec(`
20444
+ DROP TRIGGER IF EXISTS memories_ai;
20445
+ DROP TRIGGER IF EXISTS memories_ad;
20446
+ DROP TRIGGER IF EXISTS memories_au;
20447
+ DROP TABLE IF EXISTS memories_fts;
20448
+ `);
20449
+ }
20450
+ function getEmbeddingColumnSql(mode) {
20451
+ switch (mode) {
20452
+ case "omit":
20453
+ return "";
20454
+ case "nullable":
20455
+ return `embedding BLOB,
20456
+ `;
20457
+ case "required":
20458
+ return `embedding BLOB NOT NULL,
20459
+ `;
20460
+ }
20461
+ }
20462
+
20463
+ // src/sqlite/migrations/001-create-memory-schema.ts
20464
+ var createMemorySchemaMigration = {
20465
+ version: 1,
20466
+ async up(database) {
20467
+ createMemoriesTable(database, { embeddingColumn: "omit" });
20468
+ createMemoryIndexes(database);
20469
+ createMemorySearchArtifacts(database);
20470
+ }
20471
+ };
20472
+
20473
+ // src/sqlite/embedding-codec.ts
20474
+ var FLOAT32_BYTE_WIDTH = 4;
20475
+ function encodeEmbedding(vector) {
20476
+ const typedArray = Float32Array.from(vector);
20477
+ return new Uint8Array(typedArray.buffer.slice(0));
20478
+ }
20479
+ function decodeEmbedding(value) {
20480
+ const bytes = toUint8Array(value);
20481
+ if (bytes.byteLength === 0) {
20482
+ throw new Error("Embedding blob is empty.");
20483
+ }
20484
+ if (bytes.byteLength % FLOAT32_BYTE_WIDTH !== 0) {
20485
+ throw new Error("Embedding blob length is not a multiple of 4.");
20486
+ }
20487
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
20488
+ const vector = [];
20489
+ for (let offset = 0;offset < bytes.byteLength; offset += FLOAT32_BYTE_WIDTH) {
20490
+ vector.push(view.getFloat32(offset, true));
20491
+ }
20492
+ return vector;
20493
+ }
20494
+ function toUint8Array(value) {
20495
+ if (value instanceof Uint8Array) {
20496
+ return value;
20497
+ }
20498
+ if (value instanceof ArrayBuffer) {
20499
+ return new Uint8Array(value);
20500
+ }
20501
+ throw new Error("Expected embedding blob as Uint8Array or ArrayBuffer.");
20502
+ }
20503
+
20504
+ // src/sqlite/migrations/002-add-memory-embedding.ts
20505
+ function createAddMemoryEmbeddingMigration(embeddingService = new EmbeddingService) {
20506
+ return {
20507
+ version: 2,
20508
+ async up(database) {
20509
+ database.exec("ALTER TABLE memories ADD COLUMN embedding BLOB");
20510
+ const rows = database.prepare("SELECT id, content FROM memories ORDER BY created_at ASC").all();
20511
+ const updateStatement = database.prepare("UPDATE memories SET embedding = ? WHERE id = ?");
20512
+ for (const row of rows) {
20513
+ const embedding = await embeddingService.createVector(row.content);
20514
+ updateStatement.run(encodeEmbedding(embedding), row.id);
20515
+ }
20516
+ const nullRows = database.prepare("SELECT COUNT(*) AS count FROM memories WHERE embedding IS NULL").all();
20517
+ if ((nullRows[0]?.count ?? 0) > 0) {
20518
+ throw new Error("Failed to backfill embeddings for all memories.");
20519
+ }
20520
+ dropMemorySearchArtifacts(database);
20521
+ database.exec("ALTER TABLE memories RENAME TO memories_old");
20522
+ createMemoriesTable(database, { embeddingColumn: "required" });
20523
+ database.exec(`
20524
+ INSERT INTO memories (id, content, workspace, embedding, created_at, updated_at)
20525
+ SELECT id, content, workspace, embedding, created_at, updated_at
20526
+ FROM memories_old
20527
+ `);
20528
+ database.exec("DROP TABLE memories_old");
20529
+ createMemoryIndexes(database);
20530
+ createMemorySearchArtifacts(database, true);
20531
+ }
20532
+ };
20533
+ }
20534
+ var addMemoryEmbeddingMigration = createAddMemoryEmbeddingMigration();
20535
+
20536
+ // src/sqlite/migrations/index.ts
20537
+ function createMemoryMigrations(embeddingService = new EmbeddingService) {
20538
+ return [createMemorySchemaMigration, createAddMemoryEmbeddingMigration(embeddingService)];
20539
+ }
20540
+ var MEMORY_MIGRATIONS = createMemoryMigrations();
20541
+
20542
+ // src/sqlite/db.ts
20543
+ var PRAGMA_STATEMENTS = [
20544
+ "journal_mode = WAL",
20545
+ "synchronous = NORMAL",
20546
+ "foreign_keys = ON",
20547
+ "busy_timeout = 5000"
20548
+ ];
20549
+ async function openMemoryDatabase(databasePath, options = {}) {
20550
+ let database;
20275
20551
  try {
20276
- mkdirSync(dirname(databasePath), { recursive: true });
20277
- const database = new Database(databasePath);
20278
- initializeMemoryDatabase(database);
20552
+ mkdirSync2(dirname(databasePath), { recursive: true });
20553
+ database = new Database(databasePath);
20554
+ await initializeMemoryDatabase(database, createMemoryMigrations(options.embeddingService));
20279
20555
  return database;
20556
+ } catch (error2) {
20557
+ database?.close();
20558
+ if (error2 instanceof PersistenceError) {
20559
+ throw error2;
20560
+ }
20561
+ throw new PersistenceError("Failed to initialize the SQLite database.", {
20562
+ cause: error2
20563
+ });
20564
+ }
20565
+ }
20566
+ async function initializeMemoryDatabase(database, migrations = MEMORY_MIGRATIONS) {
20567
+ try {
20568
+ applyPragmas(database);
20569
+ await runSqliteMigrations(database, migrations);
20280
20570
  } catch (error2) {
20281
20571
  throw new PersistenceError("Failed to initialize the SQLite database.", {
20282
20572
  cause: error2
20283
20573
  });
20284
20574
  }
20285
20575
  }
20286
- function initializeMemoryDatabase(database) {
20576
+ async function runSqliteMigrations(database, migrations) {
20577
+ validateMigrations(migrations);
20578
+ let currentVersion = getUserVersion(database);
20579
+ for (const migration of migrations) {
20580
+ if (migration.version <= currentVersion) {
20581
+ continue;
20582
+ }
20583
+ database.exec("BEGIN");
20584
+ try {
20585
+ await migration.up(database);
20586
+ setUserVersion(database, migration.version);
20587
+ database.exec("COMMIT");
20588
+ currentVersion = migration.version;
20589
+ } catch (error2) {
20590
+ try {
20591
+ database.exec("ROLLBACK");
20592
+ } catch {}
20593
+ throw error2;
20594
+ }
20595
+ }
20596
+ }
20597
+ function applyPragmas(database) {
20287
20598
  if (database.pragma) {
20288
- database.pragma("journal_mode = WAL");
20289
- database.pragma("synchronous = NORMAL");
20290
- database.pragma("foreign_keys = ON");
20291
- database.pragma("busy_timeout = 5000");
20292
- } else {
20293
- database.exec("PRAGMA journal_mode = WAL");
20294
- database.exec("PRAGMA synchronous = NORMAL");
20295
- database.exec("PRAGMA foreign_keys = ON");
20296
- database.exec("PRAGMA busy_timeout = 5000");
20599
+ for (const statement of PRAGMA_STATEMENTS) {
20600
+ database.pragma(statement);
20601
+ }
20602
+ return;
20603
+ }
20604
+ for (const statement of PRAGMA_STATEMENTS) {
20605
+ database.exec(`PRAGMA ${statement}`);
20297
20606
  }
20298
- database.exec(MEMORY_SCHEMA);
20299
20607
  }
20300
-
20301
- // src/sqlite-memory-repository.ts
20608
+ function getUserVersion(database) {
20609
+ const rows = database.prepare("PRAGMA user_version").all();
20610
+ return rows[0]?.user_version ?? 0;
20611
+ }
20612
+ function setUserVersion(database, version3) {
20613
+ database.exec(`PRAGMA user_version = ${version3}`);
20614
+ }
20615
+ function validateMigrations(migrations) {
20616
+ let previousVersion = 0;
20617
+ for (const migration of migrations) {
20618
+ if (!Number.isInteger(migration.version) || migration.version <= previousVersion) {
20619
+ throw new Error("SQLite migrations must use strictly increasing versions.");
20620
+ }
20621
+ previousVersion = migration.version;
20622
+ }
20623
+ }
20624
+ // src/sqlite/repository.ts
20302
20625
  import { randomUUID } from "node:crypto";
20303
20626
  var DEFAULT_SEARCH_LIMIT = 15;
20304
20627
  var DEFAULT_LIST_LIMIT2 = 15;
@@ -20317,6 +20640,7 @@ class SqliteMemoryRepository {
20317
20640
  id,
20318
20641
  content,
20319
20642
  workspace,
20643
+ embedding,
20320
20644
  created_at,
20321
20645
  updated_at
20322
20646
  ) VALUES (
@@ -20324,11 +20648,12 @@ class SqliteMemoryRepository {
20324
20648
  ?,
20325
20649
  ?,
20326
20650
  ?,
20651
+ ?,
20327
20652
  ?
20328
20653
  )
20329
20654
  `);
20330
- this.getStatement = database.prepare("SELECT id, content, workspace, created_at, updated_at FROM memories WHERE id = ?");
20331
- this.updateStatement = database.prepare("UPDATE memories SET content = ?, updated_at = ? WHERE id = ?");
20655
+ this.getStatement = database.prepare("SELECT id, content, workspace, embedding, created_at, updated_at FROM memories WHERE id = ?");
20656
+ this.updateStatement = database.prepare("UPDATE memories SET content = ?, embedding = ?, updated_at = ? WHERE id = ?");
20332
20657
  this.deleteStatement = database.prepare("DELETE FROM memories WHERE id = ?");
20333
20658
  this.listWorkspacesStatement = database.prepare("SELECT DISTINCT workspace FROM memories WHERE workspace IS NOT NULL ORDER BY workspace");
20334
20659
  }
@@ -20338,11 +20663,12 @@ class SqliteMemoryRepository {
20338
20663
  const memory = {
20339
20664
  id: randomUUID(),
20340
20665
  content: input.content,
20666
+ embedding: input.embedding,
20341
20667
  workspace: input.workspace,
20342
20668
  createdAt: now,
20343
20669
  updatedAt: now
20344
20670
  };
20345
- this.insertStatement.run(memory.id, memory.content, memory.workspace, memory.createdAt.getTime(), memory.updatedAt.getTime());
20671
+ this.insertStatement.run(memory.id, memory.content, memory.workspace, encodeEmbedding(memory.embedding), memory.createdAt.getTime(), memory.updatedAt.getTime());
20346
20672
  return memory;
20347
20673
  } catch (error2) {
20348
20674
  throw new PersistenceError("Failed to save memory.", { cause: error2 });
@@ -20367,6 +20693,7 @@ class SqliteMemoryRepository {
20367
20693
  m.id,
20368
20694
  m.content,
20369
20695
  m.workspace,
20696
+ m.embedding,
20370
20697
  m.created_at,
20371
20698
  m.updated_at,
20372
20699
  MAX(0, -bm25(memories_fts)) AS score
@@ -20379,7 +20706,7 @@ class SqliteMemoryRepository {
20379
20706
  const rows = statement.all(...params);
20380
20707
  const maxScore = Math.max(...rows.map((row) => row.score), 0);
20381
20708
  return rows.map((row) => ({
20382
- ...toMemoryRecord(row),
20709
+ ...toMemoryEntity(row),
20383
20710
  score: toNormalizedScore(maxScore > 0 ? row.score / maxScore : 0)
20384
20711
  }));
20385
20712
  } catch (error2) {
@@ -20392,7 +20719,7 @@ class SqliteMemoryRepository {
20392
20719
  try {
20393
20720
  const rows = this.getStatement.all(id);
20394
20721
  const row = rows[0];
20395
- return row ? toMemoryRecord(row) : undefined;
20722
+ return row ? toMemoryEntity(row) : undefined;
20396
20723
  } catch (error2) {
20397
20724
  throw new PersistenceError("Failed to find memory.", { cause: error2 });
20398
20725
  }
@@ -20413,7 +20740,7 @@ class SqliteMemoryRepository {
20413
20740
  const queryLimit = limit + 1;
20414
20741
  params.push(queryLimit, offset);
20415
20742
  const statement = this.database.prepare(`
20416
- SELECT id, content, workspace, created_at, updated_at
20743
+ SELECT id, content, workspace, embedding, created_at, updated_at
20417
20744
  FROM memories
20418
20745
  ${whereClause}
20419
20746
  ORDER BY created_at DESC
@@ -20421,7 +20748,7 @@ class SqliteMemoryRepository {
20421
20748
  `);
20422
20749
  const rows = statement.all(...params);
20423
20750
  const hasMore = rows.length > limit;
20424
- const items = (hasMore ? rows.slice(0, limit) : rows).map(toMemoryRecord);
20751
+ const items = (hasMore ? rows.slice(0, limit) : rows).map(toMemoryEntity);
20425
20752
  return { items, hasMore };
20426
20753
  } catch (error2) {
20427
20754
  throw new PersistenceError("Failed to list memories.", { cause: error2 });
@@ -20431,7 +20758,7 @@ class SqliteMemoryRepository {
20431
20758
  let result;
20432
20759
  try {
20433
20760
  const now = Date.now();
20434
- result = this.updateStatement.run(input.content, now, input.id);
20761
+ result = this.updateStatement.run(input.content, encodeEmbedding(input.embedding), now, input.id);
20435
20762
  } catch (error2) {
20436
20763
  throw new PersistenceError("Failed to update memory.", { cause: error2 });
20437
20764
  }
@@ -20464,9 +20791,10 @@ class SqliteMemoryRepository {
20464
20791
  }
20465
20792
  }
20466
20793
  }
20467
- var toMemoryRecord = (row) => ({
20794
+ var toMemoryEntity = (row) => ({
20468
20795
  id: row.id,
20469
20796
  content: row.content,
20797
+ embedding: decodeEmbedding(row.embedding),
20470
20798
  workspace: row.workspace ?? undefined,
20471
20799
  createdAt: new Date(row.created_at),
20472
20800
  updatedAt: new Date(row.updated_at)
@@ -20479,7 +20807,6 @@ function toFtsTerm(term) {
20479
20807
  }
20480
20808
  return `"${escaped}"*`;
20481
20809
  }
20482
-
20483
20810
  // node_modules/@hono/node-server/dist/index.mjs
20484
20811
  import { createServer as createServerHTTP } from "http";
20485
20812
  import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
@@ -23870,9 +24197,10 @@ function startWebServer(memory, options) {
23870
24197
 
23871
24198
  // src/index.ts
23872
24199
  var config2 = resolveConfig();
23873
- var database = openMemoryDatabase(config2.databasePath);
23874
- var repository = new SqliteMemoryRepository(database);
23875
- var memoryService = new MemoryService(repository);
24200
+ var embeddingService = new EmbeddingService({ modelsCachePath: config2.modelsCachePath });
24201
+ var database = await openMemoryDatabase(config2.databasePath, { embeddingService });
24202
+ var repository2 = new SqliteMemoryRepository(database);
24203
+ var memoryService = new MemoryService(repository2, embeddingService);
23876
24204
  if (config2.uiMode) {
23877
24205
  let shutdown = function() {
23878
24206
  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.13",
4
+ "version": "0.0.15",
5
5
  "bin": {
6
6
  "agent-memory": "dist/index.js"
7
7
  },
@@ -20,7 +20,7 @@
20
20
  "url": "git+https://github.com/jcyamacho/agent-memory.git"
21
21
  },
22
22
  "scripts": {
23
- "build": "bun build src/index.ts --target=node --external better-sqlite3 --outfile dist/index.js --banner \"#!/usr/bin/env node\"",
23
+ "build": "bun build src/index.ts --target=node --external better-sqlite3 --external @huggingface/transformers --outfile dist/index.js --banner \"#!/usr/bin/env node\"",
24
24
  "start": "node dist/index.js",
25
25
  "test": "bun test",
26
26
  "lint": "biome check --write && tsc --noEmit",
@@ -38,6 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@hono/node-server": "^1.19.11",
41
+ "@huggingface/transformers": "^3.8.1",
41
42
  "@modelcontextprotocol/sdk": "^1.27.1",
42
43
  "better-sqlite3": "^12.6.2",
43
44
  "hono": "^4.12.8",