@persql/context 0.1.0 → 1.0.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.
package/dist/index.cjs CHANGED
@@ -26,65 +26,63 @@ __export(index_exports, {
26
26
  SCHEMA_SQL: () => SCHEMA_SQL,
27
27
  SCHEMA_VERSION: () => SCHEMA_VERSION,
28
28
  TABLE_PREFIX: () => TABLE_PREFIX,
29
- buildByTag: () => buildByTag,
29
+ buildDetectLegacySchema: () => buildDetectLegacySchema,
30
+ buildDropLegacy: () => buildDropLegacy,
30
31
  buildEdgesAmong: () => buildEdgesAmong,
31
32
  buildExtractionMessages: () => buildExtractionMessages,
32
33
  buildForget: () => buildForget,
33
34
  buildGet: () => buildGet,
35
+ buildIndex: () => buildIndex,
34
36
  buildInit: () => buildInit,
35
37
  buildNeighborEntities: () => buildNeighborEntities,
36
38
  buildRecall: () => buildRecall,
37
- buildRecent: () => buildRecent,
38
39
  buildRemember: () => buildRemember,
39
40
  buildStats: () => buildStats,
40
- buildSupersede: () => buildSupersede,
41
41
  buildUpsertEdge: () => buildUpsertEdge,
42
42
  buildUpsertEntity: () => buildUpsertEntity,
43
43
  context: () => context,
44
44
  entityId: () => entityId,
45
- normalizeTags: () => normalizeTags,
45
+ memoryTools: () => memoryTools,
46
46
  toFtsQuery: () => toFtsQuery
47
47
  });
48
48
  module.exports = __toCommonJS(index_exports);
49
49
 
50
50
  // src/core.ts
51
- var SCHEMA_VERSION = 1;
51
+ var SCHEMA_VERSION = 2;
52
52
  var TABLE_PREFIX = "ctx_";
53
53
  var T = TABLE_PREFIX;
54
54
  var SCHEMA_SQL = [
55
55
  `CREATE TABLE IF NOT EXISTS ${T}memory (
56
56
  id TEXT PRIMARY KEY,
57
- kind TEXT NOT NULL DEFAULT 'fact',
58
- topic TEXT,
57
+ name TEXT NOT NULL,
58
+ description TEXT NOT NULL,
59
+ type TEXT NOT NULL DEFAULT 'project',
59
60
  body TEXT NOT NULL,
60
- tags TEXT,
61
61
  source TEXT,
62
62
  created_at INTEGER NOT NULL,
63
- superseded_by TEXT
63
+ updated_at INTEGER NOT NULL
64
64
  )`,
65
- `CREATE INDEX IF NOT EXISTS ${T}memory_kind ON ${T}memory(kind)`,
66
- `CREATE INDEX IF NOT EXISTS ${T}memory_created ON ${T}memory(created_at)`,
67
- `CREATE INDEX IF NOT EXISTS ${T}memory_superseded ON ${T}memory(superseded_by)`,
68
- // External-content FTS index: body text is not duplicated, only indexed.
69
- // Porter stemmer so "invoice" recalls "invoicing" without embeddings.
65
+ `CREATE UNIQUE INDEX IF NOT EXISTS ${T}memory_name ON ${T}memory(name)`,
66
+ // External-content FTS on name+description+body. Porter stemmer so
67
+ // "invoice" recalls "invoicing" without embeddings.
70
68
  `CREATE VIRTUAL TABLE IF NOT EXISTS ${T}memory_fts USING fts5(
71
- topic, body, tags,
69
+ name, description, body,
72
70
  content='${T}memory', content_rowid='rowid',
73
71
  tokenize='porter unicode61'
74
72
  )`,
75
73
  `CREATE TRIGGER IF NOT EXISTS ${T}memory_ai AFTER INSERT ON ${T}memory BEGIN
76
- INSERT INTO ${T}memory_fts(rowid, topic, body, tags)
77
- VALUES (new.rowid, new.topic, new.body, new.tags);
74
+ INSERT INTO ${T}memory_fts(rowid, name, description, body)
75
+ VALUES (new.rowid, new.name, new.description, new.body);
78
76
  END`,
79
77
  `CREATE TRIGGER IF NOT EXISTS ${T}memory_ad AFTER DELETE ON ${T}memory BEGIN
80
- INSERT INTO ${T}memory_fts(${T}memory_fts, rowid, topic, body, tags)
81
- VALUES ('delete', old.rowid, old.topic, old.body, old.tags);
78
+ INSERT INTO ${T}memory_fts(${T}memory_fts, rowid, name, description, body)
79
+ VALUES ('delete', old.rowid, old.name, old.description, old.body);
82
80
  END`,
83
81
  `CREATE TRIGGER IF NOT EXISTS ${T}memory_au AFTER UPDATE ON ${T}memory BEGIN
84
- INSERT INTO ${T}memory_fts(${T}memory_fts, rowid, topic, body, tags)
85
- VALUES ('delete', old.rowid, old.topic, old.body, old.tags);
86
- INSERT INTO ${T}memory_fts(rowid, topic, body, tags)
87
- VALUES (new.rowid, new.topic, new.body, new.tags);
82
+ INSERT INTO ${T}memory_fts(${T}memory_fts, rowid, name, description, body)
83
+ VALUES ('delete', old.rowid, old.name, old.description, old.body);
84
+ INSERT INTO ${T}memory_fts(rowid, name, description, body)
85
+ VALUES (new.rowid, new.name, new.description, new.body);
88
86
  END`,
89
87
  `CREATE TABLE IF NOT EXISTS ${T}entity (
90
88
  id TEXT PRIMARY KEY,
@@ -107,15 +105,21 @@ var SCHEMA_SQL = [
107
105
  function buildInit() {
108
106
  return SCHEMA_SQL.map((sql) => ({ sql, params: [] }));
109
107
  }
110
- var FTS_OPERATORS = /* @__PURE__ */ new Set(["and", "or", "not", "near"]);
111
- function normalizeTags(tags) {
112
- if (!tags) return null;
113
- const arr = Array.isArray(tags) ? tags : String(tags).split(/[,\s]+/);
114
- const norm = Array.from(
115
- new Set(arr.map((t) => t.trim().toLowerCase()).filter(Boolean))
116
- );
117
- return norm.length ? norm.join(" ") : null;
108
+ function buildDetectLegacySchema() {
109
+ return {
110
+ sql: `SELECT count(*) AS n FROM pragma_table_info('${T}memory') WHERE name='topic'`,
111
+ params: []
112
+ };
118
113
  }
114
+ function buildDropLegacy() {
115
+ return [
116
+ { sql: `DROP TABLE IF EXISTS ${T}memory_fts`, params: [] },
117
+ { sql: `DROP TABLE IF EXISTS ${T}memory`, params: [] },
118
+ { sql: `DROP TABLE IF EXISTS ${T}entity`, params: [] },
119
+ { sql: `DROP TABLE IF EXISTS ${T}edge`, params: [] }
120
+ ];
121
+ }
122
+ var FTS_OPERATORS = /* @__PURE__ */ new Set(["and", "or", "not", "near"]);
119
123
  function toFtsQuery(input, opts = {}) {
120
124
  if (opts.mode === "raw") return input.trim() || null;
121
125
  const tokens = (input.match(/[\p{L}\p{N}_]+/gu) ?? []).filter(
@@ -138,103 +142,71 @@ function resolveEntityRef(ref) {
138
142
  function edgeId(srcId, rel, dstId) {
139
143
  return slugId("x_", `${srcId}|${rel}|${dstId}`);
140
144
  }
141
- var MEMORY_COLS = "id, kind, topic, body, tags, source, created_at AS createdAt, superseded_by AS supersededBy";
145
+ var MEMORY_COLS = "id, name, description, type, body, source, created_at AS createdAt, updated_at AS updatedAt";
142
146
  function buildRemember(input, now, id) {
143
147
  return {
144
- sql: `INSERT INTO ${T}memory (id, kind, topic, body, tags, source, created_at, superseded_by)
145
- VALUES (?, ?, ?, ?, ?, ?, ?, NULL)`,
148
+ sql: `INSERT INTO ${T}memory (id, name, description, type, body, source, created_at, updated_at)
149
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
150
+ ON CONFLICT(name) DO UPDATE SET
151
+ description = excluded.description,
152
+ body = excluded.body,
153
+ type = excluded.type,
154
+ source = excluded.source,
155
+ updated_at = excluded.updated_at`,
146
156
  params: [
147
157
  id,
148
- input.kind ?? "fact",
149
- input.topic ?? null,
158
+ input.name,
159
+ input.description,
160
+ input.type ?? "project",
150
161
  input.body,
151
- normalizeTags(input.tags),
152
162
  input.source ?? null,
163
+ now,
153
164
  now
154
165
  ]
155
166
  };
156
167
  }
157
- function buildSupersede(oldId, newId) {
158
- return {
159
- sql: `UPDATE ${T}memory SET superseded_by = ? WHERE id = ? AND superseded_by IS NULL`,
160
- params: [newId, oldId]
161
- };
162
- }
163
- function buildForget(id) {
164
- return { sql: `DELETE FROM ${T}memory WHERE id = ?`, params: [id] };
168
+ function buildForget(name) {
169
+ return { sql: `DELETE FROM ${T}memory WHERE name = ?`, params: [name] };
165
170
  }
166
- function buildGet(id) {
167
- return {
168
- sql: `SELECT ${MEMORY_COLS} FROM ${T}memory WHERE id = ?`,
169
- params: [id]
170
- };
171
- }
172
- function buildRecall(query, opts = {}) {
173
- const match = toFtsQuery(query, { operator: opts.operator, mode: opts.mode });
174
- if (!match) return null;
175
- const where = [`${T}memory_fts MATCH ?`];
176
- const params = [match];
177
- if (!opts.includeSuperseded) where.push("m.superseded_by IS NULL");
178
- if (opts.kind) {
179
- where.push("m.kind = ?");
180
- params.push(opts.kind);
181
- }
182
- if (opts.sinceMs != null) {
183
- where.push("m.created_at >= ?");
184
- params.push(opts.sinceMs);
185
- }
186
- params.push(opts.limit ?? 8);
171
+ function buildGet(name) {
187
172
  return {
188
- sql: `SELECT m.id, m.kind, m.topic, m.body, m.tags, m.source,
189
- m.created_at AS createdAt, m.superseded_by AS supersededBy
190
- FROM ${T}memory_fts
191
- JOIN ${T}memory m ON m.rowid = ${T}memory_fts.rowid
192
- WHERE ${where.join(" AND ")}
193
- ORDER BY ${T}memory_fts.rank
194
- LIMIT ?`,
195
- params
173
+ sql: `SELECT ${MEMORY_COLS} FROM ${T}memory WHERE name = ?`,
174
+ params: [name]
196
175
  };
197
176
  }
198
- function buildRecent(opts = {}) {
177
+ function buildIndex(opts = {}) {
199
178
  const where = [];
200
179
  const params = [];
201
- if (!opts.includeSuperseded) where.push("superseded_by IS NULL");
202
- if (opts.kind) {
203
- where.push("kind = ?");
204
- params.push(opts.kind);
180
+ if (opts.type) {
181
+ where.push("type = ?");
182
+ params.push(opts.type);
205
183
  }
206
- params.push(opts.limit ?? 20);
184
+ params.push(opts.limit ?? 50);
207
185
  return {
208
- sql: `SELECT ${MEMORY_COLS} FROM ${T}memory
186
+ sql: `SELECT id, name, description, type, updated_at AS updatedAt FROM ${T}memory
209
187
  ${where.length ? "WHERE " + where.join(" AND ") : ""}
210
- ORDER BY created_at DESC
211
- LIMIT ?`,
188
+ ORDER BY updated_at DESC LIMIT ?`,
212
189
  params
213
190
  };
214
191
  }
215
- function buildByTag(tag, opts = {}) {
216
- const t = tag.trim().toLowerCase();
217
- const where = ["(' ' || COALESCE(tags, '') || ' ') LIKE ?"];
218
- const params = [`% ${t} %`];
219
- if (!opts.includeSuperseded) where.push("superseded_by IS NULL");
220
- if (opts.kind) {
221
- where.push("kind = ?");
222
- params.push(opts.kind);
223
- }
224
- params.push(opts.limit ?? 20);
192
+ function buildRecall(query, opts = {}) {
193
+ const match = toFtsQuery(query, { operator: opts.operator, mode: opts.mode });
194
+ if (!match) return null;
225
195
  return {
226
- sql: `SELECT ${MEMORY_COLS} FROM ${T}memory
227
- WHERE ${where.join(" AND ")}
228
- ORDER BY created_at DESC
196
+ sql: `SELECT m.id, m.name, m.description, m.type, m.body, m.source,
197
+ m.created_at AS createdAt, m.updated_at AS updatedAt
198
+ FROM ${T}memory_fts
199
+ JOIN ${T}memory m ON m.rowid = ${T}memory_fts.rowid
200
+ WHERE ${T}memory_fts MATCH ?
201
+ ORDER BY ${T}memory_fts.rank
229
202
  LIMIT ?`,
230
- params
203
+ params: [match, opts.limit ?? 8]
231
204
  };
232
205
  }
233
206
  function buildStats() {
234
207
  return {
235
208
  sql: `SELECT
236
- (SELECT count(*) FROM ${T}memory WHERE superseded_by IS NULL) AS facts,
237
- (SELECT count(*) FROM ${T}memory) AS facts_total,
209
+ (SELECT count(*) FROM ${T}memory) AS memories,
238
210
  (SELECT count(*) FROM ${T}entity) AS entities,
239
211
  (SELECT count(*) FROM ${T}edge) AS edges`,
240
212
  params: []
@@ -283,8 +255,7 @@ function buildNeighborEntities(seedId, depth, limit) {
283
255
  GROUP BY en.id
284
256
  ORDER BY depth, en.name
285
257
  LIMIT ?`,
286
- // The seed reappears at depth >0 via a round-trip on undirected edges;
287
- // exclude it explicitly so it isn't listed as its own neighbor.
258
+ // The seed appears at depth >0 on undirected round-trips exclude it.
288
259
  params: [seedId, depth, seedId, limit]
289
260
  };
290
261
  }
@@ -300,14 +271,21 @@ function buildEdgesAmong(ids) {
300
271
  params: [...ids, ...ids]
301
272
  };
302
273
  }
303
- var EXTRACTION_SYSTEM_PROMPT = `You extract durable, shareable context from raw text for a team of AI agents that will read it later by keyword.
274
+ var EXTRACTION_SYSTEM_PROMPT = `You extract durable, structured memories from a conversation or document for an AI agent that will recall them across sessions.
304
275
 
305
- Return JSON: { "facts": [...], "entities": [...], "edges": [...] }.
306
- - facts: atomic, self-contained statements worth remembering across sessions \u2014 decisions, conventions, constraints, identifiers, preferences. Each: { "body": string, "topic"?: string, "tags"?: string[] }. Prefer specific and stable over transient chatter.
307
- - entities: named things \u2014 services, people, tables, repos, concepts. Each: { "name": string, "kind"?: string, "body"?: short description }.
308
- - edges: relationships between entities, referencing entity names. Each: { "src": name, "rel": string, "dst": name }.
276
+ Return JSON: { "memories": [...], "entities": [...], "edges": [...] }.
277
+ - memories: facts worth remembering across sessions \u2014 decisions, conventions, constraints, identifiers, preferences, schema details. Each:
278
+ {
279
+ "name": string (kebab-case slug, unique key \u2014 e.g. "auth-provider-choice", "todos-table-schema", "user-prefers-metrics"),
280
+ "description": string (one-liner shown in the always-loaded index),
281
+ "type": "user"|"feedback"|"project"|"reference",
282
+ "body": string (full markdown content with all relevant detail)
283
+ }
284
+ Types: user=who they are/preferences; feedback=guidance for agents; project=ongoing work/decisions; reference=where to find things.
285
+ - entities: named things \u2014 services, people, tables, repos. Each: { "name": string, "kind"?: string, "body"?: string }
286
+ - edges: relationships between entities. Each: { "src": name, "rel": string, "dst": name }
309
287
 
310
- Be conservative: omit anything ephemeral or low-value. Use lowercase tags. If nothing is worth keeping, return empty arrays.`;
288
+ Be conservative. Use unique, meaningful names. If nothing durable is found, return empty arrays.`;
311
289
  function buildExtractionMessages(raw, opts = {}) {
312
290
  return [
313
291
  { role: "system", content: EXTRACTION_SYSTEM_PROMPT },
@@ -322,16 +300,17 @@ function buildExtractionMessages(raw, opts = {}) {
322
300
  var EXTRACTION_JSON_SCHEMA = {
323
301
  type: "object",
324
302
  properties: {
325
- facts: {
303
+ memories: {
326
304
  type: "array",
327
305
  items: {
328
306
  type: "object",
329
307
  properties: {
330
- body: { type: "string" },
331
- topic: { type: "string" },
332
- tags: { type: "array", items: { type: "string" } }
308
+ name: { type: "string" },
309
+ description: { type: "string" },
310
+ type: { type: "string", enum: ["user", "feedback", "project", "reference"] },
311
+ body: { type: "string" }
333
312
  },
334
- required: ["body"]
313
+ required: ["name", "description", "body"]
335
314
  }
336
315
  },
337
316
  entities: {
@@ -377,44 +356,29 @@ var ContextStore = class {
377
356
  await this.db.batch(buildInit());
378
357
  }
379
358
  // --- writes -------------------------------------------------------------
380
- /** Store one structured fact/episode/artifact. Returns its id. */
359
+ /** UPSERT a named memory. Same name → update; new name → insert. */
381
360
  async remember(input) {
382
- const id = input.id ?? uid("m_");
361
+ const id = uid("m_");
383
362
  const now = Date.now();
384
- const stmts = [
385
- buildRemember({ ...input, source: input.source ?? this.opts.source }, now, id)
386
- ];
387
- const sup = input.supersedes ? Array.isArray(input.supersedes) ? input.supersedes : [input.supersedes] : [];
388
- for (const old of sup) stmts.push(buildSupersede(old, id));
389
- await this.db.transaction(stmts);
390
- return id;
391
- }
392
- /**
393
- * Extract durable context from raw text and store it. The LLM runs on the
394
- * write path; pass `extract` here or to the constructor (or use the hosted
395
- * Context MCP, which runs extraction server-side and meters it).
396
- */
397
- async rememberRaw(raw, opts = {}) {
398
- const extract = opts.extract ?? this.opts.extract;
399
- if (!extract) {
400
- throw new Error(
401
- "ContextStore.rememberRaw needs an extractor: pass { extract } here or to the constructor, or use the hosted PerSQL Context MCP."
402
- );
403
- }
404
- const extracted = await extract(raw, { hint: opts.hint });
405
- return this.write(extracted, { source: opts.source });
363
+ const s = buildRemember(
364
+ { ...input, source: input.source ?? this.opts.source },
365
+ now,
366
+ id
367
+ );
368
+ await this.db.query(s.sql, s.params);
406
369
  }
407
- /** Persist an already-extracted bundle of facts, entities, and edges. */
370
+ /** Persist an already-extracted bundle of memories, entities, and edges. */
408
371
  async write(extracted, opts = {}) {
409
372
  const now = Date.now();
410
373
  const source = opts.source ?? this.opts.source;
411
374
  const stmts = [];
412
- const memoryIds = [];
375
+ const names = [];
413
376
  const entityIds = /* @__PURE__ */ new Set();
414
- for (const f of extracted.facts ?? []) {
377
+ for (const m of extracted.memories ?? []) {
378
+ if (!m?.name || !m?.body) continue;
415
379
  const id = uid("m_");
416
- memoryIds.push(id);
417
- stmts.push(buildRemember({ ...f, source }, now, id));
380
+ names.push(m.name);
381
+ stmts.push(buildRemember({ ...m, source }, now, id));
418
382
  }
419
383
  for (const e of extracted.entities ?? []) {
420
384
  stmts.push(buildUpsertEntity(e, now));
@@ -429,19 +393,14 @@ var ContextStore = class {
429
393
  }
430
394
  if (stmts.length) await this.db.transaction(stmts);
431
395
  return {
432
- memoryIds,
396
+ names,
433
397
  entityIds: [...entityIds],
434
398
  edgeCount: (extracted.edges ?? []).length
435
399
  };
436
400
  }
437
- /** Mark older memory rows as replaced by `newId` (filtered out of recall). */
438
- async supersede(oldIds, newId) {
439
- const ids = Array.isArray(oldIds) ? oldIds : [oldIds];
440
- await this.db.transaction(ids.map((o) => buildSupersede(o, newId)));
441
- }
442
- /** Hard-delete a memory row. */
443
- async forget(id) {
444
- const s = buildForget(id);
401
+ /** Hard-delete a memory row by name. */
402
+ async forget(name) {
403
+ const s = buildForget(name);
445
404
  await this.db.query(s.sql, s.params);
446
405
  }
447
406
  /** Add a relationship between two entities (created if absent). */
@@ -457,35 +416,25 @@ var ContextStore = class {
457
416
  ]);
458
417
  }
459
418
  // --- reads (no AI, pure lexical SQL) ------------------------------------
460
- /** Keyword recall, BM25-ranked, most relevant first. */
461
- async recall(query, opts = {}) {
462
- const resolved = { ...opts };
463
- if (opts.withinDays != null && opts.sinceMs == null) {
464
- resolved.sinceMs = Date.now() - opts.withinDays * 864e5;
465
- }
466
- const s = buildRecall(query, resolved);
467
- if (!s) return [];
419
+ /** Always-loaded index: name+description+type, newest first. */
420
+ async index(opts = {}) {
421
+ const s = buildIndex(opts);
468
422
  const { data } = await this.db.query(s.sql, s.params);
469
423
  return data;
470
424
  }
471
- /** Most recent memory rows. */
472
- async recent(opts = {}) {
473
- const s = buildRecent(opts);
425
+ /** Fetch one memory by name (with full body). */
426
+ async get(name) {
427
+ const s = buildGet(name);
474
428
  const { data } = await this.db.query(s.sql, s.params);
475
- return data;
429
+ return data[0] ?? null;
476
430
  }
477
- /** Memory rows carrying a given tag. */
478
- async byTag(tag, opts = {}) {
479
- const s = buildByTag(tag, opts);
431
+ /** Keyword recall, BM25-ranked, most relevant first. */
432
+ async recall(query, opts = {}) {
433
+ const s = buildRecall(query, opts);
434
+ if (!s) return [];
480
435
  const { data } = await this.db.query(s.sql, s.params);
481
436
  return data;
482
437
  }
483
- /** Fetch one memory row by id, or null. */
484
- async get(id) {
485
- const s = buildGet(id);
486
- const { data } = await this.db.query(s.sql, s.params);
487
- return data[0] ?? null;
488
- }
489
438
  /** The subgraph around an entity: reachable entities + the edges among them. */
490
439
  async neighbors(name, opts = {}) {
491
440
  const seed = name.startsWith("e_") ? name : entityId(name);
@@ -503,14 +452,13 @@ var ContextStore = class {
503
452
  );
504
453
  return { entities, edges };
505
454
  }
506
- /** Counts: current facts, total facts, entities, edges. */
455
+ /** Counts: memories, entities, edges. */
507
456
  async stats() {
508
457
  const s = buildStats();
509
458
  const { data } = await this.db.query(s.sql, s.params);
510
459
  const r = data[0];
511
460
  return {
512
- facts: r?.facts ?? 0,
513
- factsTotal: r?.facts_total ?? 0,
461
+ memories: r?.memories ?? 0,
514
462
  entities: r?.entities ?? 0,
515
463
  edges: r?.edges ?? 0
516
464
  };
@@ -519,6 +467,91 @@ var ContextStore = class {
519
467
  function context(db, opts) {
520
468
  return new ContextStore(db, opts);
521
469
  }
470
+ function memoryTools(store) {
471
+ return [
472
+ {
473
+ type: "function",
474
+ name: "remember_memory",
475
+ description: "Save or update a named memory. Use for facts worth keeping across sessions: schema details, user preferences, decisions. Same name overwrites the previous entry.",
476
+ parameters: {
477
+ type: "object",
478
+ properties: {
479
+ name: {
480
+ type: "string",
481
+ description: "Short kebab-case key, e.g. 'billing-preference'"
482
+ },
483
+ description: {
484
+ type: "string",
485
+ description: "One-line summary shown in the memory index"
486
+ },
487
+ type: {
488
+ type: "string",
489
+ enum: ["user", "feedback", "project", "reference"],
490
+ description: "Memory category"
491
+ },
492
+ body: {
493
+ type: "string",
494
+ description: "Full content of the memory"
495
+ }
496
+ },
497
+ required: ["name", "description", "body"],
498
+ additionalProperties: false
499
+ },
500
+ invoke: async (input) => {
501
+ await store.remember({
502
+ name: String(input.name),
503
+ description: String(input.description),
504
+ type: input.type ?? "project",
505
+ body: String(input.body)
506
+ });
507
+ return { saved: input.name };
508
+ }
509
+ },
510
+ {
511
+ type: "function",
512
+ name: "recall_memory",
513
+ description: "Search memories by keyword. Returns the most relevant entries (BM25-ranked). Use when the answer might be in memory before querying a live data source.",
514
+ parameters: {
515
+ type: "object",
516
+ properties: {
517
+ query: {
518
+ type: "string",
519
+ description: "Keywords to search for"
520
+ },
521
+ limit: {
522
+ type: "number",
523
+ description: "Max results (default 10)"
524
+ }
525
+ },
526
+ required: ["query"],
527
+ additionalProperties: false
528
+ },
529
+ invoke: async (input) => {
530
+ const rows = await store.recall(String(input.query), {
531
+ limit: typeof input.limit === "number" ? input.limit : 10
532
+ });
533
+ return { memories: rows };
534
+ }
535
+ },
536
+ {
537
+ type: "function",
538
+ name: "forget_memory",
539
+ description: "Delete a saved memory by name.",
540
+ parameters: {
541
+ type: "object",
542
+ properties: {
543
+ name: { type: "string", description: "Name of the memory to delete" }
544
+ },
545
+ required: ["name"],
546
+ additionalProperties: false
547
+ },
548
+ invoke: async (input) => {
549
+ await store.forget(String(input.name));
550
+ return { deleted: input.name };
551
+ }
552
+ }
553
+ ];
554
+ }
522
555
  // Annotate the CommonJS export names for ESM import in node:
523
556
  0 && (module.exports = {
524
557
  ContextStore,
@@ -527,23 +560,23 @@ function context(db, opts) {
527
560
  SCHEMA_SQL,
528
561
  SCHEMA_VERSION,
529
562
  TABLE_PREFIX,
530
- buildByTag,
563
+ buildDetectLegacySchema,
564
+ buildDropLegacy,
531
565
  buildEdgesAmong,
532
566
  buildExtractionMessages,
533
567
  buildForget,
534
568
  buildGet,
569
+ buildIndex,
535
570
  buildInit,
536
571
  buildNeighborEntities,
537
572
  buildRecall,
538
- buildRecent,
539
573
  buildRemember,
540
574
  buildStats,
541
- buildSupersede,
542
575
  buildUpsertEdge,
543
576
  buildUpsertEntity,
544
577
  context,
545
578
  entityId,
546
- normalizeTags,
579
+ memoryTools,
547
580
  toFtsQuery
548
581
  });
549
582
  //# sourceMappingURL=index.cjs.map