@jcyamacho/agent-memory 0.0.20 → 0.1.0

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 +18 -62
  2. package/dist/index.js +187 -555
  3. package/package.json +2 -3
package/README.md CHANGED
@@ -6,11 +6,10 @@ Persistent memory for MCP-powered coding agents.
6
6
  by SQLite. It helps your agent remember preferences, project context, and prior
7
7
  decisions across sessions.
8
8
 
9
- It exposes five tools:
9
+ It exposes four tools:
10
10
 
11
11
  - `remember` -> save facts, decisions, preferences, and project context
12
- - `recall` -> retrieve the most relevant memories later
13
- - `review` -> browse all memories for a workspace
12
+ - `review` -> load workspace and global memories sorted by most recently updated
14
13
  - `revise` -> update an existing memory when it becomes outdated
15
14
  - `forget` -> delete a memory that is no longer relevant
16
15
 
@@ -44,24 +43,20 @@ OpenCode:
44
43
 
45
44
  ## Optional LLM Instructions
46
45
 
47
- Optional LLM instructions to reinforce the MCP's built-in guidance:
46
+ Optional LLM instructions to reinforce the MCP's built-in guidance. The server
47
+ instructions and tool descriptions already cover most behavior -- this prompt
48
+ targets the habits models most commonly miss:
48
49
 
49
50
  ```md
50
51
  ## Agent Memory
51
52
 
52
- - Use `memory_recall` at conversation start and before design choices,
53
- conventions, edge cases, or saving memory.
54
- - Query `memory_recall` with 2-5 short anchor-heavy terms or exact phrases,
55
- not full questions or sentences.
56
- - Use `memory_review` to browse all memories for a workspace before bulk review
57
- or cleanup.
58
- - Pass `workspace` for project-scoped memory. Omit it only for facts that
59
- apply across projects.
60
- - Use `memory_remember` to save one durable fact when the user states a stable
61
- preference, correction, or reusable project decision.
62
- - If the fact already exists, use `memory_revise` instead of creating a duplicate.
63
- - Use `memory_forget` to remove a wrong or obsolete memory.
64
- - Do not store secrets or temporary task state in memory.
53
+ - Use `memory_review` at conversation start to load workspace memories into
54
+ context. During the session, use `memory_remember`, `memory_revise`, and
55
+ `memory_forget` to keep memories accurate.
56
+ - Pass `workspace` on `memory_remember` for project-scoped memory. Omit it
57
+ only for facts that apply across projects.
58
+ - Do not store secrets, temporary task state, or facts obvious from current
59
+ code or git history.
65
60
  ```
66
61
 
67
62
  ## Web UI
@@ -80,34 +75,15 @@ npx -y @jcyamacho/agent-memory@latest --ui --port 9090
80
75
 
81
76
  The web UI uses the same database as the MCP server.
82
77
 
83
- ## How Recall Finds Memories
84
-
85
- `recall` uses a multi-signal ranking system to surface the most relevant
86
- memories:
87
-
88
- 1. **Text relevance** is the primary signal -- memories whose content best
89
- matches your search terms rank highest.
90
- 2. **Workspace match** is the next strongest signal. When you pass
91
- `workspace`, exact matches rank highest and all other scoped workspaces rank
92
- below exact matches.
93
- 3. **Embedding similarity** is a secondary signal. Recall builds an embedding
94
- from your normalized search terms and boosts memories whose stored
95
- embeddings are most semantically similar.
96
- 4. **Global memories** (saved without a workspace) are treated as relevant
97
- everywhere. When you pass `workspace`, they rank below exact workspace
98
- matches and above memories from other workspaces.
99
- 5. **Recency** is a minor tiebreaker -- newer memories rank slightly above older
100
- ones when other signals are equal.
101
-
102
- If you omit `workspace`, recall still uses text relevance, embedding similarity,
103
- and recency. For best results, pass `workspace` whenever you have one. Save
104
- memories without a workspace only when they apply across all projects.
78
+ ## How Review Works
79
+
80
+ `review` requires a `workspace` and returns memories saved in that workspace
81
+ plus global memories (saved without a workspace), sorted by most recently
82
+ updated. Results are paginated -- pass `page` to load older memories.
105
83
 
106
84
  When you save a memory from a git worktree, `agent-memory` stores the main repo
107
- root as the workspace. `recall` applies the same normalization to incoming
85
+ root as the workspace. `review` applies the same normalization to incoming
108
86
  workspace queries so linked worktrees still match repo-scoped memories exactly.
109
- When that happens, recall returns the queried workspace value so callers can
110
- treat the match as belonging to their current worktree context.
111
87
 
112
88
  ## Configuration
113
89
 
@@ -131,26 +107,6 @@ Set `AGENT_MEMORY_DB_PATH` when you want to:
131
107
  - share a memory DB across multiple clients
132
108
  - store the DB somewhere easier to back up or inspect
133
109
 
134
- ### Model Cache Location
135
-
136
- By default, downloaded embedding model files are cached at:
137
-
138
- ```text
139
- ~/.config/agent-memory/models
140
- ```
141
-
142
- Override it with:
143
-
144
- ```bash
145
- AGENT_MEMORY_MODELS_CACHE_PATH=/absolute/path/to/models
146
- ```
147
-
148
- Set `AGENT_MEMORY_MODELS_CACHE_PATH` when you want to:
149
-
150
- - keep model artifacts out of `node_modules`
151
- - share the model cache across reinstalls or multiple clients
152
- - store model downloads somewhere easier to inspect or manage
153
-
154
110
  Schema changes are migrated automatically, including workspace normalization for
155
111
  existing git worktree memories when the original path can still be resolved.
156
112
 
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: ValidationError2, opts } = it;
2434
+ const { gen, schemaEnv, validateName, ValidationError, 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 ${ValidationError2}(${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 ${ValidationError}(${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 ValidationError2 extends Error {
2786
+ class ValidationError 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 = ValidationError2;
2793
+ exports.default = ValidationError;
2794
2794
  });
2795
2795
 
2796
2796
  // node_modules/ajv/dist/compile/ref_error.js
@@ -12464,14 +12464,13 @@ class StdioServerTransport {
12464
12464
  }
12465
12465
  }
12466
12466
  // package.json
12467
- var version2 = "0.0.20";
12467
+ var version2 = "0.1.0";
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";
12475
12474
  var DEFAULT_UI_PORT = 6580;
12476
12475
  function resolveConfig(environment = process.env, argv = process.argv.slice(2)) {
12477
12476
  const { values } = parseArgs({
@@ -12484,7 +12483,6 @@ function resolveConfig(environment = process.env, argv = process.argv.slice(2))
12484
12483
  });
12485
12484
  return {
12486
12485
  databasePath: resolveDatabasePath(environment),
12487
- modelsCachePath: resolveModelsCachePath(environment),
12488
12486
  uiMode: Boolean(values.ui),
12489
12487
  uiPort: Number(values.port) || DEFAULT_UI_PORT
12490
12488
  };
@@ -12492,137 +12490,7 @@ function resolveConfig(environment = process.env, argv = process.argv.slice(2))
12492
12490
  function resolveDatabasePath(environment = process.env) {
12493
12491
  return environment[AGENT_MEMORY_DB_PATH_ENV] || join(homedir(), ".config", "agent-memory", "memory.db");
12494
12492
  }
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
12493
 
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
- }
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
- function configureModelsCache(modelsCachePath) {
12537
- mkdirSync(modelsCachePath, { recursive: true });
12538
- transformersEnv.useFSCache = true;
12539
- transformersEnv.cacheDir = modelsCachePath;
12540
- }
12541
-
12542
- class EmbeddingService {
12543
- options;
12544
- extractorPromise;
12545
- constructor(options = {}) {
12546
- this.options = options;
12547
- }
12548
- async warmup() {
12549
- await this.getExtractor();
12550
- }
12551
- async createVector(text) {
12552
- const normalizedText = text.trim();
12553
- if (!normalizedText) {
12554
- throw new ValidationError("Text is required.");
12555
- }
12556
- const extractor = await this.getExtractor();
12557
- const embedding = await extractor(normalizedText);
12558
- return normalizeVector(embedding.tolist());
12559
- }
12560
- getExtractor() {
12561
- if (!this.extractorPromise) {
12562
- this.extractorPromise = (this.options.createExtractor ?? createDefaultExtractor)();
12563
- }
12564
- return this.extractorPromise;
12565
- }
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
- }
12626
12494
  // node_modules/zod/v3/helpers/util.js
12627
12495
  var util;
12628
12496
  (function(util2) {
@@ -20030,6 +19898,37 @@ var EMPTY_COMPLETION_RESULT = {
20030
19898
  }
20031
19899
  };
20032
19900
 
19901
+ // src/errors.ts
19902
+ class MemoryError extends Error {
19903
+ code;
19904
+ constructor(code, message, options) {
19905
+ super(message, options);
19906
+ this.name = "MemoryError";
19907
+ this.code = code;
19908
+ }
19909
+ }
19910
+
19911
+ class ValidationError extends MemoryError {
19912
+ constructor(message) {
19913
+ super("VALIDATION_ERROR", message);
19914
+ this.name = "ValidationError";
19915
+ }
19916
+ }
19917
+
19918
+ class NotFoundError extends MemoryError {
19919
+ constructor(message) {
19920
+ super("NOT_FOUND", message);
19921
+ this.name = "NotFoundError";
19922
+ }
19923
+ }
19924
+
19925
+ class PersistenceError extends MemoryError {
19926
+ constructor(message, options) {
19927
+ super("PERSISTENCE_ERROR", message, options);
19928
+ this.name = "PersistenceError";
19929
+ }
19930
+ }
19931
+
20033
19932
  // src/mcp/tools/shared.ts
20034
19933
  function toMcpError(error2) {
20035
19934
  if (error2 instanceof McpError) {
@@ -20045,20 +19944,10 @@ function toMcpError(error2) {
20045
19944
  return new McpError(ErrorCode.InternalError, message);
20046
19945
  }
20047
19946
  var escapeXml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
20048
- function parseOptionalDate(value, fieldName) {
20049
- if (!value) {
20050
- return;
20051
- }
20052
- const date4 = new Date(value);
20053
- if (Number.isNaN(date4.getTime())) {
20054
- throw new MemoryError("VALIDATION_ERROR", `${fieldName} must be a valid ISO 8601 datetime.`);
20055
- }
20056
- return date4;
20057
- }
20058
19947
 
20059
19948
  // src/mcp/tools/forget.ts
20060
19949
  var forgetInputSchema = {
20061
- id: string2().describe("Memory id to delete. Use an id returned by `recall`.")
19950
+ id: string2().describe("Memory id to delete. Use an id returned by `review`.")
20062
19951
  };
20063
19952
  function registerForgetTool(server, memory) {
20064
19953
  server.registerTool("forget", {
@@ -20068,7 +19957,7 @@ function registerForgetTool(server, memory) {
20068
19957
  idempotentHint: true,
20069
19958
  openWorldHint: false
20070
19959
  },
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" />`.',
19960
+ description: 'Delete a memory that is wrong or obsolete. Use after `review` when you have the memory id. Use `revise` instead if the fact should remain with corrected wording. Returns `<memory id="..." deleted="true" />`.',
20072
19961
  inputSchema: forgetInputSchema
20073
19962
  }, async ({ id }) => {
20074
19963
  try {
@@ -20082,245 +19971,6 @@ function registerForgetTool(server, memory) {
20082
19971
  });
20083
19972
  }
20084
19973
 
20085
- // src/memory.ts
20086
- var toNormalizedScore = (value) => value;
20087
-
20088
- // src/ranking.ts
20089
- var RETRIEVAL_SCORE_WEIGHT = 9;
20090
- var EMBEDDING_SIMILARITY_WEIGHT = 4;
20091
- var WORKSPACE_MATCH_WEIGHT = 5;
20092
- var RECENCY_WEIGHT = 1;
20093
- var MAX_COMPOSITE_SCORE = RETRIEVAL_SCORE_WEIGHT + EMBEDDING_SIMILARITY_WEIGHT + WORKSPACE_MATCH_WEIGHT + RECENCY_WEIGHT;
20094
- var GLOBAL_WORKSPACE_SCORE = 0.5;
20095
- function rerankSearchResults(results, workspace, queryEmbedding) {
20096
- if (results.length === 0) {
20097
- return results;
20098
- }
20099
- const normalizedQueryWs = workspace ? normalizeWorkspacePath(workspace) : undefined;
20100
- const updatedAtTimes = results.map((result) => result.updatedAt.getTime());
20101
- const minUpdatedAt = Math.min(...updatedAtTimes);
20102
- const maxUpdatedAt = Math.max(...updatedAtTimes);
20103
- return results.map((result) => {
20104
- const embeddingSimilarityScore = computeEmbeddingSimilarityScore(result, queryEmbedding);
20105
- const workspaceScore = computeWorkspaceScore(result.workspace, normalizedQueryWs);
20106
- const recencyScore = maxUpdatedAt === minUpdatedAt ? 0 : (result.updatedAt.getTime() - minUpdatedAt) / (maxUpdatedAt - minUpdatedAt);
20107
- const combinedScore = (result.score * RETRIEVAL_SCORE_WEIGHT + embeddingSimilarityScore * EMBEDDING_SIMILARITY_WEIGHT + workspaceScore * WORKSPACE_MATCH_WEIGHT + recencyScore * RECENCY_WEIGHT) / MAX_COMPOSITE_SCORE;
20108
- return {
20109
- ...result,
20110
- score: toNormalizedScore(combinedScore)
20111
- };
20112
- }).sort((a, b) => b.score - a.score);
20113
- }
20114
- function computeEmbeddingSimilarityScore(result, queryEmbedding) {
20115
- return normalizeCosineSimilarity(compareVectors(result.embedding, queryEmbedding));
20116
- }
20117
- function normalizeWorkspacePath(value) {
20118
- return value.trim().replaceAll("\\", "/").replace(/\/+/g, "/").split("/").filter(Boolean).join("/");
20119
- }
20120
- function normalizeCosineSimilarity(value) {
20121
- return (value + 1) / 2;
20122
- }
20123
- function computeWorkspaceScore(memoryWs, queryWs) {
20124
- if (!queryWs) {
20125
- return 0;
20126
- }
20127
- if (!memoryWs) {
20128
- return GLOBAL_WORKSPACE_SCORE;
20129
- }
20130
- const normalizedMemoryWs = normalizeWorkspacePath(memoryWs);
20131
- if (normalizedMemoryWs === queryWs) {
20132
- return 1;
20133
- }
20134
- return 0;
20135
- }
20136
-
20137
- // src/memory-service.ts
20138
- var DEFAULT_RECALL_LIMIT = 15;
20139
- var MAX_RECALL_LIMIT = 50;
20140
- var RECALL_CANDIDATE_LIMIT_MULTIPLIER = 3;
20141
- var DEFAULT_LIST_LIMIT = 15;
20142
- var MAX_LIST_LIMIT = 100;
20143
-
20144
- class MemoryService {
20145
- repository;
20146
- embeddingService;
20147
- workspaceResolver;
20148
- constructor(repository, embeddingService, workspaceResolver) {
20149
- this.repository = repository;
20150
- this.embeddingService = embeddingService;
20151
- this.workspaceResolver = workspaceResolver;
20152
- }
20153
- async create(input) {
20154
- const content = input.content.trim();
20155
- if (!content) {
20156
- throw new ValidationError("Memory content is required.");
20157
- }
20158
- const workspace = await this.workspaceResolver.resolve(input.workspace);
20159
- const memory = await this.repository.create({
20160
- content,
20161
- embedding: await this.embeddingService.createVector(content),
20162
- workspace
20163
- });
20164
- return toPublicMemoryRecord(memory);
20165
- }
20166
- async update(input) {
20167
- const content = input.content.trim();
20168
- if (!content)
20169
- throw new ValidationError("Memory content is required.");
20170
- const memory = await this.repository.update({
20171
- id: input.id,
20172
- content,
20173
- embedding: await this.embeddingService.createVector(content)
20174
- });
20175
- return toPublicMemoryRecord(memory);
20176
- }
20177
- async delete(input) {
20178
- const id = input.id.trim();
20179
- if (!id)
20180
- throw new ValidationError("Memory id is required.");
20181
- return this.repository.delete({ id });
20182
- }
20183
- async get(id) {
20184
- const memory = await this.repository.get(id);
20185
- return memory ? toPublicMemoryRecord(memory) : undefined;
20186
- }
20187
- async list(input) {
20188
- const queryWorkspace = normalizeOptionalString(input.workspace);
20189
- const workspace = await this.workspaceResolver.resolve(input.workspace);
20190
- const page = await this.repository.list({
20191
- workspace,
20192
- workspaceIsNull: workspace ? false : Boolean(input.workspaceIsNull),
20193
- offset: normalizeOffset(input.offset),
20194
- limit: normalizeListLimit(input.limit)
20195
- });
20196
- return {
20197
- items: page.items.map((item) => toPublicMemoryRecord(remapWorkspace(item, workspace, queryWorkspace))),
20198
- hasMore: page.hasMore
20199
- };
20200
- }
20201
- async listWorkspaces() {
20202
- return this.repository.listWorkspaces();
20203
- }
20204
- async search(input) {
20205
- const terms = normalizeTerms(input.terms);
20206
- if (terms.length === 0) {
20207
- throw new ValidationError("At least one search term is required.");
20208
- }
20209
- const requestedLimit = normalizeLimit(input.limit);
20210
- const queryWorkspace = normalizeOptionalString(input.workspace);
20211
- const workspace = await this.workspaceResolver.resolve(input.workspace);
20212
- const normalizedQuery = {
20213
- terms,
20214
- limit: requestedLimit * RECALL_CANDIDATE_LIMIT_MULTIPLIER,
20215
- updatedAfter: input.updatedAfter,
20216
- updatedBefore: input.updatedBefore
20217
- };
20218
- const [results, queryEmbedding] = await Promise.all([
20219
- this.repository.search(normalizedQuery),
20220
- this.embeddingService.createVector(terms.join(" "))
20221
- ]);
20222
- return rerankSearchResults(results, workspace, queryEmbedding).slice(0, requestedLimit).map((result) => toPublicSearchResult(remapWorkspace(result, workspace, queryWorkspace)));
20223
- }
20224
- }
20225
- function toPublicMemoryRecord(memory) {
20226
- return {
20227
- id: memory.id,
20228
- content: memory.content,
20229
- workspace: memory.workspace,
20230
- createdAt: memory.createdAt,
20231
- updatedAt: memory.updatedAt
20232
- };
20233
- }
20234
- function toPublicSearchResult(result) {
20235
- return {
20236
- id: result.id,
20237
- content: result.content,
20238
- score: result.score,
20239
- workspace: result.workspace,
20240
- createdAt: result.createdAt,
20241
- updatedAt: result.updatedAt
20242
- };
20243
- }
20244
- function normalizeLimit(value) {
20245
- if (value === undefined) {
20246
- return DEFAULT_RECALL_LIMIT;
20247
- }
20248
- if (!Number.isInteger(value) || value < 1 || value > MAX_RECALL_LIMIT) {
20249
- throw new ValidationError(`Limit must be an integer between 1 and ${MAX_RECALL_LIMIT}.`);
20250
- }
20251
- return value;
20252
- }
20253
- function normalizeOffset(value) {
20254
- return Number.isInteger(value) && value && value > 0 ? value : 0;
20255
- }
20256
- function normalizeListLimit(value) {
20257
- if (!Number.isInteger(value) || value === undefined) {
20258
- return DEFAULT_LIST_LIMIT;
20259
- }
20260
- return Math.min(Math.max(value, 1), MAX_LIST_LIMIT);
20261
- }
20262
- function normalizeTerms(terms) {
20263
- const normalizedTerms = terms.map((term) => term.trim()).filter(Boolean);
20264
- return [...new Set(normalizedTerms)];
20265
- }
20266
- function normalizeOptionalString(value) {
20267
- const trimmed = value?.trim();
20268
- return trimmed ? trimmed : undefined;
20269
- }
20270
- function remapWorkspace(record3, canonicalWorkspace, queryWorkspace) {
20271
- if (record3.workspace !== canonicalWorkspace) {
20272
- return record3;
20273
- }
20274
- return { ...record3, workspace: queryWorkspace };
20275
- }
20276
-
20277
- // src/mcp/tools/recall.ts
20278
- var recallInputSchema = {
20279
- 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."),
20280
- 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."),
20281
- workspace: string2().optional().describe("Current working directory for project-scoped recall. Omit for cross-project recall."),
20282
- updated_after: string2().optional().describe("Only return memories updated on or after this ISO 8601 timestamp."),
20283
- updated_before: string2().optional().describe("Only return memories updated on or before this ISO 8601 timestamp.")
20284
- };
20285
- function toMemoryXml(r) {
20286
- const workspace = r.workspace ? ` workspace="${escapeXml(r.workspace)}"` : "";
20287
- const content = escapeXml(r.content);
20288
- const score = Number(r.score.toFixed(3)).toString();
20289
- return `<memory id="${r.id}" score="${score}"${workspace} updated_at="${r.updatedAt.toISOString()}">
20290
- ${content}
20291
- </memory>`;
20292
- }
20293
- function registerRecallTool(server, memory) {
20294
- server.registerTool("recall", {
20295
- annotations: {
20296
- title: "Recall",
20297
- readOnlyHint: true,
20298
- openWorldHint: false
20299
- },
20300
- 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.",
20301
- inputSchema: recallInputSchema
20302
- }, async ({ terms, limit, workspace, updated_after, updated_before }) => {
20303
- try {
20304
- const results = await memory.search({
20305
- terms,
20306
- limit,
20307
- workspace,
20308
- updatedAfter: parseOptionalDate(updated_after, "updated_after"),
20309
- updatedBefore: parseOptionalDate(updated_before, "updated_before")
20310
- });
20311
- 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>
20312
- ${results.map(toMemoryXml).join(`
20313
- `)}
20314
- </memories>`;
20315
- return {
20316
- content: [{ type: "text", text }]
20317
- };
20318
- } catch (error2) {
20319
- throw toMcpError(error2);
20320
- }
20321
- });
20322
- }
20323
-
20324
19974
  // src/mcp/tools/remember.ts
20325
19975
  var rememberInputSchema = {
20326
19976
  content: string2().describe("One new durable fact to save. Use a self-contained sentence or short note."),
@@ -20334,7 +19984,7 @@ function registerRememberTool(server, memory) {
20334
19984
  idempotentHint: false,
20335
19985
  openWorldHint: false
20336
19986
  },
20337
- 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="..." />`.',
19987
+ description: 'Save one new durable fact. 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="..." />`.',
20338
19988
  inputSchema: rememberInputSchema
20339
19989
  }, async ({ content, workspace }) => {
20340
19990
  try {
@@ -20352,14 +20002,15 @@ function registerRememberTool(server, memory) {
20352
20002
  }
20353
20003
 
20354
20004
  // src/mcp/tools/review.ts
20355
- var REVIEW_PAGE_SIZE = 25;
20005
+ var REVIEW_PAGE_SIZE = 50;
20356
20006
  var reviewInputSchema = {
20357
20007
  workspace: string2().describe("Current working directory for project-scoped listing."),
20358
20008
  page: number2().int().min(0).optional().describe("Zero-based page number. Defaults to 0.")
20359
20009
  };
20360
- function toMemoryXml2(record3) {
20010
+ function toMemoryXml(record3, workspace) {
20011
+ const global2 = record3.workspace !== workspace ? ' global="true"' : "";
20361
20012
  const content = escapeXml(record3.content);
20362
- return `<memory id="${record3.id}" updated_at="${record3.updatedAt.toISOString()}">
20013
+ return `<memory id="${record3.id}"${global2} updated_at="${record3.updatedAt.toISOString()}">
20363
20014
  ${content}
20364
20015
  </memory>`;
20365
20016
  }
@@ -20370,13 +20021,14 @@ function registerReviewTool(server, memory) {
20370
20021
  readOnlyHint: true,
20371
20022
  openWorldHint: false
20372
20023
  },
20373
- description: 'Browse all memories for a workspace in creation order. Use before bulk review, cleanup, or when you need to scan memories without specific search terms. For targeted retrieval by topic, use `recall` instead. Returns `<memories workspace="..." has_more="true|false">...</memories>` with pagination support.',
20024
+ description: 'Load workspace and global memories sorted by most recently updated. Use at the start of a task and before saving or revising memory. Returns `<memories workspace="..." has_more="true|false">...</memories>` with pagination support. Global memories are marked with `global="true"`.',
20374
20025
  inputSchema: reviewInputSchema
20375
20026
  }, async ({ workspace, page }) => {
20376
20027
  try {
20377
20028
  const pageIndex = page ?? 0;
20378
20029
  const result = await memory.list({
20379
20030
  workspace,
20031
+ global: true,
20380
20032
  offset: pageIndex * REVIEW_PAGE_SIZE,
20381
20033
  limit: REVIEW_PAGE_SIZE
20382
20034
  });
@@ -20385,9 +20037,11 @@ function registerReviewTool(server, memory) {
20385
20037
  content: [{ type: "text", text: "No memories found for this workspace." }]
20386
20038
  };
20387
20039
  }
20388
- const text = `<memories workspace="${escapeXml(workspace)}" has_more="${result.hasMore}">
20389
- ${result.items.map(toMemoryXml2).join(`
20390
- `)}
20040
+ const escapedWorkspace = escapeXml(workspace);
20041
+ const memories = result.items.map((item) => toMemoryXml(item, workspace)).join(`
20042
+ `);
20043
+ const text = `<memories workspace="${escapedWorkspace}" has_more="${result.hasMore}">
20044
+ ${memories}
20391
20045
  </memories>`;
20392
20046
  return {
20393
20047
  content: [{ type: "text", text }]
@@ -20400,7 +20054,7 @@ ${result.items.map(toMemoryXml2).join(`
20400
20054
 
20401
20055
  // src/mcp/tools/revise.ts
20402
20056
  var reviseInputSchema = {
20403
- id: string2().describe("Memory id to update. Use an id returned by `recall`."),
20057
+ id: string2().describe("Memory id to update. Use an id returned by `review`."),
20404
20058
  content: string2().describe("Corrected replacement text for that memory.")
20405
20059
  };
20406
20060
  function registerReviseTool(server, memory) {
@@ -20411,7 +20065,7 @@ function registerReviseTool(server, memory) {
20411
20065
  idempotentHint: false,
20412
20066
  openWorldHint: false
20413
20067
  },
20414
- 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="..." />`.',
20068
+ description: 'Update one existing memory when the same fact still applies but its wording or details changed. Use after `review` when you already have the memory id. Returns `<memory id="..." updated_at="..." />`.',
20415
20069
  inputSchema: reviseInputSchema
20416
20070
  }, async ({ id, content }) => {
20417
20071
  try {
@@ -20432,13 +20086,12 @@ function registerReviseTool(server, memory) {
20432
20086
 
20433
20087
  // src/mcp/server.ts
20434
20088
  var SERVER_INSTRUCTIONS = [
20435
- "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.",
20436
- "Use `recall` at conversation start, before design choices, and before saving or revising memory.",
20437
- "Use `review` to browse all memories for a workspace before bulk review or cleanup.",
20438
- "Use `remember` for one new durable fact. Use `revise` when the fact already exists but needs correction.",
20439
- "Use `forget` only when a memory is wrong or obsolete.",
20440
- "Pass workspace for project-scoped memory. Omit it only for facts that apply across projects.",
20441
- "Do not store secrets, temporary task state, or facts obvious from current code or git history."
20089
+ "Durable memory for stable preferences, corrections, reusable decisions, and project context not obvious from code or git history.",
20090
+ "Workflow: (1) Call `review` with the current workspace at conversation start -- this loads workspace and global memories into context.",
20091
+ "(2) During the session, call `remember` to save a new fact, `revise` to correct an existing one, or `forget` to remove one that is wrong or obsolete.",
20092
+ "Always check loaded memories before calling `remember` to avoid duplicates -- use `revise` instead when the fact already exists.",
20093
+ "Pass workspace on `remember` for project-scoped memory. Omit it only for facts that apply across all projects.",
20094
+ "Never store secrets, temporary task state, or facts obvious from current code or git history."
20442
20095
  ].join(" ");
20443
20096
  function createMcpServer(memory, version3) {
20444
20097
  const server = new McpServer({
@@ -20448,27 +20101,96 @@ function createMcpServer(memory, version3) {
20448
20101
  instructions: SERVER_INSTRUCTIONS
20449
20102
  });
20450
20103
  registerRememberTool(server, memory);
20451
- registerRecallTool(server, memory);
20452
20104
  registerReviseTool(server, memory);
20453
20105
  registerForgetTool(server, memory);
20454
20106
  registerReviewTool(server, memory);
20455
20107
  return server;
20456
20108
  }
20457
20109
 
20110
+ // src/memory-service.ts
20111
+ var DEFAULT_LIST_LIMIT = 15;
20112
+ var MAX_LIST_LIMIT = 100;
20113
+
20114
+ class MemoryService {
20115
+ repository;
20116
+ workspaceResolver;
20117
+ constructor(repository, workspaceResolver) {
20118
+ this.repository = repository;
20119
+ this.workspaceResolver = workspaceResolver;
20120
+ }
20121
+ async create(input) {
20122
+ const content = input.content.trim();
20123
+ if (!content) {
20124
+ throw new ValidationError("Memory content is required.");
20125
+ }
20126
+ const workspace = await this.workspaceResolver.resolve(input.workspace);
20127
+ return this.repository.create({ content, workspace });
20128
+ }
20129
+ async update(input) {
20130
+ const content = input.content.trim();
20131
+ if (!content)
20132
+ throw new ValidationError("Memory content is required.");
20133
+ return this.repository.update({ id: input.id, content });
20134
+ }
20135
+ async delete(input) {
20136
+ const id = input.id.trim();
20137
+ if (!id)
20138
+ throw new ValidationError("Memory id is required.");
20139
+ return this.repository.delete({ id });
20140
+ }
20141
+ async get(id) {
20142
+ return this.repository.get(id);
20143
+ }
20144
+ async list(input) {
20145
+ const queryWorkspace = normalizeOptionalString(input.workspace);
20146
+ const workspace = await this.workspaceResolver.resolve(input.workspace);
20147
+ const page = await this.repository.list({
20148
+ workspace,
20149
+ global: input.global,
20150
+ offset: normalizeOffset(input.offset),
20151
+ limit: normalizeListLimit(input.limit)
20152
+ });
20153
+ return {
20154
+ items: page.items.map((item) => remapWorkspace(item, workspace, queryWorkspace)),
20155
+ hasMore: page.hasMore
20156
+ };
20157
+ }
20158
+ async listWorkspaces() {
20159
+ return this.repository.listWorkspaces();
20160
+ }
20161
+ }
20162
+ function normalizeOffset(value) {
20163
+ return Number.isInteger(value) && value && value > 0 ? value : 0;
20164
+ }
20165
+ function normalizeListLimit(value) {
20166
+ if (!Number.isInteger(value) || value === undefined) {
20167
+ return DEFAULT_LIST_LIMIT;
20168
+ }
20169
+ return Math.min(Math.max(value, 1), MAX_LIST_LIMIT);
20170
+ }
20171
+ function normalizeOptionalString(value) {
20172
+ const trimmed = value?.trim();
20173
+ return trimmed ? trimmed : undefined;
20174
+ }
20175
+ function remapWorkspace(record3, canonicalWorkspace, queryWorkspace) {
20176
+ if (record3.workspace !== canonicalWorkspace) {
20177
+ return record3;
20178
+ }
20179
+ return { ...record3, workspace: queryWorkspace };
20180
+ }
20181
+
20458
20182
  // src/sqlite/db.ts
20459
- import { mkdirSync as mkdirSync2 } from "node:fs";
20183
+ import { mkdirSync } from "node:fs";
20460
20184
  import { dirname } from "node:path";
20461
20185
  import Database from "better-sqlite3";
20462
20186
 
20463
20187
  // src/sqlite/memory-schema.ts
20464
- function createMemoriesTable(database, options) {
20465
- const embeddingColumn = getEmbeddingColumnSql(options.embeddingColumn);
20188
+ function createMemoriesTable(database) {
20466
20189
  database.exec(`
20467
20190
  CREATE TABLE IF NOT EXISTS memories (
20468
20191
  id TEXT PRIMARY KEY,
20469
20192
  content TEXT NOT NULL,
20470
20193
  workspace TEXT,
20471
- ${embeddingColumn}
20472
20194
  created_at INTEGER NOT NULL,
20473
20195
  updated_at INTEGER NOT NULL
20474
20196
  );
@@ -20476,7 +20198,7 @@ function createMemoriesTable(database, options) {
20476
20198
  }
20477
20199
  function createMemoryIndexes(database) {
20478
20200
  database.exec(`
20479
- CREATE INDEX IF NOT EXISTS idx_memories_created_at ON memories(created_at);
20201
+ CREATE INDEX IF NOT EXISTS idx_memories_updated_at ON memories(updated_at);
20480
20202
  CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace);
20481
20203
  `);
20482
20204
  }
@@ -20516,82 +20238,38 @@ function dropMemorySearchArtifacts(database) {
20516
20238
  DROP TABLE IF EXISTS memories_fts;
20517
20239
  `);
20518
20240
  }
20519
- function getEmbeddingColumnSql(mode) {
20520
- switch (mode) {
20521
- case "omit":
20522
- return "";
20523
- case "nullable":
20524
- return `embedding BLOB,
20525
- `;
20526
- case "required":
20527
- return `embedding BLOB NOT NULL,
20528
- `;
20529
- }
20530
- }
20531
20241
 
20532
20242
  // src/sqlite/migrations/001-create-memory-schema.ts
20533
20243
  var createMemorySchemaMigration = {
20534
20244
  version: 1,
20535
20245
  async up(database) {
20536
- createMemoriesTable(database, { embeddingColumn: "omit" });
20246
+ createMemoriesTable(database);
20537
20247
  createMemoryIndexes(database);
20538
20248
  createMemorySearchArtifacts(database);
20539
20249
  }
20540
20250
  };
20541
20251
 
20542
- // src/sqlite/embedding-codec.ts
20543
- var FLOAT32_BYTE_WIDTH = 4;
20544
- function encodeEmbedding(vector) {
20545
- const typedArray = Float32Array.from(vector);
20546
- return new Uint8Array(typedArray.buffer.slice(0));
20547
- }
20548
- function decodeEmbedding(value) {
20549
- const bytes = toUint8Array(value);
20550
- if (bytes.byteLength === 0) {
20551
- throw new Error("Embedding blob is empty.");
20552
- }
20553
- if (bytes.byteLength % FLOAT32_BYTE_WIDTH !== 0) {
20554
- throw new Error("Embedding blob length is not a multiple of 4.");
20555
- }
20556
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
20557
- const vector = [];
20558
- for (let offset = 0;offset < bytes.byteLength; offset += FLOAT32_BYTE_WIDTH) {
20559
- vector.push(view.getFloat32(offset, true));
20560
- }
20561
- return vector;
20562
- }
20563
- function toUint8Array(value) {
20564
- if (value instanceof Uint8Array) {
20565
- return value;
20566
- }
20567
- if (value instanceof ArrayBuffer) {
20568
- return new Uint8Array(value);
20569
- }
20570
- throw new Error("Expected embedding blob as Uint8Array or ArrayBuffer.");
20571
- }
20572
-
20573
20252
  // src/sqlite/migrations/002-add-memory-embedding.ts
20574
- function createAddMemoryEmbeddingMigration(embeddingService) {
20253
+ function createAddMemoryEmbeddingMigration() {
20575
20254
  return {
20576
20255
  version: 2,
20577
20256
  async up(database) {
20578
20257
  database.exec("ALTER TABLE memories ADD COLUMN embedding BLOB");
20579
- const rows = database.prepare("SELECT id, content FROM memories ORDER BY created_at ASC").all();
20580
- const updateStatement = database.prepare("UPDATE memories SET embedding = ? WHERE id = ?");
20581
- for (const row of rows) {
20582
- const embedding = await embeddingService.createVector(row.content);
20583
- updateStatement.run(encodeEmbedding(embedding), row.id);
20584
- }
20585
- const nullRows = database.prepare("SELECT COUNT(*) AS count FROM memories WHERE embedding IS NULL").all();
20586
- if ((nullRows[0]?.count ?? 0) > 0) {
20587
- throw new Error("Failed to backfill embeddings for all memories.");
20588
- }
20589
20258
  dropMemorySearchArtifacts(database);
20590
20259
  database.exec("ALTER TABLE memories RENAME TO memories_old");
20591
- createMemoriesTable(database, { embeddingColumn: "required" });
20260
+ database.exec(`
20261
+ CREATE TABLE IF NOT EXISTS memories (
20262
+ id TEXT PRIMARY KEY,
20263
+ content TEXT NOT NULL,
20264
+ workspace TEXT,
20265
+ embedding BLOB NOT NULL,
20266
+ created_at INTEGER NOT NULL,
20267
+ updated_at INTEGER NOT NULL
20268
+ );
20269
+ `);
20592
20270
  database.exec(`
20593
20271
  INSERT INTO memories (id, content, workspace, embedding, created_at, updated_at)
20594
- SELECT id, content, workspace, embedding, created_at, updated_at
20272
+ SELECT id, content, workspace, COALESCE(embedding, X'00000000'), created_at, updated_at
20595
20273
  FROM memories_old
20596
20274
  `);
20597
20275
  database.exec("DROP TABLE memories_old");
@@ -20619,12 +20297,31 @@ function createNormalizeWorkspaceMigration(workspaceResolver) {
20619
20297
  };
20620
20298
  }
20621
20299
 
20300
+ // src/sqlite/migrations/004-remove-memory-embedding.ts
20301
+ var removeMemoryEmbeddingMigration = {
20302
+ version: 4,
20303
+ async up(database) {
20304
+ dropMemorySearchArtifacts(database);
20305
+ database.exec("ALTER TABLE memories RENAME TO memories_old");
20306
+ createMemoriesTable(database);
20307
+ database.exec(`
20308
+ INSERT INTO memories (id, content, workspace, created_at, updated_at)
20309
+ SELECT id, content, workspace, created_at, updated_at
20310
+ FROM memories_old
20311
+ `);
20312
+ database.exec("DROP TABLE memories_old");
20313
+ createMemoryIndexes(database);
20314
+ createMemorySearchArtifacts(database, true);
20315
+ }
20316
+ };
20317
+
20622
20318
  // src/sqlite/migrations/index.ts
20623
20319
  function createMemoryMigrations(options) {
20624
20320
  return [
20625
20321
  createMemorySchemaMigration,
20626
- createAddMemoryEmbeddingMigration(options.embeddingService),
20627
- createNormalizeWorkspaceMigration(options.workspaceResolver)
20322
+ createAddMemoryEmbeddingMigration(),
20323
+ createNormalizeWorkspaceMigration(options.workspaceResolver),
20324
+ removeMemoryEmbeddingMigration
20628
20325
  ];
20629
20326
  }
20630
20327
 
@@ -20638,7 +20335,7 @@ var PRAGMA_STATEMENTS = [
20638
20335
  async function openMemoryDatabase(databasePath, options) {
20639
20336
  let database;
20640
20337
  try {
20641
- mkdirSync2(dirname(databasePath), { recursive: true });
20338
+ mkdirSync(dirname(databasePath), { recursive: true });
20642
20339
  database = new Database(databasePath);
20643
20340
  await initializeMemoryDatabase(database, createMemoryMigrations(options));
20644
20341
  return database;
@@ -20712,7 +20409,6 @@ function validateMigrations(migrations) {
20712
20409
  }
20713
20410
  // src/sqlite/repository.ts
20714
20411
  import { randomUUID } from "node:crypto";
20715
- var DEFAULT_SEARCH_LIMIT = 15;
20716
20412
  var DEFAULT_LIST_LIMIT2 = 15;
20717
20413
 
20718
20414
  class SqliteMemoryRepository {
@@ -20725,24 +20421,11 @@ class SqliteMemoryRepository {
20725
20421
  constructor(database) {
20726
20422
  this.database = database;
20727
20423
  this.insertStatement = database.prepare(`
20728
- INSERT INTO memories (
20729
- id,
20730
- content,
20731
- workspace,
20732
- embedding,
20733
- created_at,
20734
- updated_at
20735
- ) VALUES (
20736
- ?,
20737
- ?,
20738
- ?,
20739
- ?,
20740
- ?,
20741
- ?
20742
- )
20424
+ INSERT INTO memories (id, content, workspace, created_at, updated_at)
20425
+ VALUES (?, ?, ?, ?, ?)
20743
20426
  `);
20744
- this.getStatement = database.prepare("SELECT id, content, workspace, embedding, created_at, updated_at FROM memories WHERE id = ?");
20745
- this.updateStatement = database.prepare("UPDATE memories SET content = ?, embedding = ?, updated_at = ? WHERE id = ?");
20427
+ this.getStatement = database.prepare("SELECT id, content, workspace, created_at, updated_at FROM memories WHERE id = ?");
20428
+ this.updateStatement = database.prepare("UPDATE memories SET content = ?, updated_at = ? WHERE id = ?");
20746
20429
  this.deleteStatement = database.prepare("DELETE FROM memories WHERE id = ?");
20747
20430
  this.listWorkspacesStatement = database.prepare("SELECT DISTINCT workspace FROM memories WHERE workspace IS NOT NULL ORDER BY workspace");
20748
20431
  }
@@ -20752,63 +20435,21 @@ class SqliteMemoryRepository {
20752
20435
  const memory = {
20753
20436
  id: randomUUID(),
20754
20437
  content: input.content,
20755
- embedding: input.embedding,
20756
20438
  workspace: input.workspace,
20757
20439
  createdAt: now,
20758
20440
  updatedAt: now
20759
20441
  };
20760
- this.insertStatement.run(memory.id, memory.content, memory.workspace, encodeEmbedding(memory.embedding), memory.createdAt.getTime(), memory.updatedAt.getTime());
20442
+ this.insertStatement.run(memory.id, memory.content, memory.workspace, memory.createdAt.getTime(), memory.updatedAt.getTime());
20761
20443
  return memory;
20762
20444
  } catch (error2) {
20763
20445
  throw new PersistenceError("Failed to save memory.", { cause: error2 });
20764
20446
  }
20765
20447
  }
20766
- async search(query) {
20767
- try {
20768
- const whereParams = [toFtsQuery(query.terms)];
20769
- const limit = query.limit ?? DEFAULT_SEARCH_LIMIT;
20770
- const whereClauses = ["memories_fts MATCH ?"];
20771
- if (query.updatedAfter) {
20772
- whereClauses.push("m.updated_at >= ?");
20773
- whereParams.push(query.updatedAfter.getTime());
20774
- }
20775
- if (query.updatedBefore) {
20776
- whereClauses.push("m.updated_at <= ?");
20777
- whereParams.push(query.updatedBefore.getTime());
20778
- }
20779
- const params = [...whereParams, limit];
20780
- const statement = this.database.prepare(`
20781
- SELECT
20782
- m.id,
20783
- m.content,
20784
- m.workspace,
20785
- m.embedding,
20786
- m.created_at,
20787
- m.updated_at,
20788
- MAX(0, -bm25(memories_fts)) AS score
20789
- FROM memories_fts
20790
- INNER JOIN memories AS m ON m.rowid = memories_fts.rowid
20791
- WHERE ${whereClauses.join(" AND ")}
20792
- ORDER BY score DESC
20793
- LIMIT ?
20794
- `);
20795
- const rows = statement.all(...params);
20796
- const maxScore = Math.max(...rows.map((row) => row.score), 0);
20797
- return rows.map((row) => ({
20798
- ...toMemoryEntity(row),
20799
- score: toNormalizedScore(maxScore > 0 ? row.score / maxScore : 0)
20800
- }));
20801
- } catch (error2) {
20802
- throw new PersistenceError("Failed to search memories.", {
20803
- cause: error2
20804
- });
20805
- }
20806
- }
20807
20448
  async get(id) {
20808
20449
  try {
20809
20450
  const rows = this.getStatement.all(id);
20810
20451
  const row = rows[0];
20811
- return row ? toMemoryEntity(row) : undefined;
20452
+ return row ? toMemoryRecord(row) : undefined;
20812
20453
  } catch (error2) {
20813
20454
  throw new PersistenceError("Failed to find memory.", { cause: error2 });
20814
20455
  }
@@ -20819,25 +20460,28 @@ class SqliteMemoryRepository {
20819
20460
  const params = [];
20820
20461
  const offset = options.offset ?? 0;
20821
20462
  const limit = options.limit ?? DEFAULT_LIST_LIMIT2;
20822
- if (options.workspace) {
20463
+ if (options.workspace && options.global) {
20464
+ whereClauses.push("(workspace = ? OR workspace IS NULL)");
20465
+ params.push(options.workspace);
20466
+ } else if (options.workspace) {
20823
20467
  whereClauses.push("workspace = ?");
20824
20468
  params.push(options.workspace);
20825
- } else if (options.workspaceIsNull) {
20469
+ } else if (options.global) {
20826
20470
  whereClauses.push("workspace IS NULL");
20827
20471
  }
20828
20472
  const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
20829
20473
  const queryLimit = limit + 1;
20830
20474
  params.push(queryLimit, offset);
20831
20475
  const statement = this.database.prepare(`
20832
- SELECT id, content, workspace, embedding, created_at, updated_at
20476
+ SELECT id, content, workspace, created_at, updated_at
20833
20477
  FROM memories
20834
20478
  ${whereClause}
20835
- ORDER BY created_at DESC
20479
+ ORDER BY updated_at DESC
20836
20480
  LIMIT ? OFFSET ?
20837
20481
  `);
20838
20482
  const rows = statement.all(...params);
20839
20483
  const hasMore = rows.length > limit;
20840
- const items = (hasMore ? rows.slice(0, limit) : rows).map(toMemoryEntity);
20484
+ const items = (hasMore ? rows.slice(0, limit) : rows).map(toMemoryRecord);
20841
20485
  return { items, hasMore };
20842
20486
  } catch (error2) {
20843
20487
  throw new PersistenceError("Failed to list memories.", { cause: error2 });
@@ -20847,7 +20491,7 @@ class SqliteMemoryRepository {
20847
20491
  let result;
20848
20492
  try {
20849
20493
  const now = Date.now();
20850
- result = this.updateStatement.run(input.content, encodeEmbedding(input.embedding), now, input.id);
20494
+ result = this.updateStatement.run(input.content, now, input.id);
20851
20495
  } catch (error2) {
20852
20496
  throw new PersistenceError("Failed to update memory.", { cause: error2 });
20853
20497
  }
@@ -20880,22 +20524,13 @@ class SqliteMemoryRepository {
20880
20524
  }
20881
20525
  }
20882
20526
  }
20883
- var toMemoryEntity = (row) => ({
20527
+ var toMemoryRecord = (row) => ({
20884
20528
  id: row.id,
20885
20529
  content: row.content,
20886
- embedding: decodeEmbedding(row.embedding),
20887
20530
  workspace: row.workspace ?? undefined,
20888
20531
  createdAt: new Date(row.created_at),
20889
20532
  updatedAt: new Date(row.updated_at)
20890
20533
  });
20891
- var toFtsQuery = (terms) => terms.map(toFtsTerm).join(" OR ");
20892
- function toFtsTerm(term) {
20893
- const escaped = term.replaceAll('"', '""');
20894
- if (term.includes(" ")) {
20895
- return `"${escaped}"`;
20896
- }
20897
- return `"${escaped}"*`;
20898
- }
20899
20534
  // node_modules/@hono/node-server/dist/index.mjs
20900
20535
  import { createServer as createServerHTTP } from "http";
20901
20536
  import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
@@ -24211,7 +23846,7 @@ function createPageRoutes(memory) {
24211
23846
  const wsFilter = workspace && !isNoWorkspace ? workspace : undefined;
24212
23847
  const page = await memory.list({
24213
23848
  workspace: wsFilter,
24214
- workspaceIsNull: isNoWorkspace,
23849
+ global: isNoWorkspace,
24215
23850
  offset: (pageNum - 1) * DEFAULT_LIST_LIMIT3,
24216
23851
  limit: DEFAULT_LIST_LIMIT3
24217
23852
  });
@@ -24332,13 +23967,10 @@ function normalizeOptionalString2(value) {
24332
23967
 
24333
23968
  // src/index.ts
24334
23969
  var config2 = resolveConfig();
24335
- configureModelsCache(config2.modelsCachePath);
24336
- var embeddingService = new EmbeddingService;
24337
23970
  var workspaceResolver = createGitWorkspaceResolver();
24338
- var database = await openMemoryDatabase(config2.databasePath, { embeddingService, workspaceResolver });
23971
+ var database = await openMemoryDatabase(config2.databasePath, { workspaceResolver });
24339
23972
  var repository2 = new SqliteMemoryRepository(database);
24340
- var memoryService = new MemoryService(repository2, embeddingService, workspaceResolver);
24341
- embeddingService.warmup();
23973
+ var memoryService = new MemoryService(repository2, workspaceResolver);
24342
23974
  if (config2.uiMode) {
24343
23975
  let shutdown = function() {
24344
23976
  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.20",
4
+ "version": "0.1.0",
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 --external @huggingface/transformers --outfile dist/index.js --banner \"#!/usr/bin/env node\"",
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
26
  "lint": "biome check --write && tsc --noEmit",
@@ -38,7 +38,6 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@hono/node-server": "^1.19.11",
41
- "@huggingface/transformers": "^3.8.1",
42
41
  "@modelcontextprotocol/sdk": "^1.27.1",
43
42
  "better-sqlite3": "^12.6.2",
44
43
  "hono": "^4.12.8",