@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/CHANGELOG.md +41 -0
- package/README.md +9 -3
- package/dist/embeddings.d.ts +12 -0
- package/dist/embeddings.js +65 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/http.js +18 -0
- package/dist/http.js.map +1 -1
- package/dist/index.js +42 -2
- package/dist/index.js.map +1 -1
- package/dist/llm.d.ts +33 -0
- package/dist/llm.js +168 -0
- package/dist/llm.js.map +1 -0
- package/dist/retrieval.d.ts +11 -6
- package/dist/retrieval.js +57 -25
- package/dist/retrieval.js.map +1 -1
- package/dist/service.d.ts +76 -5
- package/dist/service.js +225 -5
- package/dist/service.js.map +1 -1
- package/dist/store.d.ts +18 -8
- package/dist/store.js +83 -17
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.js.map +1 -1
- package/docs/chatgpt.md +11 -0
- package/docs/clients.md +6 -0
- package/docs/lifecycle.md +94 -0
- package/docs/providers.md +107 -0
- package/docs/tools.md +27 -2
- package/package.json +1 -1
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
|
|
13
|
-
*
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
56
|
-
async all() {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
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":"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
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
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;
|
|
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> }
|
|
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.
|
|
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.
|
|
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": {
|