@mono-agent/memory 0.4.1
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/README.md +61 -0
- package/dist/bujo/capture.d.ts +20 -0
- package/dist/bujo/capture.d.ts.map +1 -0
- package/dist/bujo/capture.js +77 -0
- package/dist/bujo/capture.js.map +1 -0
- package/dist/bujo/cli-env.d.ts +11 -0
- package/dist/bujo/cli-env.d.ts.map +1 -0
- package/dist/bujo/cli-env.js +30 -0
- package/dist/bujo/cli-env.js.map +1 -0
- package/dist/bujo/cli.d.ts +3 -0
- package/dist/bujo/cli.d.ts.map +1 -0
- package/dist/bujo/cli.js +98 -0
- package/dist/bujo/cli.js.map +1 -0
- package/dist/bujo/consolidate.d.ts +15 -0
- package/dist/bujo/consolidate.d.ts.map +1 -0
- package/dist/bujo/consolidate.js +61 -0
- package/dist/bujo/consolidate.js.map +1 -0
- package/dist/bujo/daily.d.ts +17 -0
- package/dist/bujo/daily.d.ts.map +1 -0
- package/dist/bujo/daily.js +47 -0
- package/dist/bujo/daily.js.map +1 -0
- package/dist/bujo/distill.d.ts +10 -0
- package/dist/bujo/distill.d.ts.map +1 -0
- package/dist/bujo/distill.js +50 -0
- package/dist/bujo/distill.js.map +1 -0
- package/dist/bujo/entities.d.ts +24 -0
- package/dist/bujo/entities.d.ts.map +1 -0
- package/dist/bujo/entities.js +80 -0
- package/dist/bujo/entities.js.map +1 -0
- package/dist/bujo/grammar.d.ts +18 -0
- package/dist/bujo/grammar.d.ts.map +1 -0
- package/dist/bujo/grammar.js +147 -0
- package/dist/bujo/grammar.js.map +1 -0
- package/dist/bujo/graph.d.ts +17 -0
- package/dist/bujo/graph.d.ts.map +1 -0
- package/dist/bujo/graph.js +96 -0
- package/dist/bujo/graph.js.map +1 -0
- package/dist/bujo/ids.d.ts +9 -0
- package/dist/bujo/ids.d.ts.map +1 -0
- package/dist/bujo/ids.js +23 -0
- package/dist/bujo/ids.js.map +1 -0
- package/dist/bujo/index.d.ts +26 -0
- package/dist/bujo/index.d.ts.map +1 -0
- package/dist/bujo/index.js +20 -0
- package/dist/bujo/index.js.map +1 -0
- package/dist/bujo/json.d.ts +3 -0
- package/dist/bujo/json.d.ts.map +1 -0
- package/dist/bujo/json.js +58 -0
- package/dist/bujo/json.js.map +1 -0
- package/dist/bujo/llm.d.ts +14 -0
- package/dist/bujo/llm.d.ts.map +1 -0
- package/dist/bujo/llm.js +2 -0
- package/dist/bujo/llm.js.map +1 -0
- package/dist/bujo/migrate.d.ts +12 -0
- package/dist/bujo/migrate.d.ts.map +1 -0
- package/dist/bujo/migrate.js +136 -0
- package/dist/bujo/migrate.js.map +1 -0
- package/dist/bujo/model-error.d.ts +16 -0
- package/dist/bujo/model-error.d.ts.map +1 -0
- package/dist/bujo/model-error.js +20 -0
- package/dist/bujo/model-error.js.map +1 -0
- package/dist/bujo/ollama-llm.d.ts +7 -0
- package/dist/bujo/ollama-llm.d.ts.map +1 -0
- package/dist/bujo/ollama-llm.js +43 -0
- package/dist/bujo/ollama-llm.js.map +1 -0
- package/dist/bujo/projections.d.ts +8 -0
- package/dist/bujo/projections.d.ts.map +1 -0
- package/dist/bujo/projections.js +53 -0
- package/dist/bujo/projections.js.map +1 -0
- package/dist/bujo/rebuild.d.ts +12 -0
- package/dist/bujo/rebuild.d.ts.map +1 -0
- package/dist/bujo/rebuild.js +73 -0
- package/dist/bujo/rebuild.js.map +1 -0
- package/dist/bujo/recall.d.ts +7 -0
- package/dist/bujo/recall.d.ts.map +1 -0
- package/dist/bujo/recall.js +33 -0
- package/dist/bujo/recall.js.map +1 -0
- package/dist/bujo/reconcile.d.ts +37 -0
- package/dist/bujo/reconcile.d.ts.map +1 -0
- package/dist/bujo/reconcile.js +190 -0
- package/dist/bujo/reconcile.js.map +1 -0
- package/dist/bujo/reflect.d.ts +30 -0
- package/dist/bujo/reflect.d.ts.map +1 -0
- package/dist/bujo/reflect.js +100 -0
- package/dist/bujo/reflect.js.map +1 -0
- package/dist/bujo/store.d.ts +77 -0
- package/dist/bujo/store.d.ts.map +1 -0
- package/dist/bujo/store.js +222 -0
- package/dist/bujo/store.js.map +1 -0
- package/dist/bujo/types.d.ts +39 -0
- package/dist/bujo/types.d.ts.map +1 -0
- package/dist/bujo/types.js +2 -0
- package/dist/bujo/types.js.map +1 -0
- package/dist/search/chunks.d.ts +14 -0
- package/dist/search/chunks.d.ts.map +1 -0
- package/dist/search/chunks.js +75 -0
- package/dist/search/chunks.js.map +1 -0
- package/dist/search/circuit-breaker.d.ts +34 -0
- package/dist/search/circuit-breaker.d.ts.map +1 -0
- package/dist/search/circuit-breaker.js +89 -0
- package/dist/search/circuit-breaker.js.map +1 -0
- package/dist/search/embeddings.d.ts +44 -0
- package/dist/search/embeddings.d.ts.map +1 -0
- package/dist/search/embeddings.js +141 -0
- package/dist/search/embeddings.js.map +1 -0
- package/dist/search/index.d.ts +8 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +5 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/types.d.ts +41 -0
- package/dist/search/types.d.ts.map +1 -0
- package/dist/search/types.js +2 -0
- package/dist/search/types.js.map +1 -0
- package/dist/search/vector-index.d.ts +28 -0
- package/dist/search/vector-index.d.ts.map +1 -0
- package/dist/search/vector-index.js +214 -0
- package/dist/search/vector-index.js.map +1 -0
- package/dist/store/contract.d.ts +2 -0
- package/dist/store/contract.d.ts.map +1 -0
- package/dist/store/contract.js +2 -0
- package/dist/store/contract.js.map +1 -0
- package/dist/store/db.d.ts +84 -0
- package/dist/store/db.d.ts.map +1 -0
- package/dist/store/db.js +471 -0
- package/dist/store/db.js.map +1 -0
- package/dist/store/fts.d.ts +3 -0
- package/dist/store/fts.d.ts.map +1 -0
- package/dist/store/fts.js +6 -0
- package/dist/store/fts.js.map +1 -0
- package/dist/store/index.d.ts +5 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +3 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/ranking.d.ts +16 -0
- package/dist/store/ranking.d.ts.map +1 -0
- package/dist/store/ranking.js +32 -0
- package/dist/store/ranking.js.map +1 -0
- package/dist/store/schema.d.ts +3 -0
- package/dist/store/schema.d.ts.map +1 -0
- package/dist/store/schema.js +61 -0
- package/dist/store/schema.js.map +1 -0
- package/dist/store/types.d.ts +102 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/store/types.js +25 -0
- package/dist/store/types.js.map +1 -0
- package/dist/store/vec.d.ts +12 -0
- package/dist/store/vec.d.ts.map +1 -0
- package/dist/store/vec.js +20 -0
- package/dist/store/vec.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { relative } from "node:path";
|
|
2
|
+
import { appendBullet, dailyFilePath, rewriteBullet } from "./daily.js";
|
|
3
|
+
import { parseJsonLoose } from "./json.js";
|
|
4
|
+
import { MemoryModelError } from "./model-error.js";
|
|
5
|
+
const VALID_ACTIONS = new Set(["add", "update", "supersede", "noop"]);
|
|
6
|
+
/**
|
|
7
|
+
* Reconcile distilled candidates against the existing memory index, writing to BOTH the
|
|
8
|
+
* canonical markdown daily files and the SQLite index. Each candidate is handled independently
|
|
9
|
+
* (an LLM/IO failure on one does not abort the others). The LLM is consulted only for candidates
|
|
10
|
+
* that are close to an existing memory; clearly-novel candidates are added without an LLM call.
|
|
11
|
+
*/
|
|
12
|
+
export async function reconcile(candidates, deps) {
|
|
13
|
+
const threadThreshold = deps.threadThreshold ?? 0.35;
|
|
14
|
+
const dupThreshold = deps.dupThreshold ?? 0.5;
|
|
15
|
+
const actions = [];
|
|
16
|
+
for (const candidate of candidates) {
|
|
17
|
+
try {
|
|
18
|
+
// findSimilar embeds the query, so a down embedding model throws here for EVERY candidate —
|
|
19
|
+
// a systemic outage, not a per-item data problem. Tag it so the catch below surfaces it.
|
|
20
|
+
let similar;
|
|
21
|
+
try {
|
|
22
|
+
similar = await deps.db.findSimilar(candidate.text, 5);
|
|
23
|
+
}
|
|
24
|
+
catch (cause) {
|
|
25
|
+
throw new MemoryModelError("embedding", "findSimilar", cause);
|
|
26
|
+
}
|
|
27
|
+
// Clearly novel (nothing close enough) → ADD outright, no LLM.
|
|
28
|
+
if (similar.length === 0 || (similar[0]?.distance ?? Infinity) > dupThreshold) {
|
|
29
|
+
actions.push(await add(candidate, similar, deps, threadThreshold));
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const decision = await classify(candidate, similar, deps);
|
|
33
|
+
actions.push(await apply(candidate, decision, similar, deps, threadThreshold));
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
// A model outage (embedding or classify LLM) is systemic and must surface — every candidate
|
|
37
|
+
// would hit it, so swallowing it would make a dead model look like a no-op capture.
|
|
38
|
+
if (err instanceof MemoryModelError)
|
|
39
|
+
throw err;
|
|
40
|
+
// Per-candidate isolation: a genuine per-item *data* failure (e.g. a missing daily file during
|
|
41
|
+
// UPDATE/SUPERSEDE) must not abort the rest of the batch. Skip it.
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return actions;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Ask the LLM to classify the candidate against its nearest neighbours. A malformed *reply* is
|
|
49
|
+
* tolerated (→ undefined → caller falls back to ADD), but a model *failure* is rethrown as a
|
|
50
|
+
* {@link MemoryModelError} so a dead model surfaces instead of silently degrading to ADD.
|
|
51
|
+
*/
|
|
52
|
+
async function classify(candidate, similar, deps) {
|
|
53
|
+
let raw;
|
|
54
|
+
try {
|
|
55
|
+
raw = await deps.llm.complete(classifyPrompt(candidate, similar), { label: "capture:reconcile" });
|
|
56
|
+
}
|
|
57
|
+
catch (cause) {
|
|
58
|
+
throw new MemoryModelError("llm", "classify", cause);
|
|
59
|
+
}
|
|
60
|
+
const parsed = parseJsonLoose(raw);
|
|
61
|
+
if (parsed === undefined || typeof parsed !== "object")
|
|
62
|
+
return undefined;
|
|
63
|
+
const action = typeof parsed.action === "string" ? parsed.action : "";
|
|
64
|
+
if (!VALID_ACTIONS.has(action))
|
|
65
|
+
return undefined;
|
|
66
|
+
const targetId = typeof parsed.targetId === "string" ? parsed.targetId : undefined;
|
|
67
|
+
// The target must be one of the neighbours we offered the LLM. "add" needs no target.
|
|
68
|
+
if (action !== "add") {
|
|
69
|
+
if (targetId === undefined || !similar.some((h) => h.record.id === targetId))
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
action,
|
|
74
|
+
...(targetId !== undefined && { targetId }),
|
|
75
|
+
...(typeof parsed.text === "string" && { text: parsed.text }),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const classifyPrompt = (candidate, similar) => {
|
|
79
|
+
const neighbours = similar
|
|
80
|
+
.map((h) => `- id=${h.record.id} distance=${h.distance.toFixed(3)} text="${h.record.text}"`)
|
|
81
|
+
.join("\n");
|
|
82
|
+
return `CLASSIFY a new candidate memory against existing memories. Decide whether it is novel,
|
|
83
|
+
a duplicate, a refinement, or a contradiction. Return ONLY JSON:
|
|
84
|
+
{"action":"add|update|supersede|noop","targetId":"<existing id, omit for add>","text":"<merged/new text for update|supersede>"}.
|
|
85
|
+
- add: genuinely new information.
|
|
86
|
+
- noop: an exact duplicate of an existing memory (no change needed).
|
|
87
|
+
- update: refines/merges an existing memory; set targetId and text to the merged sentence.
|
|
88
|
+
- supersede: contradicts/replaces an existing memory; set targetId and text to the new sentence.
|
|
89
|
+
|
|
90
|
+
CANDIDATE: type=${candidate.type} text="${candidate.text}"
|
|
91
|
+
|
|
92
|
+
EXISTING:
|
|
93
|
+
${neighbours}`;
|
|
94
|
+
};
|
|
95
|
+
/** Dispatch a parsed classification (or fall back to ADD when it is missing/invalid). */
|
|
96
|
+
async function apply(candidate, decision, similar, deps, threadThreshold) {
|
|
97
|
+
if (decision === undefined)
|
|
98
|
+
return add(candidate, similar, deps, threadThreshold);
|
|
99
|
+
switch (decision.action) {
|
|
100
|
+
case "noop":
|
|
101
|
+
// targetId is guaranteed present for non-add by classify().
|
|
102
|
+
return { kind: "noop", id: decision.targetId ?? "" };
|
|
103
|
+
case "update":
|
|
104
|
+
return update(candidate, decision, similar, deps, threadThreshold);
|
|
105
|
+
case "supersede":
|
|
106
|
+
return supersede(candidate, decision, similar, deps, threadThreshold);
|
|
107
|
+
default:
|
|
108
|
+
return add(candidate, similar, deps, threadThreshold);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** ADD: append a new bullet, index it, and thread edges to near neighbours. */
|
|
112
|
+
async function add(candidate, similar, deps, threadThreshold) {
|
|
113
|
+
const now = deps.now();
|
|
114
|
+
const id = deps.nextId();
|
|
115
|
+
const bullet = {
|
|
116
|
+
id,
|
|
117
|
+
type: candidate.type,
|
|
118
|
+
status: "open",
|
|
119
|
+
text: candidate.text,
|
|
120
|
+
salience: candidate.salience,
|
|
121
|
+
isInsight: candidate.isInsight,
|
|
122
|
+
createdAt: now.toISOString(),
|
|
123
|
+
refs: [],
|
|
124
|
+
};
|
|
125
|
+
appendBullet(deps.root, bullet, now);
|
|
126
|
+
await deps.db.upsert(recordFor(bullet, deps.root, now));
|
|
127
|
+
for (const hit of similar) {
|
|
128
|
+
if (hit.distance <= threadThreshold) {
|
|
129
|
+
deps.db.addEdge(id, hit.record.id, "thread", 1 - hit.distance);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { kind: "add", id };
|
|
133
|
+
}
|
|
134
|
+
/** UPDATE: merge text into an existing memory (markdown + re-embedded index), keeping its id. */
|
|
135
|
+
async function update(candidate, decision, similar, deps, threadThreshold) {
|
|
136
|
+
const targetId = decision.targetId ?? "";
|
|
137
|
+
const target = deps.db.get(targetId);
|
|
138
|
+
if (target === undefined || target.source.file === undefined) {
|
|
139
|
+
return add(candidate, similar, deps, threadThreshold);
|
|
140
|
+
}
|
|
141
|
+
const mergedText = decision.text ?? candidate.text;
|
|
142
|
+
rewriteBullet(deps.root, target.source.file, targetId, { text: mergedText });
|
|
143
|
+
await deps.db.upsert({ ...target, text: mergedText });
|
|
144
|
+
return { kind: "update", id: targetId };
|
|
145
|
+
}
|
|
146
|
+
/** SUPERSEDE: invalidate the old memory (markdown + index) and add the new one in its place. */
|
|
147
|
+
async function supersede(candidate, decision, similar, deps, threadThreshold) {
|
|
148
|
+
const targetId = decision.targetId ?? "";
|
|
149
|
+
const old = deps.db.get(targetId);
|
|
150
|
+
if (old === undefined) {
|
|
151
|
+
return add(candidate, similar, deps, threadThreshold);
|
|
152
|
+
}
|
|
153
|
+
const now = deps.now();
|
|
154
|
+
const id = deps.nextId();
|
|
155
|
+
const newText = decision.text ?? candidate.text;
|
|
156
|
+
const bullet = {
|
|
157
|
+
id,
|
|
158
|
+
type: candidate.type,
|
|
159
|
+
status: "open",
|
|
160
|
+
text: newText,
|
|
161
|
+
salience: candidate.salience,
|
|
162
|
+
isInsight: candidate.isInsight,
|
|
163
|
+
createdAt: now.toISOString(),
|
|
164
|
+
refs: [],
|
|
165
|
+
};
|
|
166
|
+
// Canonical-markdown first (strike old, append new), then mirror to the index. If the index step
|
|
167
|
+
// throws, a rebuild from markdown still reproduces the correct state (old invalidated, new present).
|
|
168
|
+
if (old.source.file !== undefined) {
|
|
169
|
+
rewriteBullet(deps.root, old.source.file, targetId, { status: "invalidated" });
|
|
170
|
+
}
|
|
171
|
+
appendBullet(deps.root, bullet, now);
|
|
172
|
+
await deps.db.supersede(targetId, recordFor(bullet, deps.root, now));
|
|
173
|
+
return { kind: "supersede", oldId: targetId, newId: id };
|
|
174
|
+
}
|
|
175
|
+
/** Build an index record mirroring a freshly-appended bullet (source.file is the daily file, relative to root). */
|
|
176
|
+
function recordFor(bullet, root, now) {
|
|
177
|
+
return {
|
|
178
|
+
id: bullet.id,
|
|
179
|
+
type: bullet.type,
|
|
180
|
+
status: bullet.status,
|
|
181
|
+
text: bullet.text,
|
|
182
|
+
salience: bullet.salience,
|
|
183
|
+
isInsight: bullet.isInsight,
|
|
184
|
+
createdAt: bullet.createdAt,
|
|
185
|
+
accessCount: 0,
|
|
186
|
+
tags: [],
|
|
187
|
+
source: { file: relative(root, dailyFilePath(root, now)) },
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=reconcile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../src/bujo/reconcile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { MemoryDb } from "../store/index.js";
|
|
2
|
+
import type { LlmComplete } from "./llm.js";
|
|
3
|
+
export interface ReflectDeps {
|
|
4
|
+
readonly db: MemoryDb;
|
|
5
|
+
readonly root: string;
|
|
6
|
+
readonly llm: LlmComplete;
|
|
7
|
+
readonly nextId: () => string;
|
|
8
|
+
readonly now: () => Date;
|
|
9
|
+
readonly halfLifeDays?: number;
|
|
10
|
+
readonly floor?: number;
|
|
11
|
+
readonly maxInsights?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ReflectResult {
|
|
14
|
+
readonly decayed: number;
|
|
15
|
+
readonly insights: number;
|
|
16
|
+
readonly due: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function reflect(deps: ReflectDeps): Promise<ReflectResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Synthesize higher-level insights from the top-salient non-insight memories.
|
|
21
|
+
* Asks the LLM for up to `maxInsights ?? 3` insights as JSON, appends each as a
|
|
22
|
+
* note bullet with isInsight=true, upserts the index record, and adds `supports`
|
|
23
|
+
* edges from the insight to valid source memory ids.
|
|
24
|
+
*
|
|
25
|
+
* A malformed/parse-failed *reply* returns 0, but a model *failure* is rethrown as a
|
|
26
|
+
* {@link MemoryModelError} so a dead model surfaces (the ritual scheduler logs it) instead of
|
|
27
|
+
* looking like a reflection that simply found no insights.
|
|
28
|
+
*/
|
|
29
|
+
export declare function synthesizeInsights(deps: ReflectDeps, now: Date): Promise<number>;
|
|
30
|
+
//# sourceMappingURL=reflect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reflect.d.ts","sourceRoot":"","sources":["../../src/bujo/reflect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAgB,MAAM,mBAAmB,CAAC;AAIhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAI5C,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,IAAI,CAAC;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CASvE;AAuBD;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAqDtF"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { relative } from "node:path";
|
|
2
|
+
import { appendBullet, dailyFilePath } from "./daily.js";
|
|
3
|
+
import { parseJsonLoose } from "./json.js";
|
|
4
|
+
import { MemoryModelError } from "./model-error.js";
|
|
5
|
+
export async function reflect(deps) {
|
|
6
|
+
const now = deps.now();
|
|
7
|
+
const { decayed } = deps.db.applyDecay(now, {
|
|
8
|
+
...(deps.halfLifeDays !== undefined && { halfLifeDays: deps.halfLifeDays }),
|
|
9
|
+
...(deps.floor !== undefined && { floor: deps.floor }),
|
|
10
|
+
});
|
|
11
|
+
const insights = await synthesizeInsights(deps, now);
|
|
12
|
+
const due = deps.db.dueItems(now).length;
|
|
13
|
+
return { decayed, insights, due };
|
|
14
|
+
}
|
|
15
|
+
/** Build an index record for a freshly-appended insight bullet (source.file relative to root). */
|
|
16
|
+
function insightRecordFor(bullet, root, now) {
|
|
17
|
+
return {
|
|
18
|
+
id: bullet.id,
|
|
19
|
+
type: bullet.type,
|
|
20
|
+
status: bullet.status,
|
|
21
|
+
text: bullet.text,
|
|
22
|
+
salience: bullet.salience,
|
|
23
|
+
isInsight: bullet.isInsight,
|
|
24
|
+
createdAt: bullet.createdAt,
|
|
25
|
+
accessCount: 0,
|
|
26
|
+
tags: [],
|
|
27
|
+
source: { file: relative(root, dailyFilePath(root, now)) },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Synthesize higher-level insights from the top-salient non-insight memories.
|
|
32
|
+
* Asks the LLM for up to `maxInsights ?? 3` insights as JSON, appends each as a
|
|
33
|
+
* note bullet with isInsight=true, upserts the index record, and adds `supports`
|
|
34
|
+
* edges from the insight to valid source memory ids.
|
|
35
|
+
*
|
|
36
|
+
* A malformed/parse-failed *reply* returns 0, but a model *failure* is rethrown as a
|
|
37
|
+
* {@link MemoryModelError} so a dead model surfaces (the ritual scheduler logs it) instead of
|
|
38
|
+
* looking like a reflection that simply found no insights.
|
|
39
|
+
*/
|
|
40
|
+
export async function synthesizeInsights(deps, now) {
|
|
41
|
+
const candidates = deps.db.topSalient(20).filter((m) => !m.isInsight);
|
|
42
|
+
if (candidates.length < 3)
|
|
43
|
+
return 0;
|
|
44
|
+
const prompt = buildInsightPrompt(candidates, deps.maxInsights ?? 3);
|
|
45
|
+
let raw;
|
|
46
|
+
try {
|
|
47
|
+
raw = await deps.llm.complete(prompt, { label: "reflect" });
|
|
48
|
+
}
|
|
49
|
+
catch (cause) {
|
|
50
|
+
throw new MemoryModelError("llm", "insights", cause);
|
|
51
|
+
}
|
|
52
|
+
const parsed = parseJsonLoose(raw);
|
|
53
|
+
if (!Array.isArray(parsed))
|
|
54
|
+
return 0;
|
|
55
|
+
const candidateIds = new Set(candidates.map((m) => m.id));
|
|
56
|
+
const maxInsights = deps.maxInsights ?? 3;
|
|
57
|
+
let created = 0;
|
|
58
|
+
for (const item of parsed) {
|
|
59
|
+
if (created >= maxInsights)
|
|
60
|
+
break;
|
|
61
|
+
if (typeof item !== "object" || item === null)
|
|
62
|
+
continue;
|
|
63
|
+
const text = typeof item.text === "string" ? item.text.trim() : "";
|
|
64
|
+
if (text.length === 0)
|
|
65
|
+
continue;
|
|
66
|
+
const id = deps.nextId();
|
|
67
|
+
const bullet = {
|
|
68
|
+
id,
|
|
69
|
+
type: "note",
|
|
70
|
+
status: "open",
|
|
71
|
+
text,
|
|
72
|
+
salience: 0.7,
|
|
73
|
+
isInsight: true,
|
|
74
|
+
createdAt: now.toISOString(),
|
|
75
|
+
refs: [],
|
|
76
|
+
};
|
|
77
|
+
appendBullet(deps.root, bullet, now);
|
|
78
|
+
await deps.db.upsert(insightRecordFor(bullet, deps.root, now));
|
|
79
|
+
// Add "supports" edges from this insight to each referenced source that is in our candidate set.
|
|
80
|
+
const sourceIds = Array.isArray(item.sourceIds) ? item.sourceIds : [];
|
|
81
|
+
for (const sourceId of sourceIds) {
|
|
82
|
+
if (typeof sourceId === "string" && candidateIds.has(sourceId)) {
|
|
83
|
+
deps.db.addEdge(id, sourceId, "supports");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
created += 1;
|
|
87
|
+
}
|
|
88
|
+
return created;
|
|
89
|
+
}
|
|
90
|
+
function buildInsightPrompt(memories, maxInsights) {
|
|
91
|
+
const memoryList = memories.map((m) => `- id=${m.id} text="${m.text}"`).join("\n");
|
|
92
|
+
return `You are a reflective memory assistant. Given the following memories, synthesize up to ${maxInsights} higher-level insight(s) that reveal patterns, principles, or connections not obvious from any single memory.
|
|
93
|
+
|
|
94
|
+
Return ONLY a JSON array (no prose, no code fences):
|
|
95
|
+
[{"text":"<insight sentence>","sourceIds":["<id1>","<id2>"]}]
|
|
96
|
+
|
|
97
|
+
MEMORIES:
|
|
98
|
+
${memoryList}`;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=reflect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reflect.js","sourceRoot":"","sources":["../../src/bujo/reflect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { MemoryBlock, MemoryStore, MemoryWriteResult } from "@mono-agent/agent-contracts";
|
|
2
|
+
import type { RecallHit } from "../store/index.js";
|
|
3
|
+
import { type ReflectResult } from "./reflect.js";
|
|
4
|
+
import { type MigrateResult } from "./migrate.js";
|
|
5
|
+
import { type ConsolidateResult } from "./consolidate.js";
|
|
6
|
+
import type { BujoOptions, BujoTier } from "./types.js";
|
|
7
|
+
export declare class BujoMemoryStore implements MemoryStore {
|
|
8
|
+
private readonly root;
|
|
9
|
+
private readonly db;
|
|
10
|
+
private readonly maxBytes;
|
|
11
|
+
private readonly clock;
|
|
12
|
+
private readonly nextId;
|
|
13
|
+
private readonly llm?;
|
|
14
|
+
private readonly _tier;
|
|
15
|
+
private readonly logger;
|
|
16
|
+
private captureChain;
|
|
17
|
+
constructor(options: BujoOptions);
|
|
18
|
+
/** The effective tier of this store (lite / journal / bujo). */
|
|
19
|
+
tier(): BujoTier;
|
|
20
|
+
load(conversationId: string, query?: string): Promise<MemoryBlock | undefined>;
|
|
21
|
+
/** Query-based hybrid recall (text + score). Used by the MCP and any deliberate recall surface. */
|
|
22
|
+
recall(query: string, options?: {
|
|
23
|
+
topK?: number;
|
|
24
|
+
trackAccess?: boolean;
|
|
25
|
+
}): Promise<RecallHit[]>;
|
|
26
|
+
appendHostSummary(conversationId: string, summary: string): Promise<MemoryWriteResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Run the nightly reflection ritual: decay salience, synthesize insights from top memories,
|
|
29
|
+
* and surface due intentions. Also writes future-log.md + index.md.
|
|
30
|
+
*
|
|
31
|
+
* Returns `undefined` when no `llm` was configured (matches `capture()` pattern).
|
|
32
|
+
*/
|
|
33
|
+
reflect(): Promise<ReflectResult | undefined>;
|
|
34
|
+
/**
|
|
35
|
+
* Run the monthly BuJo migration ritual: review aging open memories and apply LLM decisions
|
|
36
|
+
* (promote / reschedule / cluster / forget). Also writes future-log.md.
|
|
37
|
+
*
|
|
38
|
+
* Returns `undefined` when no `llm` was configured (matches `capture()` pattern).
|
|
39
|
+
*/
|
|
40
|
+
migrate(): Promise<MigrateResult | undefined>;
|
|
41
|
+
/**
|
|
42
|
+
* Enqueue a best-effort intelligent capture. Returns immediately. Captures run strictly
|
|
43
|
+
* one-at-a-time (serialized across all channels sharing this store), and a failure is caught +
|
|
44
|
+
* logged so it never breaks the chain, the reply, or the process. No-op without an llm.
|
|
45
|
+
*/
|
|
46
|
+
scheduleCapture(conversationId: string, text: string): void;
|
|
47
|
+
/** Await all captures queued before this call (graceful shutdown / one-shot exit). */
|
|
48
|
+
flush(): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* LLM-backed intelligent capture: distills the turn text into atomic candidate memories,
|
|
51
|
+
* reconciles them against the existing index (ADD / UPDATE / SUPERSEDE / NOOP), and extracts
|
|
52
|
+
* typed entities into the canonical graph.
|
|
53
|
+
*
|
|
54
|
+
* Returns `undefined` when no `llm` was configured — use `appendHostSummary` as the always-on
|
|
55
|
+
* deterministic rapid-log (P1 path). `capture()` is the intelligent path invoked by P3
|
|
56
|
+
* reflection/cron and future session hooks; it is safe to call on every turn when an LLM is
|
|
57
|
+
* present, and a no-op when one is not.
|
|
58
|
+
*/
|
|
59
|
+
capture(conversationId: string, text: string): Promise<{
|
|
60
|
+
actions: number;
|
|
61
|
+
entities: number;
|
|
62
|
+
} | undefined>;
|
|
63
|
+
/**
|
|
64
|
+
* Apply salience decay to all memories in the store.
|
|
65
|
+
*
|
|
66
|
+
* Usable in all tiers (lite, journal, bujo) — no LLM required. In the `journal` and `bujo`
|
|
67
|
+
* tiers this is the primary maintenance call; in the `lite` tier it still runs harmlessly.
|
|
68
|
+
*/
|
|
69
|
+
decay(): Promise<{
|
|
70
|
+
decayed: number;
|
|
71
|
+
}>;
|
|
72
|
+
/** Run deterministic, no-LLM BuJo consolidation in every tier. */
|
|
73
|
+
consolidate(): Promise<ConsolidateResult>;
|
|
74
|
+
close(): Promise<void>;
|
|
75
|
+
}
|
|
76
|
+
export declare function createBujoMemoryStore(options: BujoOptions): BujoMemoryStore;
|
|
77
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/bujo/store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAC/F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AASnD,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAyB,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEjF,OAAO,KAAK,EAAsB,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAM5E,qBAAa,eAAgB,YAAW,WAAW;IACjD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAW;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAW;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IACpC,OAAO,CAAC,YAAY,CAAoC;gBAE5C,OAAO,EAAE,WAAW;IAwBhC,gEAAgE;IAChE,IAAI,IAAI,QAAQ;IAIV,IAAI,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAiBpF,mGAAmG;IAC7F,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAOnG,iBAAiB,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAkC5F;;;;;OAKG;IACG,OAAO,IAAI,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAcnD;;;;;OAKG;IACG,OAAO,IAAI,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAanD;;;;OAIG;IACH,eAAe,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAe3D,sFAAsF;IAChF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;;;;;;;;OASG;IACG,OAAO,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAY/G;;;;;OAKG;IACG,KAAK,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAI3C,kEAAkE;IAC5D,WAAW,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAQzC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAM7B;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,WAAW,GAAG,eAAe,CAE3E"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { join, relative } from "node:path";
|
|
2
|
+
import { openMemoryDb } from "../store/index.js";
|
|
3
|
+
import { appendBullet, dailyFilePath } from "./daily.js";
|
|
4
|
+
import { serializeBullet } from "./grammar.js";
|
|
5
|
+
import { createIdFactory } from "./ids.js";
|
|
6
|
+
import { captureTurn } from "./capture.js";
|
|
7
|
+
import { composeRecallBlock } from "./recall.js";
|
|
8
|
+
import { reflect as reflectFn } from "./reflect.js";
|
|
9
|
+
import { migrate as migrateFn } from "./migrate.js";
|
|
10
|
+
import { consolidateBujoMemory } from "./consolidate.js";
|
|
11
|
+
import { writeFutureLog, writeIndex } from "./projections.js";
|
|
12
|
+
// Cap the recall query so an attachment turn's inlined document text (the user message can carry
|
|
13
|
+
// up to a few KB of extracted file content) cannot drown the FTS/embedding signal.
|
|
14
|
+
const MAX_RECALL_QUERY_CHARS = 4_000;
|
|
15
|
+
export class BujoMemoryStore {
|
|
16
|
+
root;
|
|
17
|
+
db;
|
|
18
|
+
maxBytes;
|
|
19
|
+
clock;
|
|
20
|
+
nextId;
|
|
21
|
+
llm;
|
|
22
|
+
_tier;
|
|
23
|
+
logger;
|
|
24
|
+
captureChain = Promise.resolve();
|
|
25
|
+
constructor(options) {
|
|
26
|
+
this.root = options.root;
|
|
27
|
+
this.maxBytes = options.maxBytes ?? 8_000;
|
|
28
|
+
this.clock = options.clock ?? (() => new Date());
|
|
29
|
+
this.nextId = createIdFactory({ clock: this.clock });
|
|
30
|
+
this.db = openMemoryDb({
|
|
31
|
+
path: join(options.root, "memory.db"),
|
|
32
|
+
...(options.embeddings !== undefined && { embeddings: options.embeddings }),
|
|
33
|
+
...(options.dim !== undefined && { dim: options.dim }),
|
|
34
|
+
clock: this.clock,
|
|
35
|
+
});
|
|
36
|
+
if (options.llm !== undefined) {
|
|
37
|
+
this.llm = options.llm;
|
|
38
|
+
}
|
|
39
|
+
this._tier =
|
|
40
|
+
options.tier ??
|
|
41
|
+
(options.embeddings === undefined
|
|
42
|
+
? "lite"
|
|
43
|
+
: options.llm === undefined
|
|
44
|
+
? "journal"
|
|
45
|
+
: "bujo");
|
|
46
|
+
this.logger = options.logger ?? { warn: () => { } };
|
|
47
|
+
}
|
|
48
|
+
/** The effective tier of this store (lite / journal / bujo). */
|
|
49
|
+
tier() {
|
|
50
|
+
return this._tier;
|
|
51
|
+
}
|
|
52
|
+
async load(conversationId, query) {
|
|
53
|
+
// Recall against what the user actually said. Legacy callers pass no query, so fall back to the
|
|
54
|
+
// conversation id as a coarse seed; an explicit empty query carries no usable signal, so skip
|
|
55
|
+
// recall rather than surface near-random hits.
|
|
56
|
+
let recallQuery;
|
|
57
|
+
if (query === undefined) {
|
|
58
|
+
recallQuery = conversationId;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const trimmed = query.trim();
|
|
62
|
+
if (trimmed.length === 0) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
recallQuery = trimmed.slice(0, MAX_RECALL_QUERY_CHARS);
|
|
66
|
+
}
|
|
67
|
+
return composeRecallBlock(this.db, recallQuery, { topK: 8, maxBytes: this.maxBytes });
|
|
68
|
+
}
|
|
69
|
+
/** Query-based hybrid recall (text + score). Used by the MCP and any deliberate recall surface. */
|
|
70
|
+
async recall(query, options = {}) {
|
|
71
|
+
return this.db.recall(query, {
|
|
72
|
+
...(options.topK !== undefined && { topK: options.topK }),
|
|
73
|
+
...(options.trackAccess !== undefined && { trackAccess: options.trackAccess }),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
async appendHostSummary(conversationId, summary) {
|
|
77
|
+
const now = this.clock();
|
|
78
|
+
const bullet = {
|
|
79
|
+
id: this.nextId(),
|
|
80
|
+
type: "note",
|
|
81
|
+
status: "open",
|
|
82
|
+
// Collapse whitespace/newlines to a single line: a bullet is one markdown line, and
|
|
83
|
+
// serializeBullet rejects newlines. The harness emits multi-line summaries; P2's distiller
|
|
84
|
+
// will split these into multiple atomic memories — for P1 we store one normalized line.
|
|
85
|
+
text: summary.trim().replace(/\s+/gu, " "),
|
|
86
|
+
salience: 0.5,
|
|
87
|
+
isInsight: false,
|
|
88
|
+
createdAt: now.toISOString(),
|
|
89
|
+
refs: [],
|
|
90
|
+
};
|
|
91
|
+
const path = dailyFilePath(this.root, now);
|
|
92
|
+
appendBullet(this.root, bullet, now);
|
|
93
|
+
const record = {
|
|
94
|
+
id: bullet.id,
|
|
95
|
+
type: bullet.type,
|
|
96
|
+
status: bullet.status,
|
|
97
|
+
text: bullet.text,
|
|
98
|
+
salience: bullet.salience,
|
|
99
|
+
isInsight: bullet.isInsight,
|
|
100
|
+
createdAt: bullet.createdAt,
|
|
101
|
+
accessCount: 0,
|
|
102
|
+
tags: [],
|
|
103
|
+
source: { session: conversationId, file: relative(this.root, path) },
|
|
104
|
+
};
|
|
105
|
+
await this.db.upsert(record);
|
|
106
|
+
// bytesWritten reflects the bullet line actually appended to the daily file, not the raw summary.
|
|
107
|
+
return { conversationId, source: path, bytesWritten: Buffer.byteLength(`${serializeBullet(bullet)}\n`, "utf8") };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Run the nightly reflection ritual: decay salience, synthesize insights from top memories,
|
|
111
|
+
* and surface due intentions. Also writes future-log.md + index.md.
|
|
112
|
+
*
|
|
113
|
+
* Returns `undefined` when no `llm` was configured (matches `capture()` pattern).
|
|
114
|
+
*/
|
|
115
|
+
async reflect() {
|
|
116
|
+
if (this.llm === undefined)
|
|
117
|
+
return undefined;
|
|
118
|
+
const r = await reflectFn({
|
|
119
|
+
db: this.db,
|
|
120
|
+
root: this.root,
|
|
121
|
+
llm: this.llm,
|
|
122
|
+
nextId: this.nextId,
|
|
123
|
+
now: this.clock,
|
|
124
|
+
});
|
|
125
|
+
writeFutureLog(this.root, this.db, this.clock());
|
|
126
|
+
writeIndex(this.root, this.db, this.clock());
|
|
127
|
+
return r;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Run the monthly BuJo migration ritual: review aging open memories and apply LLM decisions
|
|
131
|
+
* (promote / reschedule / cluster / forget). Also writes future-log.md.
|
|
132
|
+
*
|
|
133
|
+
* Returns `undefined` when no `llm` was configured (matches `capture()` pattern).
|
|
134
|
+
*/
|
|
135
|
+
async migrate() {
|
|
136
|
+
if (this.llm === undefined)
|
|
137
|
+
return undefined;
|
|
138
|
+
const m = await migrateFn({
|
|
139
|
+
db: this.db,
|
|
140
|
+
root: this.root,
|
|
141
|
+
llm: this.llm,
|
|
142
|
+
nextId: this.nextId,
|
|
143
|
+
now: this.clock,
|
|
144
|
+
});
|
|
145
|
+
writeFutureLog(this.root, this.db, this.clock());
|
|
146
|
+
return m;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Enqueue a best-effort intelligent capture. Returns immediately. Captures run strictly
|
|
150
|
+
* one-at-a-time (serialized across all channels sharing this store), and a failure is caught +
|
|
151
|
+
* logged so it never breaks the chain, the reply, or the process. No-op without an llm.
|
|
152
|
+
*/
|
|
153
|
+
scheduleCapture(conversationId, text) {
|
|
154
|
+
if (this.llm === undefined)
|
|
155
|
+
return;
|
|
156
|
+
this.captureChain = this.captureChain
|
|
157
|
+
.then(async () => {
|
|
158
|
+
try {
|
|
159
|
+
await this.capture(conversationId, text);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
this.logger.warn(`bujo capture failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
// Terminal guard: if even the logging path throws, the chain must not settle rejected —
|
|
166
|
+
// otherwise every future scheduleCapture would silently stop (`.then` skips a rejected promise).
|
|
167
|
+
.catch(() => undefined);
|
|
168
|
+
}
|
|
169
|
+
/** Await all captures queued before this call (graceful shutdown / one-shot exit). */
|
|
170
|
+
async flush() {
|
|
171
|
+
await this.captureChain;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* LLM-backed intelligent capture: distills the turn text into atomic candidate memories,
|
|
175
|
+
* reconciles them against the existing index (ADD / UPDATE / SUPERSEDE / NOOP), and extracts
|
|
176
|
+
* typed entities into the canonical graph.
|
|
177
|
+
*
|
|
178
|
+
* Returns `undefined` when no `llm` was configured — use `appendHostSummary` as the always-on
|
|
179
|
+
* deterministic rapid-log (P1 path). `capture()` is the intelligent path invoked by P3
|
|
180
|
+
* reflection/cron and future session hooks; it is safe to call on every turn when an LLM is
|
|
181
|
+
* present, and a no-op when one is not.
|
|
182
|
+
*/
|
|
183
|
+
async capture(conversationId, text) {
|
|
184
|
+
if (this.llm === undefined)
|
|
185
|
+
return undefined;
|
|
186
|
+
const res = await captureTurn(text, {
|
|
187
|
+
db: this.db,
|
|
188
|
+
root: this.root,
|
|
189
|
+
llm: this.llm,
|
|
190
|
+
nextId: this.nextId,
|
|
191
|
+
now: this.clock,
|
|
192
|
+
});
|
|
193
|
+
return { actions: res.actions.length, entities: res.entities };
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Apply salience decay to all memories in the store.
|
|
197
|
+
*
|
|
198
|
+
* Usable in all tiers (lite, journal, bujo) — no LLM required. In the `journal` and `bujo`
|
|
199
|
+
* tiers this is the primary maintenance call; in the `lite` tier it still runs harmlessly.
|
|
200
|
+
*/
|
|
201
|
+
async decay() {
|
|
202
|
+
return this.db.applyDecay(this.clock());
|
|
203
|
+
}
|
|
204
|
+
/** Run deterministic, no-LLM BuJo consolidation in every tier. */
|
|
205
|
+
async consolidate() {
|
|
206
|
+
return consolidateBujoMemory({
|
|
207
|
+
root: this.root,
|
|
208
|
+
db: this.db,
|
|
209
|
+
now: this.clock(),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
async close() {
|
|
213
|
+
// Drain any queued captures before closing the db handle, so a caller that omits flush()
|
|
214
|
+
// (e.g. the MCP's signal handler) doesn't strand an in-flight capture against a closed db.
|
|
215
|
+
await this.flush();
|
|
216
|
+
this.db.close();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
export function createBujoMemoryStore(options) {
|
|
220
|
+
return new BujoMemoryStore(options);
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=store.js.map
|