@hilbras/remembra 0.2.0 → 0.4.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/store.js CHANGED
@@ -3,14 +3,16 @@ import path from "node:path";
3
3
  import os from "node:os";
4
4
  import { randomUUID } from "node:crypto";
5
5
  /**
6
- * File-based memory store.
6
+ * File-based memory store (source of truth — no database).
7
7
  *
8
8
  * Layout (default root: ~/.remembra):
9
- * global/<id>.md — memories valid everywhere
10
- * scopes/<scope>/<id>.md — memories scoped to a project/workspace
9
+ * global/<id>.md — active memories, valid everywhere
10
+ * scopes/<scope>/<id>.md — active memories scoped to a project/workspace
11
+ * archived/global/<id>.md — archived memories (out of search, listed with flag)
12
+ * archived/scopes/<scope>/ — archived scoped memories
11
13
  *
12
- * Each file is markdown with YAML-ish frontmatter for human readability
13
- * and simple greppability.
14
+ * Each file is markdown with frontmatter for human readability and greppability.
15
+ * Maintenance runs opportunistically on search + via `memory_maintain`.
14
16
  */
15
17
  export class MemoryStore {
16
18
  root;
@@ -21,13 +23,15 @@ export class MemoryStore {
21
23
  return process.env.REMEMBRA_HOME ?? path.join(os.homedir(), ".remembra");
22
24
  }
23
25
  fileFor(m) {
24
- if (m.scope === "global") {
25
- return path.join(this.root, "global", `${m.id}.md`);
26
- }
27
- const safeScope = m.scope.replace(/[^a-zA-Z0-9._/-]/g, "_");
28
- return path.join(this.root, "scopes", safeScope, `${m.id}.md`);
26
+ const safeScope = m.scope === "global" ? "global" : m.scope.replace(/[^a-zA-Z0-9._/-]/g, "_");
27
+ const base = m.scope === "global" ? path.join(this.root, "global") : path.join(this.root, "scopes", safeScope);
28
+ const archivedBase = m.scope === "global"
29
+ ? path.join(this.root, "archived", "global")
30
+ : path.join(this.root, "archived", "scopes", safeScope);
31
+ const dir = m.archivedAt ? archivedBase : base;
32
+ return path.join(dir, `${m.id}.md`);
29
33
  }
30
- async store(input) {
34
+ async store(input, embedding) {
31
35
  const now = new Date().toISOString();
32
36
  const memory = {
33
37
  id: randomUUID().slice(0, 8),
@@ -39,6 +43,7 @@ export class MemoryStore {
39
43
  createdAt: now,
40
44
  updatedAt: now,
41
45
  source: input.source,
46
+ embedding,
42
47
  };
43
48
  const file = this.fileFor(memory);
44
49
  await fs.mkdir(path.dirname(file), { recursive: true });
@@ -52,12 +57,13 @@ export class MemoryStore {
52
57
  await fs.unlink(file);
53
58
  return true;
54
59
  }
55
- /** Load every memory across all scopes (global + scoped). */
56
- async all() {
57
- const files = [];
58
- for (const dir of [path.join(this.root, "global"), path.join(this.root, "scopes")]) {
59
- files.push(...(await walk(dir)));
60
+ /** Load active memories (excludes archived). Pass includeArchived for everything. */
61
+ async all(includeArchived = false) {
62
+ const dirs = [path.join(this.root, "global"), path.join(this.root, "scopes")];
63
+ if (includeArchived) {
64
+ dirs.push(path.join(this.root, "archived", "global"), path.join(this.root, "archived", "scopes"));
60
65
  }
66
+ const files = await walk(...dirs);
61
67
  const memories = await Promise.all(files.map((f) => parse(f)));
62
68
  return memories.filter((m) => m !== null);
63
69
  }
@@ -67,8 +73,56 @@ export class MemoryStore {
67
73
  return null;
68
74
  return parse(file);
69
75
  }
76
+ /** Move a memory to the archived tree (sets archivedAt). */
77
+ async archive(id) {
78
+ const m = await this.get(id);
79
+ if (!m || m.archivedAt)
80
+ return null;
81
+ const oldFile = this.fileFor(m);
82
+ const updated = { ...m, archivedAt: new Date().toISOString() };
83
+ const newFile = this.fileFor(updated);
84
+ if (oldFile === newFile)
85
+ return null;
86
+ await fs.mkdir(path.dirname(newFile), { recursive: true });
87
+ await fs.writeFile(newFile, render(updated), "utf8");
88
+ await fs.unlink(oldFile);
89
+ return updated;
90
+ }
91
+ /** Bring an archived memory back into active search. */
92
+ async revive(id) {
93
+ const m = await this.get(id);
94
+ if (!m || !m.archivedAt)
95
+ return null;
96
+ const oldFile = this.fileFor(m);
97
+ const now = new Date().toISOString();
98
+ const updated = { ...m, archivedAt: undefined, lastSeen: now, updatedAt: now };
99
+ const newFile = this.fileFor(updated);
100
+ await fs.mkdir(path.dirname(newFile), { recursive: true });
101
+ await fs.writeFile(newFile, render(updated), "utf8");
102
+ await fs.unlink(oldFile);
103
+ return updated;
104
+ }
105
+ /** Persist changes to an existing memory (merge/update path). */
106
+ async update(memory) {
107
+ const updated = { ...memory, updatedAt: new Date().toISOString() };
108
+ const file = this.fileFor(updated);
109
+ await fs.mkdir(path.dirname(file), { recursive: true });
110
+ await fs.writeFile(file, render(updated), "utf8");
111
+ return updated;
112
+ }
113
+ /** Record that a memory surfaced in search (decay refresh). Cheap: no-op if seen <1h ago. */
114
+ async touch(id) {
115
+ const m = await this.get(id);
116
+ if (!m)
117
+ return;
118
+ const last = Date.parse(m.lastSeen ?? m.updatedAt);
119
+ if (Number.isFinite(last) && Date.now() - last < 3_600_000)
120
+ return;
121
+ m.lastSeen = new Date().toISOString();
122
+ await fs.writeFile(this.fileFor(m), render(m), "utf8");
123
+ }
70
124
  async findFile(id) {
71
- const files = await walk(path.join(this.root, "global"), path.join(this.root, "scopes"));
125
+ const files = await walk(path.join(this.root, "global"), path.join(this.root, "scopes"), path.join(this.root, "archived"));
72
126
  return files.find((f) => path.basename(f, ".md") === id) ?? null;
73
127
  }
74
128
  }
@@ -102,7 +156,10 @@ function render(m) {
102
156
  `importance: ${m.importance}`,
103
157
  `created: ${m.createdAt}`,
104
158
  `updated: ${m.updatedAt}`,
159
+ m.lastSeen ? `lastSeen: ${m.lastSeen}` : undefined,
160
+ m.archivedAt ? `archivedAt: ${m.archivedAt}` : undefined,
105
161
  m.source ? `source: ${m.source}` : undefined,
162
+ m.embedding && m.embedding.length > 0 ? `embedding: [${m.embedding.join(",")}]` : undefined,
106
163
  "---",
107
164
  "",
108
165
  m.content,
@@ -124,6 +181,12 @@ async function parse(file) {
124
181
  meta[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
125
182
  }
126
183
  const tagsRaw = (meta.tags ?? "[]").replace(/^\[|\]$/g, "");
184
+ let embedding;
185
+ if (meta.embedding) {
186
+ const nums = meta.embedding.replace(/^\[|\]$/g, "").split(",").map(Number);
187
+ if (nums.length > 0 && nums.every((n) => Number.isFinite(n)))
188
+ embedding = nums;
189
+ }
127
190
  return {
128
191
  id: meta.id ?? path.basename(file, ".md"),
129
192
  type: (meta.type ?? "fact"),
@@ -133,7 +196,10 @@ async function parse(file) {
133
196
  importance: Number(meta.importance ?? 3),
134
197
  createdAt: meta.created ?? new Date(0).toISOString(),
135
198
  updatedAt: meta.updated ?? meta.created ?? new Date(0).toISOString(),
199
+ lastSeen: meta.lastSeen,
200
+ archivedAt: meta.archivedAt,
136
201
  source: meta.source,
202
+ embedding,
137
203
  };
138
204
  }
139
205
  catch {
package/dist/store.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,WAAW;IACO;IAA7B,YAA6B,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;IAAG,CAAC;IAE7C,MAAM,CAAC,WAAW;QAChB,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;IAC3E,CAAC;IAEO,OAAO,CAAC,CAAS;QACvB,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;QAC9F,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;QAC/G,MAAM,YAAY,GAChB,CAAC,CAAC,KAAK,KAAK,QAAQ;YAClB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC;YAC5C,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;QAC5D,MAAM,GAAG,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC;QAC/C,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAAiB,EAAE,SAAoB;QACjD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,MAAM,MAAM,GAAW;YACrB,EAAE,EAAE,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;YAC5B,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,SAAS,EAAE,GAAG;YACd,SAAS,EAAE,GAAG;YACd,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,SAAS;SACV,CAAC;QACF,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACxD,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;QACjD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAU;QACrB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACxB,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,qFAAqF;IACrF,KAAK,CAAC,GAAG,CAAC,eAAe,GAAG,KAAK;QAC/B,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC9E,IAAI,eAAe,EAAE,CAAC;YACpB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;QACpG,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QAClC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/D,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;IACzD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,EAAU;QAClB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC;IACrB,CAAC;IAED,4DAA4D;IAC5D,KAAK,CAAC,OAAO,CAAC,EAAU;QACtB,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC7B,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,OAAO,GAAW,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;QACvE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,OAAO,KAAK,OAAO;YAAE,OAAO,IAAI,CAAC;QACrC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,MAAM,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;QACrD,MAAM,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACzB,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,wDAAwD;IACxD,KAAK,CAAC,MAAM,CAAC,EAAU;QACrB,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC7B,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,MAAM,OAAO,GAAW,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;QACvF,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACtC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,MAAM,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;QACrD,MAAM,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACzB,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,iEAAiE;IACjE,KAAK,CAAC,MAAM,CAAC,MAAc;QACzB,MAAM,OAAO,GAAW,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;QAC3E,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACnC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACxD,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;QAClD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,6FAA6F;IAC7F,KAAK,CAAC,KAAK,CAAC,EAAU;QACpB,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC7B,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC;QACnD,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS;YAAE,OAAO;QACnE,CAAC,CAAC,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACtC,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACzD,CAAC;IAEO,KAAK,CAAC,QAAQ,CAAC,EAAU;QAC/B,MAAM,KAAK,GAAG,MAAM,IAAI,CACtB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAC9B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAC9B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CACjC,CAAC;QACF,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,IAAI,CAAC;IACnE,CAAC;CACF;AAED,KAAK,UAAU,IAAI,CAAC,GAAG,IAAc;IACnC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;YACpC,IAAI,CAAC,CAAC,WAAW,EAAE;gBAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;iBAChD,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,MAAM,CAAC,CAAS;IACvB,MAAM,KAAK,GAAG;QACZ,KAAK;QACL,OAAO,CAAC,CAAC,EAAE,EAAE;QACb,SAAS,CAAC,CAAC,IAAI,EAAE;QACjB,UAAU,CAAC,CAAC,KAAK,EAAE;QACnB,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;QAC9B,eAAe,CAAC,CAAC,UAAU,EAAE;QAC7B,YAAY,CAAC,CAAC,SAAS,EAAE;QACzB,YAAY,CAAC,CAAC,SAAS,EAAE;QACzB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS;QAClD,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,SAAS;QACxD,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS;QAC5C,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;QAC3F,KAAK;QACL,EAAE;QACF,CAAC,CAAC,OAAO;QACT,EAAE;KACH,CAAC;IACF,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzD,CAAC;AAED,KAAK,UAAU,KAAK,CAAC,IAAY;IAC/B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAChE,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,MAAM,IAAI,GAA2B,EAAE,CAAC;QACxC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACxC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC9B,IAAI,GAAG,KAAK,CAAC,CAAC;gBAAE,SAAS;YACzB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC/D,CAAC;QACD,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QAC5D,IAAI,SAA+B,CAAC;QACpC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAC3E,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBAAE,SAAS,GAAG,IAAI,CAAC;QACjF,CAAC;QACD,OAAO;YACL,EAAE,EAAE,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;YACzC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,MAAM,CAAmB;YAC7C,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;YACxB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,QAAQ;YAC7B,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE;YAC5D,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC;YACxC,SAAS,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;YACpD,SAAS,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;YACpE,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,SAAS;SACV,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
package/dist/types.d.ts CHANGED
@@ -18,6 +18,12 @@ export interface Memory {
18
18
  createdAt: string;
19
19
  updatedAt: string;
20
20
  source?: string;
21
+ /** Last time the memory surfaced in search results (decay signal). */
22
+ lastSeen?: string;
23
+ /** Set when archived; archived memories are out of search until revived. */
24
+ archivedAt?: string;
25
+ /** Cached embedding vector (REMEMBRA_EMBEDDINGS≠none); serialized in frontmatter. */
26
+ embedding?: number[];
21
27
  }
22
28
  export declare const StoreInput: z.ZodObject<{
23
29
  type: z.ZodEnum<["fact", "decision", "role", "history"]>;
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,6CAA6C;AAC7C,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAsB1E,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC;IACjC,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;IACnC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACrC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAC,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,6CAA6C;AAC7C,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AA4B1E,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC;IACjC,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;IACnC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACrC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAC,CAAC"}
package/docs/chatgpt.md CHANGED
@@ -70,6 +70,17 @@ curl -X DELETE http://localhost:8787/memories/<id> \
70
70
  -H "x-api-key: $REMEMBRA_API_KEY"
71
71
  ```
72
72
 
73
+ ### Digest a session (LLM extraction)
74
+
75
+ ```bash
76
+ curl -X POST http://localhost:8787/memories/digest \
77
+ -H "content-type: application/json" \
78
+ -H "x-api-key: $REMEMBRA_API_KEY" \
79
+ -d '{"transcript":"<conversation text>","scope":"chatgpt"}'
80
+ ```
81
+
82
+ Requires `REMEMBRA_LLM` + key — see [providers.md](providers.md).
83
+
73
84
  ## 3. Create the Custom GPT
74
85
 
75
86
  1. Go to **chatgpt.com → Explore GPTs → Create a GPT**.
package/docs/clients.md CHANGED
@@ -86,6 +86,12 @@ REMEMBRA_API_KEY="your-secret" remembra --http
86
86
  | `REMEMBRA_HOME` | `~/.remembra` | Where memory files live |
87
87
  | `REMEMBRA_API_KEY` | *(unset)* | Enables auth on the HTTP API |
88
88
  | `REMEMBRA_PORT` | `8787` | HTTP API port (`--port` overrides) |
89
+ | `REMEMBRA_LLM` | `openai` | Digest LLM: `openai` \| `anthropic` \| `ollama` |
90
+ | `REMEMBRA_EMBEDDINGS` | `none` | Semantic search: `openai` \| `ollama` \| `none` |
91
+ | `REMEMBRA_ARCHIVE_AFTER_DAYS` | `90` | Unused active memory → archived |
92
+ | `REMEMBRA_ARCHIVE_TTL_DAYS` | `365` | Archived memory → deleted |
93
+
94
+ LLM/embedding key setup: see **[providers.md](providers.md)**.
89
95
 
90
96
  ## Tips
91
97
 
@@ -0,0 +1,94 @@
1
+ # Memory Lifecycle & Maintenance (v3)
2
+
3
+ Remembra memories age. Instead of the store growing forever and stale facts
4
+ competing with current ones, memories move through a lifecycle — and nothing
5
+ active is ever auto-deleted.
6
+
7
+ ## The lifecycle
8
+
9
+ ```
10
+ active ──(unused 90d)──► archived ──(365d past archive)──► deleted
11
+ ▲ │
12
+ └────── revive ◄───────────┘ (exact duplicate re-digest / re-store)
13
+ ```
14
+
15
+ | Stage | Trigger | Effect |
16
+ |-------|---------|--------|
17
+ | **Active** | normal storage | fully searchable |
18
+ | **Downrank** | age (recency score decays naturally) | older memories rank lower |
19
+ | **Archived** | unused for `REMEMBRA_ARCHIVE_AFTER_DAYS` (default **90**) | moved to `archived/` — out of search, still visible via `memory_list {includeArchived: true}` |
20
+ | **Deleted** | `REMEMBRA_ARCHIVE_TTL_DAYS` (default **365**) after archiving | file removed permanently |
21
+
22
+ ### Rules
23
+
24
+ - **Search hits refresh the clock** — a memory that surfaces in results gets its
25
+ `lastSeen` bumped (throttled to once/hour), pushing its archive date out.
26
+ Used memories stay alive; forgotten ones fade.
27
+ - **Roles never decay** — standing instructions are excluded from archiving.
28
+ - **Only archived memories are ever auto-deleted** — an active memory must
29
+ survive the full 90 + 365 days of neglect first.
30
+ - **Revival is automatic** — digesting an exact duplicate of an archived memory
31
+ brings it back to active with a fresh clock.
32
+ - **Reversible until deleted** — archived files sit in plain markdown under
33
+ `~/.remembra/archived/`; move one back by hand or re-store it.
34
+
35
+ ## When maintenance runs
36
+
37
+ | Path | What runs | Cost |
38
+ |------|-----------|------|
39
+ | **On search** (piggyback, debounced 1/hour) | decay sweep + `lastSeen` refresh | free (file math) |
40
+ | **`memory_maintain` tool** / **`POST /maintain`** / **`remembra maintain`** CLI | decay sweep **+ embedding backfill** | backfill uses your embedding API |
41
+
42
+ Heavy/quotad work (embedding backfill) is always explicit; decay sweeps
43
+ opportunistically because they cost nothing.
44
+
45
+ ```bash
46
+ # CLI one-shot (prints JSON, exits)
47
+ remembra maintain
48
+ ```
49
+
50
+ ```json
51
+ { "archived": ["a1b2c3d4"], "deleted": ["e5f6a7b8"], "embedded": 12 }
52
+ ```
53
+
54
+ ## Contradiction merging
55
+
56
+ When a digest extracts something that *evolved* from a stored memory
57
+ ("API limit is 100 rpm" → "API limit is 500 rum"), an LLM decides per item:
58
+
59
+ | Decision | Behavior |
60
+ |----------|----------|
61
+ | `store` | unrelated/complementary — stored fresh alongside |
62
+ | `skip` | same fact, paraphrased — counted as duplicate, nothing written |
63
+ | `merge` | newer version of the same fact — stored memory **updated in place** |
64
+
65
+ Merged files preserve the old value:
66
+
67
+ ```markdown
68
+ API rate limit is 500 rpm
69
+
70
+ > superseded (2026-09-23): API rate limit is 100 rpm
71
+ ```
72
+
73
+ Candidate detection is cheap (keyword overlap or cosine similarity ≥ 0.4,
74
+ same type + scope) — the LLM is only called when two memories are plausibly
75
+ about the same thing. If the merge LLM fails, the item is **stored fresh**
76
+ (fail-open: extraction never loses data).
77
+
78
+ ## Configuration
79
+
80
+ | Variable | Default | Purpose |
81
+ |----------|---------|---------|
82
+ | `REMEMBRA_ARCHIVE_AFTER_DAYS` | `90` | Unused active memory → archive after this |
83
+ | `REMEMBRA_ARCHIVE_TTL_DAYS` | `365` | Archived memory deleted after this |
84
+
85
+ ## Storage layout
86
+
87
+ ```
88
+ ~/.remembra/
89
+ ├── global/<id>.md # active, global
90
+ ├── scopes/<scope>/<id>.md # active, project-scoped
91
+ └── archived/
92
+ ├── global/<id>.md # archived (excluded from search)
93
+ └── scopes/<scope>/<id>.md
94
+ ```
@@ -0,0 +1,107 @@
1
+ # AI Providers & Session Digest (v2)
2
+
3
+ Remembra can call an LLM to extract memories automatically (**session digest**)
4
+ and use embeddings for semantic search. Both are pluggable and off-by-default
5
+ where possible, so a plain keyword install still works with zero API keys.
6
+
7
+ ## Configuration
8
+
9
+ | Variable | Values | Default | Purpose |
10
+ |----------|--------|---------|---------|
11
+ | `REMEMBRA_LLM` | `openai` \| `anthropic` \| `ollama` | `openai` | Which LLM does digest extraction |
12
+ | `REMEMBRA_LLM_MODEL` | provider model id | provider default | Override the extraction model |
13
+ | `REMEMBRA_EMBEDDINGS` | `openai` \| `ollama` \| `none` | `none` | Semantic search provider |
14
+ | `REMEMBRA_EMBEDDING_MODEL` | provider model id | provider default | Override the embedding model |
15
+ | `OPENAI_API_KEY` | — | — | Required when provider = openai |
16
+ | `ANTHROPIC_API_KEY` | — | — | Required when provider = anthropic |
17
+ | `OLLAMA_HOST` | URL | `http://localhost:11434` | Ollama endpoint (LLM and/or embeddings) |
18
+
19
+ ### Examples
20
+
21
+ ```bash
22
+ # OpenAI for both (simplest hosted setup)
23
+ export OPENAI_API_KEY=sk-...
24
+ export REMEMBRA_LLM=openai
25
+ export REMEMBRA_EMBEDDINGS=openai
26
+
27
+ # Fully local with Ollama — no API keys
28
+ export REMEMBRA_LLM=ollama
29
+ export REMEMBRA_LLM_MODEL=llama3.2
30
+ export REMEMBRA_EMBEDDINGS=ollama
31
+ export REMEMBRA_EMBEDDING_MODEL=nomic-embed-text
32
+
33
+ # Anthropic for extraction, semantic search off (keyword mode)
34
+ export ANTHROPIC_API_KEY=sk-ant-...
35
+ export REMEMBRA_LLM=anthropic
36
+ ```
37
+
38
+ ## Session digest
39
+
40
+ Instead of the model remembering to call `memory_store` for every little thing,
41
+ hand the whole conversation to one tool at the end of a session:
42
+
43
+ ```
44
+ memory_digest {
45
+ transcript: "<full transcript or a detailed summary>",
46
+ scope: "/path/to/project", // optional, default global
47
+ source: "opencode" // optional
48
+ }
49
+ ```
50
+
51
+ Remembra asks the configured LLM to extract **facts, decisions, roles, and
52
+ history**, then stores each one — **skipping exact duplicates** that are
53
+ already present (normalized by type + scope + content). Running a digest twice
54
+ over the same conversation is a no-op.
55
+
56
+ HTTP equivalent:
57
+
58
+ ```bash
59
+ curl -X POST http://localhost:8787/memories/digest \
60
+ -H "content-type: application/json" \
61
+ -H "x-api-key: $REMEMBRA_API_KEY" \
62
+ -d '{"transcript":"...","scope":"chatgpt","source":"chatgpt"}'
63
+ ```
64
+
65
+ Response:
66
+
67
+ ```json
68
+ {
69
+ "extracted": 4,
70
+ "stored": [ ... ],
71
+ "skippedDuplicates": 2,
72
+ "ids": ["a1b2c3d4", "..."]
73
+ }
74
+ ```
75
+
76
+ > **Note:** the digest LLM key is only needed when you actually call
77
+ > `memory_digest` — storage and search work without it.
78
+
79
+ ## Semantic search
80
+
81
+ With `REMEMBRA_EMBEDDINGS=openai|ollama`:
82
+
83
+ - **On write**: every stored memory gets an embedding, cached in its
84
+ frontmatter (`embedding: [...]`) — computed once, never re-embedded.
85
+ - **On search**: the query is embedded and **cosine similarity becomes the
86
+ primary ranking signal**. Importance and recency remain small modifiers.
87
+ - **Gates stay absolute**: `role` memories always surface, and memories from
88
+ other scopes are never returned, no matter how similar.
89
+ - **Memories without vectors** (stored while embeddings were off) fall back
90
+ to keyword matching.
91
+
92
+ With `REMEMBRA_EMBEDDINGS=none` (default): pure keyword scoring — exactly
93
+ the v1 behavior.
94
+
95
+ ### Backfilling vectors
96
+
97
+ Memories stored before you enabled embeddings have no vectors. They still
98
+ work (keyword fallback), but to bring them into semantic search, re-store
99
+ them or wait for v3's maintenance commands.
100
+
101
+ ## Failure behavior
102
+
103
+ - **Embedding API fails** → warning logged, write continues without a vector,
104
+ search degrades to keywords. Never blocks storing.
105
+ - **LLM call fails** → `memory_digest` returns the error; nothing is stored.
106
+ - **No keys configured** → MCP/HTTP servers run normally; only `memory_digest`
107
+ errors if invoked.
package/docs/tools.md CHANGED
@@ -64,12 +64,37 @@ Permanently delete a memory.
64
64
 
65
65
  Returns an error result if no memory matches the id.
66
66
 
67
+ ## `memory_digest`
68
+
69
+ Extract memories from a conversation transcript using the configured LLM and
70
+ store them, skipping exact duplicates. See [providers.md](providers.md).
71
+
72
+ | Argument | Type | Required | Description |
73
+ |----------|------|----------|-------------|
74
+ | `transcript` | string | ✅ | Full transcript or a detailed session summary |
75
+ | `scope` | string | no | Scope for extracted memories (default `global`) |
76
+ | `source` | string | no | Originating session/client |
77
+
78
+ Requires `REMEMBRA_LLM` + its API key (or Ollama). Returns counts:
79
+ extracted / stored / duplicates skipped, plus stored ids.
80
+
81
+ ## `memory_maintain`
82
+
83
+ Run maintenance on demand: archive memories unused past
84
+ `REMEMBRA_ARCHIVE_AFTER_DAYS` (default 90), auto-delete archived memories past
85
+ `REMEMBRA_ARCHIVE_TTL_DAYS` (default 365), and backfill missing embedding
86
+ vectors. Roles never decay. Takes no arguments. See [lifecycle.md](lifecycle.md).
87
+
88
+ Returns counts + affected ids. Also available as `POST /maintain` and the
89
+ `remembra maintain` CLI command.
90
+
67
91
  ---
68
92
 
69
93
  ## Suggested session flow
70
94
 
71
95
  ```
72
- 1. memory_search { scope: <current project> } → recover roles, facts, decisions
96
+ 1. memory_search { scope: <current project> } → recover roles, facts, decisions
73
97
  2. ... work happens; model calls memory_store when something worth keeping emerges ...
74
- 3. (v2) automatic session digest sweeps anything missed
98
+ 3. memory_digest { transcript, scope } → end-of-session sweep (v2, LLM extracts)
99
+ (decay + backfill run opportunistically on search; memory_maintain when explicit)
75
100
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hilbras/remembra",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "External memory for AI assistants — remember facts, decisions, roles and history across sessions. MCP server for OpenCode, Claude Code, Cline, Kimi Code and more.",
5
5
  "type": "module",
6
6
  "bin": {