cogmemory 0.0.1-beta.2
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 +139 -0
- package/dist/arbiter.cjs +22 -0
- package/dist/arbiter.cjs.map +1 -0
- package/dist/arbiter.d.ts +72 -0
- package/dist/arbiter.js +85 -0
- package/dist/arbiter.js.map +1 -0
- package/dist/cognitive-memory.cjs +15 -0
- package/dist/cognitive-memory.cjs.map +1 -0
- package/dist/cognitive-memory.js +1068 -0
- package/dist/cognitive-memory.js.map +1 -0
- package/dist/index.d.ts +1086 -0
- package/package.json +83 -0
- package/src/arbiter.ts +14 -0
- package/src/client.ts +105 -0
- package/src/cogmem.ts +74 -0
- package/src/cognitive/arbiter.ts +105 -0
- package/src/cognitive/fast-gate.ts +63 -0
- package/src/cognitive/index.ts +64 -0
- package/src/cognitive/memory.ts +878 -0
- package/src/cognitive/relevance.ts +199 -0
- package/src/cognitive/rules.ts +127 -0
- package/src/cognitive/types.ts +232 -0
- package/src/errors.ts +85 -0
- package/src/helpers/turn.ts +172 -0
- package/src/index.ts +134 -0
- package/src/resources/context.ts +17 -0
- package/src/resources/memories.ts +60 -0
- package/src/resources/recall.ts +17 -0
- package/src/resources/self-model.ts +21 -0
- package/src/resources/stats.ts +27 -0
- package/src/resources/tensions.ts +36 -0
- package/src/resources/turns.ts +16 -0
- package/src/types.ts +246 -0
|
@@ -0,0 +1,1068 @@
|
|
|
1
|
+
import { ofetch as x, FetchError as E } from "ofetch";
|
|
2
|
+
class T extends Error {
|
|
3
|
+
/**
|
|
4
|
+
* Set as a real value, because a minifier renames the class and that silently
|
|
5
|
+
* changes what monitoring groups on. `error.name` is correct either way — the
|
|
6
|
+
* constructor assigns it — but `SomeError.name` and `error.constructor.name`
|
|
7
|
+
* are how a reporter identifies a class, and a minified `"h"` breaks that.
|
|
8
|
+
*/
|
|
9
|
+
static name = "CognitiveMemoryError";
|
|
10
|
+
/** The service's own tag: `Unauthorized`, `Forbidden`, `InvalidRequest`, … */
|
|
11
|
+
code;
|
|
12
|
+
status;
|
|
13
|
+
/** Present on a 403: the scope the key would have needed. */
|
|
14
|
+
requiredScope;
|
|
15
|
+
/** Present on a 400: which field, and why. */
|
|
16
|
+
issues;
|
|
17
|
+
body;
|
|
18
|
+
constructor(e, t) {
|
|
19
|
+
super(e), this.name = "CognitiveMemoryError", this.status = t.status, this.code = t.code, this.body = t.body, this.requiredScope = t.requiredScope, this.issues = t.issues, Error.captureStackTrace && Error.captureStackTrace(this, T);
|
|
20
|
+
}
|
|
21
|
+
/** The key is missing, malformed, expired or revoked. */
|
|
22
|
+
isAuthError() {
|
|
23
|
+
return this.status === 401;
|
|
24
|
+
}
|
|
25
|
+
/** The key is valid but lacks a scope. `requiredScope` says which. */
|
|
26
|
+
isScopeError() {
|
|
27
|
+
return this.status === 403;
|
|
28
|
+
}
|
|
29
|
+
/** The request body did not match the schema. `issues` says where. */
|
|
30
|
+
isValidationError() {
|
|
31
|
+
return this.status === 400;
|
|
32
|
+
}
|
|
33
|
+
/** Nothing exists at that id — or it belongs to another organisation. */
|
|
34
|
+
isNotFoundError() {
|
|
35
|
+
return this.status === 404;
|
|
36
|
+
}
|
|
37
|
+
isRateLimitError() {
|
|
38
|
+
return this.status === 429;
|
|
39
|
+
}
|
|
40
|
+
/** A dependency of ours failed, not the request. Safe to retry. */
|
|
41
|
+
isServerError() {
|
|
42
|
+
return this.status >= 500;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function L(i) {
|
|
46
|
+
const {
|
|
47
|
+
apiKey: e,
|
|
48
|
+
baseUrl: t = D(),
|
|
49
|
+
timeout: s = 3e4,
|
|
50
|
+
retry: o = 2,
|
|
51
|
+
debug: n = !1,
|
|
52
|
+
headers: r = {}
|
|
53
|
+
} = i;
|
|
54
|
+
if (typeof e != "string" || e.trim() === "")
|
|
55
|
+
throw new Error(
|
|
56
|
+
"cognitive-memory: apiKey is required. Issue one in the dashboard — the secret is shown only once."
|
|
57
|
+
);
|
|
58
|
+
const a = x.create({
|
|
59
|
+
baseURL: `${t.replace(/\/$/, "")}/api/v1`,
|
|
60
|
+
timeout: s,
|
|
61
|
+
// Retries are for transport hiccups, not for a 400. ofetch already limits
|
|
62
|
+
// this to idempotent-looking methods, and the service is idempotent for the
|
|
63
|
+
// reads that matter.
|
|
64
|
+
retry: o,
|
|
65
|
+
headers: {
|
|
66
|
+
accept: "application/json",
|
|
67
|
+
...r
|
|
68
|
+
},
|
|
69
|
+
onRequest({ options: h, request: u }) {
|
|
70
|
+
h.headers.set("authorization", `Bearer ${e}`), h.headers.set("content-type", "application/json"), n && console.log("[cognitive-memory]", h.method ?? "GET", `${h.baseURL ?? ""}${u ?? ""}`);
|
|
71
|
+
},
|
|
72
|
+
onResponse({ response: h, options: u, request: c }) {
|
|
73
|
+
n && console.log("[cognitive-memory]", h.status, u.method ?? "GET", c ?? "");
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return async (h, u) => {
|
|
77
|
+
try {
|
|
78
|
+
return await a(h, u);
|
|
79
|
+
} catch (c) {
|
|
80
|
+
if (!(c instanceof E) || !c.response) throw c;
|
|
81
|
+
const d = c.response, m = d._data ?? void 0;
|
|
82
|
+
throw new T(m?.message ?? `cognitive-memory: request failed with ${d.status}`, {
|
|
83
|
+
status: d.status,
|
|
84
|
+
code: m?.error ?? "UnknownError",
|
|
85
|
+
...m === void 0 ? {} : { body: m },
|
|
86
|
+
...m?.requiredScope === void 0 ? {} : { requiredScope: m.requiredScope },
|
|
87
|
+
...m?.issues === void 0 ? {} : { issues: m.issues }
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function D() {
|
|
93
|
+
return typeof window < "u" ? window.location.origin : "http://localhost:3000";
|
|
94
|
+
}
|
|
95
|
+
class R {
|
|
96
|
+
constructor(e) {
|
|
97
|
+
this.client = e;
|
|
98
|
+
}
|
|
99
|
+
build(e = {}) {
|
|
100
|
+
return this.client("/context", { method: "POST", body: e });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
class I {
|
|
104
|
+
constructor(e) {
|
|
105
|
+
this.client = e;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The envelope is unwrapped here rather than passed on.
|
|
109
|
+
*
|
|
110
|
+
* The service replies with `{ memories, stats }` so a dashboard can get both in
|
|
111
|
+
* one call, but an SDK caller who asked for a list wants a list. Deciding that
|
|
112
|
+
* in one place here is better than making every integration know the shape.
|
|
113
|
+
*/
|
|
114
|
+
async list(e = {}) {
|
|
115
|
+
return (await this.client("/memories", {
|
|
116
|
+
method: "GET",
|
|
117
|
+
query: e
|
|
118
|
+
})).memories;
|
|
119
|
+
}
|
|
120
|
+
async get(e) {
|
|
121
|
+
return (await this.client(`/memories/${encodeURIComponent(e)}`, {
|
|
122
|
+
method: "GET"
|
|
123
|
+
})).memory;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* State facts outright.
|
|
127
|
+
*
|
|
128
|
+
* Restatements of something already held are folded in rather than stored
|
|
129
|
+
* twice, and the result says which ones were folded and into what — a write
|
|
130
|
+
* that silently did nothing is indistinguishable from a bug.
|
|
131
|
+
*/
|
|
132
|
+
create(e) {
|
|
133
|
+
return this.client("/memories", { method: "POST", body: e });
|
|
134
|
+
}
|
|
135
|
+
/** Move a memory between tiers. L1 is pre-staged into every context build. */
|
|
136
|
+
async promote(e, t) {
|
|
137
|
+
return (await this.client(`/memories/${encodeURIComponent(e)}`, {
|
|
138
|
+
method: "PATCH",
|
|
139
|
+
body: { tier: t }
|
|
140
|
+
})).memory;
|
|
141
|
+
}
|
|
142
|
+
remove(e) {
|
|
143
|
+
return this.client(`/memories/${encodeURIComponent(e)}`, { method: "DELETE" });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
class O {
|
|
147
|
+
constructor(e) {
|
|
148
|
+
this.client = e;
|
|
149
|
+
}
|
|
150
|
+
search(e) {
|
|
151
|
+
return this.client("/recall", { method: "POST", body: e });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
class j {
|
|
155
|
+
constructor(e) {
|
|
156
|
+
this.client = e;
|
|
157
|
+
}
|
|
158
|
+
get() {
|
|
159
|
+
return this.client("/self-model", { method: "GET" });
|
|
160
|
+
}
|
|
161
|
+
record(e) {
|
|
162
|
+
return this.client("/self-model/outcome", { method: "POST", body: e });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
class H {
|
|
166
|
+
constructor(e) {
|
|
167
|
+
this.client = e;
|
|
168
|
+
}
|
|
169
|
+
/** Needs only `stats:read`, so monitoring can watch without reading memories. */
|
|
170
|
+
get() {
|
|
171
|
+
return this.client("/stats", { method: "GET" });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const P = async (i) => {
|
|
175
|
+
const e = i.replace(/\/$/, ""), t = await fetch(`${e}/api/v1/health`, { headers: { accept: "application/json" } });
|
|
176
|
+
if (!t.ok)
|
|
177
|
+
throw new Error(`cognitive-memory: health check failed with ${t.status}`);
|
|
178
|
+
return await t.json();
|
|
179
|
+
};
|
|
180
|
+
class z {
|
|
181
|
+
constructor(e) {
|
|
182
|
+
this.client = e;
|
|
183
|
+
}
|
|
184
|
+
async list(e) {
|
|
185
|
+
return (await this.client("/tensions", {
|
|
186
|
+
method: "GET",
|
|
187
|
+
query: e === void 0 ? {} : { status: e }
|
|
188
|
+
})).tensions;
|
|
189
|
+
}
|
|
190
|
+
async create(e) {
|
|
191
|
+
return (await this.client("/tensions", {
|
|
192
|
+
method: "POST",
|
|
193
|
+
body: e
|
|
194
|
+
})).tension;
|
|
195
|
+
}
|
|
196
|
+
async resolve(e, t) {
|
|
197
|
+
return (await this.client(`/tensions/${encodeURIComponent(e)}`, {
|
|
198
|
+
method: "POST",
|
|
199
|
+
body: t
|
|
200
|
+
})).tension;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
class N {
|
|
204
|
+
constructor(e) {
|
|
205
|
+
this.client = e;
|
|
206
|
+
}
|
|
207
|
+
learn(e) {
|
|
208
|
+
return this.client("/turns", { method: "POST", body: e });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
class U {
|
|
212
|
+
client;
|
|
213
|
+
baseUrl;
|
|
214
|
+
memories;
|
|
215
|
+
context;
|
|
216
|
+
recall;
|
|
217
|
+
turns;
|
|
218
|
+
tensions;
|
|
219
|
+
selfModel;
|
|
220
|
+
stats;
|
|
221
|
+
constructor(e) {
|
|
222
|
+
this.baseUrl = (e.baseUrl ?? "http://localhost:3000").replace(/\/$/, ""), this.client = L(e), this.memories = new I(this.client), this.context = new R(this.client), this.recall = new O(this.client), this.turns = new N(this.client), this.tensions = new z(this.client), this.selfModel = new j(this.client), this.stats = new H(this.client);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Liveness and limits, without spending a request on the configured key.
|
|
226
|
+
*
|
|
227
|
+
* Useful in a readiness probe: if `extractor` is `rules-only` the service is up
|
|
228
|
+
* but is not doing model-backed extraction, and `problems` is non-empty when a
|
|
229
|
+
* setting is present but unusable.
|
|
230
|
+
*/
|
|
231
|
+
health() {
|
|
232
|
+
return P(this.baseUrl);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function he(i) {
|
|
236
|
+
return new U(i);
|
|
237
|
+
}
|
|
238
|
+
async function de(i, e, t = {}) {
|
|
239
|
+
const s = await i.context.build({
|
|
240
|
+
userMessage: e.userMessage,
|
|
241
|
+
...t.sessionId === void 0 ? {} : { sessionId: t.sessionId }
|
|
242
|
+
}), o = await e.run(s.text);
|
|
243
|
+
if (t.learn === !1)
|
|
244
|
+
return { context: s, learning: null, learningSkipped: "learning disabled for this turn" };
|
|
245
|
+
let n = null;
|
|
246
|
+
try {
|
|
247
|
+
n = await i.turns.learn({
|
|
248
|
+
userMessage: e.userMessage,
|
|
249
|
+
assistantResponse: o,
|
|
250
|
+
...t.sessionId === void 0 ? {} : { sessionId: t.sessionId }
|
|
251
|
+
}), t.onLearn?.(n);
|
|
252
|
+
} catch (r) {
|
|
253
|
+
return console.warn("[cognitive-memory] could not learn from this turn", r), { context: s, learning: null, learningSkipped: String(r) };
|
|
254
|
+
}
|
|
255
|
+
if (t.domain !== void 0)
|
|
256
|
+
try {
|
|
257
|
+
await i.selfModel.record({
|
|
258
|
+
domain: t.domain,
|
|
259
|
+
success: t.failurePattern === void 0,
|
|
260
|
+
...t.failurePattern === void 0 ? {} : { failurePattern: t.failurePattern },
|
|
261
|
+
...t.strategy === void 0 ? {} : { strategy: t.strategy }
|
|
262
|
+
});
|
|
263
|
+
} catch (r) {
|
|
264
|
+
console.warn("[cognitive-memory] could not record the domain outcome", r);
|
|
265
|
+
}
|
|
266
|
+
return { context: s, learning: n };
|
|
267
|
+
}
|
|
268
|
+
async function me(i, e, t = {}) {
|
|
269
|
+
const s = await i.recall.search({
|
|
270
|
+
query: e,
|
|
271
|
+
...t.limit === void 0 ? {} : { limit: t.limit }
|
|
272
|
+
});
|
|
273
|
+
if (s.empty)
|
|
274
|
+
return t.emptyMessage ?? `Nothing in memory matches "${e}". If you were not told, say so rather than guessing.`;
|
|
275
|
+
const o = s.results.map((n) => {
|
|
276
|
+
const r = n.memory.domains.length > 0 ? ` (${n.memory.domains.join(", ")})` : "";
|
|
277
|
+
return `- ${n.memory.content}${r} [${n.memory.tier}, relevance ${n.score.toFixed(2)}]`;
|
|
278
|
+
});
|
|
279
|
+
return `Remembered (${s.results.length} match${s.results.length === 1 ? "" : "es"}):
|
|
280
|
+
${o.join(`
|
|
281
|
+
`)}`;
|
|
282
|
+
}
|
|
283
|
+
async function ue(i, e) {
|
|
284
|
+
const t = await i.memories.create({
|
|
285
|
+
items: e.facts.map((s) => ({ content: s })),
|
|
286
|
+
...e.sessionId === void 0 ? {} : { sessionId: e.sessionId }
|
|
287
|
+
});
|
|
288
|
+
return { stored: t.stored, merged: t.mergedInto, rejected: t.rejected };
|
|
289
|
+
}
|
|
290
|
+
const B = [
|
|
291
|
+
/actually,?\s+(?:we|i)\s+(?:switched|changed|moved|migrated|replaced|use|want|have)/i,
|
|
292
|
+
/that(?:'s|\s+is)\s+(?:wrong|incorrect|outdated|false|not\s+right|no\s+longer)/i,
|
|
293
|
+
/no,?\s+(?:it(?:'s|\s+is)|we\s+use|we\s+switched|it\s+should\s+be|don(?:'t|\s+not)\s+use)/i,
|
|
294
|
+
/stop\s+using\s+/i,
|
|
295
|
+
/instead\s+of\s+/i,
|
|
296
|
+
/disregard\s+(?:prior|previous|what\s+i\s+said)/i
|
|
297
|
+
], W = {
|
|
298
|
+
auth: [/auth/i, /login/i, /session/i, /jwt/i, /oauth/i, /token/i, /password/i, /permission/i],
|
|
299
|
+
database: [/postgres/i, /sqlite/i, /sql/i, /schema/i, /migration/i, /prisma/i, /drizzle/i, /mongo/i, /redis/i],
|
|
300
|
+
frontend: [/css/i, /tailwind/i, /component/i, /jsx/i, /tsx/i, /layout/i, /ui/i, /render/i, /html/i],
|
|
301
|
+
build: [/vite/i, /webpack/i, /turbo/i, /tsconfig/i, /pnpm/i, /package\.json/i, /bundle/i],
|
|
302
|
+
api: [/rest/i, /graphql/i, /endpoint/i, /route/i, /fetch/i, /request/i, /response/i],
|
|
303
|
+
git: [/branch/i, /commit/i, /merge/i, /rebase/i, /worktree/i, /conflict/i]
|
|
304
|
+
}, F = (i) => {
|
|
305
|
+
if (!i || i.trim().length === 0)
|
|
306
|
+
return { action: "proceed" };
|
|
307
|
+
for (const e of B)
|
|
308
|
+
if (e.test(i))
|
|
309
|
+
return {
|
|
310
|
+
action: "inject_caution",
|
|
311
|
+
cautionNote: "User appears to be explicitly correcting a prior premise or configuration. Verify current facts before proceeding with assumptions.",
|
|
312
|
+
detectedDomains: k(i)
|
|
313
|
+
};
|
|
314
|
+
return {
|
|
315
|
+
action: "proceed",
|
|
316
|
+
detectedDomains: k(i)
|
|
317
|
+
};
|
|
318
|
+
}, k = (i) => {
|
|
319
|
+
const e = [];
|
|
320
|
+
for (const [t, s] of Object.entries(W))
|
|
321
|
+
s.some((o) => o.test(i)) && e.push(t);
|
|
322
|
+
return e;
|
|
323
|
+
}, _ = /* @__PURE__ */ new Set([
|
|
324
|
+
"the",
|
|
325
|
+
"and",
|
|
326
|
+
"for",
|
|
327
|
+
"this",
|
|
328
|
+
"that",
|
|
329
|
+
"with",
|
|
330
|
+
"from",
|
|
331
|
+
"you",
|
|
332
|
+
"are",
|
|
333
|
+
"was",
|
|
334
|
+
"has",
|
|
335
|
+
"have",
|
|
336
|
+
"what",
|
|
337
|
+
"which",
|
|
338
|
+
"when",
|
|
339
|
+
"were",
|
|
340
|
+
"will",
|
|
341
|
+
"your",
|
|
342
|
+
"our",
|
|
343
|
+
"its",
|
|
344
|
+
"not",
|
|
345
|
+
"but",
|
|
346
|
+
"all",
|
|
347
|
+
"any",
|
|
348
|
+
"can",
|
|
349
|
+
"did",
|
|
350
|
+
"does",
|
|
351
|
+
"how",
|
|
352
|
+
"into",
|
|
353
|
+
"out",
|
|
354
|
+
"use",
|
|
355
|
+
"used",
|
|
356
|
+
"using",
|
|
357
|
+
"one",
|
|
358
|
+
"two",
|
|
359
|
+
"get",
|
|
360
|
+
"new",
|
|
361
|
+
"now",
|
|
362
|
+
"then",
|
|
363
|
+
"than",
|
|
364
|
+
"them",
|
|
365
|
+
"they",
|
|
366
|
+
"his",
|
|
367
|
+
"her",
|
|
368
|
+
"she",
|
|
369
|
+
"him",
|
|
370
|
+
"been",
|
|
371
|
+
"being",
|
|
372
|
+
"there",
|
|
373
|
+
"here",
|
|
374
|
+
"also",
|
|
375
|
+
"just",
|
|
376
|
+
"like",
|
|
377
|
+
"make",
|
|
378
|
+
"made",
|
|
379
|
+
"need",
|
|
380
|
+
"want",
|
|
381
|
+
"about",
|
|
382
|
+
"after",
|
|
383
|
+
"tell",
|
|
384
|
+
"know",
|
|
385
|
+
"give",
|
|
386
|
+
"show",
|
|
387
|
+
"please",
|
|
388
|
+
"would",
|
|
389
|
+
"could",
|
|
390
|
+
"should",
|
|
391
|
+
"shall"
|
|
392
|
+
]), q = /* @__PURE__ */ new Set([
|
|
393
|
+
"this",
|
|
394
|
+
"that",
|
|
395
|
+
"these",
|
|
396
|
+
"those",
|
|
397
|
+
"there",
|
|
398
|
+
"here",
|
|
399
|
+
"with",
|
|
400
|
+
"from",
|
|
401
|
+
"into",
|
|
402
|
+
"must",
|
|
403
|
+
"should",
|
|
404
|
+
"always",
|
|
405
|
+
"never",
|
|
406
|
+
"under",
|
|
407
|
+
"over",
|
|
408
|
+
"about",
|
|
409
|
+
"after",
|
|
410
|
+
"before",
|
|
411
|
+
"when",
|
|
412
|
+
"where",
|
|
413
|
+
"which",
|
|
414
|
+
"what",
|
|
415
|
+
"your",
|
|
416
|
+
"their",
|
|
417
|
+
"them",
|
|
418
|
+
"they",
|
|
419
|
+
"then",
|
|
420
|
+
"than",
|
|
421
|
+
"also",
|
|
422
|
+
"just",
|
|
423
|
+
"only",
|
|
424
|
+
"each",
|
|
425
|
+
"every",
|
|
426
|
+
"some",
|
|
427
|
+
"such",
|
|
428
|
+
"very",
|
|
429
|
+
"more",
|
|
430
|
+
"most",
|
|
431
|
+
"same",
|
|
432
|
+
"file",
|
|
433
|
+
"files",
|
|
434
|
+
"name",
|
|
435
|
+
"names",
|
|
436
|
+
"project",
|
|
437
|
+
"repository",
|
|
438
|
+
"repo",
|
|
439
|
+
"note"
|
|
440
|
+
]), g = (i) => new Set(
|
|
441
|
+
i.toLowerCase().split(/[^a-z0-9]+/).filter((e) => e.length >= 3 && !_.has(e))
|
|
442
|
+
), v = (i, e) => {
|
|
443
|
+
if (i.size === 0 || e.size === 0) return 0;
|
|
444
|
+
let t = 0;
|
|
445
|
+
for (const s of i) e.has(s) && (t += 1);
|
|
446
|
+
return t / Math.min(i.size, e.size);
|
|
447
|
+
}, G = 0.3, Z = 0.12, Q = 8, K = 0.8, p = (i) => new Set(
|
|
448
|
+
i.toLowerCase().replace(/['']/g, "").split(/[^a-z0-9]+/).filter(Boolean).filter((e) => /\d/.test(e) || e.length >= 4).filter((e) => !q.has(e))
|
|
449
|
+
), b = (i, e) => {
|
|
450
|
+
const t = (o) => new Set([...o].map((n) => n.length >= 4 && n.endsWith("s") ? n.slice(0, -1) : n)), s = t(p(e));
|
|
451
|
+
for (const o of t(p(i)))
|
|
452
|
+
if (!s.has(o)) return !0;
|
|
453
|
+
return !1;
|
|
454
|
+
}, V = /\b[a-z0-9][a-z0-9-]{1,}(?:\.[a-z0-9-]+)*\.[a-z]{2,}\b/gi, J = /* @__PURE__ */ new Set([
|
|
455
|
+
"eg",
|
|
456
|
+
"ie",
|
|
457
|
+
"etc",
|
|
458
|
+
"vs",
|
|
459
|
+
"approx",
|
|
460
|
+
"inc",
|
|
461
|
+
"ltd",
|
|
462
|
+
"corp",
|
|
463
|
+
"dept",
|
|
464
|
+
"est",
|
|
465
|
+
"fig",
|
|
466
|
+
"no",
|
|
467
|
+
"al"
|
|
468
|
+
]), fe = (i) => {
|
|
469
|
+
const e = /* @__PURE__ */ new Set();
|
|
470
|
+
for (const t of i.match(/\bhttps?:\/\/[^\s<>()[\]"'`]+/g) ?? []) e.add(t);
|
|
471
|
+
for (const t of i.match(V) ?? [])
|
|
472
|
+
J.has(t.toLowerCase()) || e.add(t);
|
|
473
|
+
for (const t of i.match(/\b(?:\.{0,2}\/)?[\w-]+(?:\/[\w.-]+)+\/?/g) ?? [])
|
|
474
|
+
t.length > 3 && e.add(t);
|
|
475
|
+
for (const t of i.match(/\b[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+\b/g) ?? []) e.add(t);
|
|
476
|
+
for (const t of i.match(/\b[A-Z]{2,}[0-9][A-Z0-9-]*\b/g) ?? []) e.add(t);
|
|
477
|
+
for (const t of i.match(/\b[a-z]+(?:[A-Z][a-z0-9]+){1,}\b/g) ?? []) e.add(t);
|
|
478
|
+
for (const t of i.match(/\b[a-z]+(?:-[a-z0-9]+){2,}\b/g) ?? []) e.add(t);
|
|
479
|
+
for (const t of i.match(/\b[0-9a-f]{6,}\b/gi) ?? []) e.add(t);
|
|
480
|
+
return [...e];
|
|
481
|
+
}, Y = [
|
|
482
|
+
/\b(?:do not|don'?t|never|no need to)\s+(?:verify|check|confirm|look\s?up|search|investigate|browse|resolve)\b/i,
|
|
483
|
+
/\bjust\s+(?:remember|note|acknowledge|retain|treat)\b/i,
|
|
484
|
+
/\b(?:held|noted|stored|remembered)\s+(?:in|for)\s+(?:this|the)\s+conversation\b/i,
|
|
485
|
+
/\bnot\s+verified\b/i,
|
|
486
|
+
/\bwithout\s+verifying\b/i,
|
|
487
|
+
/\bfor\s+this\s+(?:conversation|session|turn|reply|response)\s+only\b/i,
|
|
488
|
+
/^(?:ok|okay|noted|got it|sure|thanks)\b[.!]?$/i
|
|
489
|
+
], A = (i) => Y.some((e) => e.test(i)), X = (i, e) => {
|
|
490
|
+
const t = p(i), s = p(e);
|
|
491
|
+
if (t.size === 0 || s.size === 0) return 0;
|
|
492
|
+
let o = 0;
|
|
493
|
+
for (const n of t) s.has(n) && (o += 1);
|
|
494
|
+
return o / Math.min(t.size, s.size);
|
|
495
|
+
}, pe = (i) => i.trim().toLowerCase(), ee = (i) => Math.ceil(i.length / 4), $ = (i) => {
|
|
496
|
+
if (i.gist && i.gist.trim().length > 0) return i.gist.trim();
|
|
497
|
+
const t = (i.content.split(/(?<=[.!?])\s/)[0] ?? i.content).trim();
|
|
498
|
+
return t.length > 90 ? `${t.slice(0, 90)}…` : t;
|
|
499
|
+
}, te = 5;
|
|
500
|
+
class ge {
|
|
501
|
+
// L0: Pinned core state (identity, self-model, active tensions)
|
|
502
|
+
activeTensions = /* @__PURE__ */ new Map();
|
|
503
|
+
selfModel;
|
|
504
|
+
activeTaskTrace = "";
|
|
505
|
+
// L1: Hot Cache (pre-staged for upcoming turn)
|
|
506
|
+
l1HotCache = /* @__PURE__ */ new Map();
|
|
507
|
+
// L2: Warm Storage (indexed candidates ready for promotion)
|
|
508
|
+
l2WarmStore = /* @__PURE__ */ new Map();
|
|
509
|
+
// L3: Cold Archive (historical, loaded on demand)
|
|
510
|
+
l3ColdArchive = /* @__PURE__ */ new Map();
|
|
511
|
+
maxL0Tokens;
|
|
512
|
+
maxL1Tokens;
|
|
513
|
+
/** Ceiling on everything injected into one prompt, index and bodies together. */
|
|
514
|
+
maxTotalTokens;
|
|
515
|
+
arbiter = null;
|
|
516
|
+
autoExtractMemories;
|
|
517
|
+
/** Model-backed turn extractor; the regex fallback runs when this is absent. */
|
|
518
|
+
reconcile;
|
|
519
|
+
extract;
|
|
520
|
+
onPersist;
|
|
521
|
+
stats = {
|
|
522
|
+
totalTurnsProcessed: 0,
|
|
523
|
+
predictionsHit: 0,
|
|
524
|
+
predictionsTotal: 0,
|
|
525
|
+
tensionsDetected: 0
|
|
526
|
+
};
|
|
527
|
+
constructor(e = {}) {
|
|
528
|
+
this.maxL0Tokens = e.maxL0Tokens ?? 2e3, this.maxL1Tokens = e.maxL1Tokens ?? 8e3, this.maxTotalTokens = e.maxTotalTokens ?? 2e3, this.arbiter = e.arbiter ?? null, this.autoExtractMemories = e.autoExtractMemories ?? !0, this.extract = e.extract, this.reconcile = e.reconcile, this.onPersist = e.onPersist, this.selfModel = {
|
|
529
|
+
domains: e.initialSelfModel?.domains ?? {},
|
|
530
|
+
calibrationFactor: e.initialSelfModel?.calibrationFactor ?? 1,
|
|
531
|
+
activeDomains: e.initialSelfModel?.activeDomains ?? []
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Sub-1ms synchronous call to produce the prompt context block.
|
|
536
|
+
* Concatenates L0 (active tensions + self-model) and pre-staged L1.
|
|
537
|
+
* Adds zero latency to TTFT.
|
|
538
|
+
*/
|
|
539
|
+
/**
|
|
540
|
+
* Decide what goes into this turn's prompt, and record why.
|
|
541
|
+
*
|
|
542
|
+
* Follows the index/body split that Claude Code's `MEMORY.md` and Letta's
|
|
543
|
+
* progressive disclosure both converged on: a short index of every memory is
|
|
544
|
+
* always present, and a body is only spent where a deterministic signal earned
|
|
545
|
+
* it. Always injecting bodies costs an order of magnitude more tokens and, per
|
|
546
|
+
* Chroma's context-rot work, injects distractors by construction.
|
|
547
|
+
*
|
|
548
|
+
* @param forceFull memory ids whose body should be included regardless of tier.
|
|
549
|
+
*/
|
|
550
|
+
planInjection(e = {}) {
|
|
551
|
+
const t = new Set(e.forceFull ?? []), s = [], o = [];
|
|
552
|
+
let n = 0, r = !1;
|
|
553
|
+
const a = (c, d, m) => {
|
|
554
|
+
const f = $(c), S = m ? c.content : void 0, C = c.metadata.domains.slice(0, 3), M = C.length > 0 ? ` (${C.join(", ")})` : "", y = ee(`${f}${M}${S ?? ""}`);
|
|
555
|
+
if (n + y > this.maxTotalTokens) {
|
|
556
|
+
r = !0;
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
n += y, o.push({ id: c.id, tier: c.tier, reason: d, gist: f, body: S, tokens: y });
|
|
560
|
+
};
|
|
561
|
+
if (e.userMessage) {
|
|
562
|
+
const c = F(e.userMessage);
|
|
563
|
+
c.action === "inject_caution" && c.cautionNote && s.push(`### ⚠️ Correction Detected In This Message
|
|
564
|
+
${c.cautionNote}`);
|
|
565
|
+
}
|
|
566
|
+
const l = this.selfModel.activeDomains.map((c) => ({ d: c, capability: this.selfModel.domains[c] })).filter((c) => c.capability && c.capability.reliabilityScore < 0.75);
|
|
567
|
+
if (l.length > 0) {
|
|
568
|
+
const c = l.map(({ d, capability: m }) => {
|
|
569
|
+
const f = m;
|
|
570
|
+
return a(
|
|
571
|
+
{
|
|
572
|
+
id: `guardrail-${d}`,
|
|
573
|
+
content: `${d}: reliability ${Math.round(f.reliabilityScore * 100)}% over ${f.sampleCount} tasks.${f.knownFailurePatterns.length > 0 ? ` Known pitfalls: ${f.knownFailurePatterns.join("; ")}` : ""}${f.recommendedStrategies.length > 0 ? ` Approach: ${f.recommendedStrategies.join("; ")}` : ""}`,
|
|
574
|
+
tier: "L0",
|
|
575
|
+
metadata: { domains: [d], createdAt: Date.now(), lastAccessedAt: Date.now(), accessCount: 0 }
|
|
576
|
+
},
|
|
577
|
+
"guardrail",
|
|
578
|
+
!0
|
|
579
|
+
), `- ${d} (${Math.round(f.reliabilityScore * 100)}% reliable): ${f.knownFailurePatterns.join("; ") || "be careful"}`;
|
|
580
|
+
});
|
|
581
|
+
s.push(`### Weak Domains — Under 75% Reliability
|
|
582
|
+
${c.join(`
|
|
583
|
+
`)}`);
|
|
584
|
+
}
|
|
585
|
+
const h = [];
|
|
586
|
+
for (const c of this.l1HotCache.values()) {
|
|
587
|
+
const d = c.metadata.domains.slice(0, 3), m = d.length > 0 ? ` (${d.join(", ")})` : "";
|
|
588
|
+
t.has(c.id) ? (a(c, "trigger", !0), h.push(`- ${c.content}${m}`)) : (a(c, "index", !1), h.push(`- ${$(c)}${m}`));
|
|
589
|
+
}
|
|
590
|
+
h.length > 0 && s.push(
|
|
591
|
+
[
|
|
592
|
+
"### Memory index — established earlier in this project",
|
|
593
|
+
"One line per remembered item. Use `recall` to pull a full item, then treat it as true.",
|
|
594
|
+
...h
|
|
595
|
+
].join(`
|
|
596
|
+
`)
|
|
597
|
+
);
|
|
598
|
+
const u = [...this.activeTensions.values()].filter((c) => c.status === "active");
|
|
599
|
+
if (u.length > 0) {
|
|
600
|
+
const c = u.map((d) => (a(
|
|
601
|
+
{
|
|
602
|
+
id: d.id,
|
|
603
|
+
content: `${d.claimA.statement} vs ${d.claimB.statement} — ${d.actionableQuestion}`,
|
|
604
|
+
bookmark: d.claimA.statement,
|
|
605
|
+
tier: "L0",
|
|
606
|
+
metadata: { domains: [], createdAt: d.claimA.timestamp, lastAccessedAt: Date.now(), accessCount: 0 }
|
|
607
|
+
},
|
|
608
|
+
"tension",
|
|
609
|
+
!0
|
|
610
|
+
), `- [${d.impact.toUpperCase()}] "${d.claimA.statement}" conflicts with "${d.claimB.statement}". Ask: ${d.actionableQuestion}`));
|
|
611
|
+
s.push(`### Unresolved Contradictions
|
|
612
|
+
${c.join(`
|
|
613
|
+
`)}`);
|
|
614
|
+
}
|
|
615
|
+
return {
|
|
616
|
+
text: s.length > 0 ? `
|
|
617
|
+
## Cognitive Memory State
|
|
618
|
+
${s.join(`
|
|
619
|
+
|
|
620
|
+
`)}
|
|
621
|
+
` : "",
|
|
622
|
+
entries: o,
|
|
623
|
+
totalTokens: n,
|
|
624
|
+
truncated: r
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
/** Prompt text only. Prefer `planInjection` when you want to log what went in. */
|
|
628
|
+
getPromptContext(e) {
|
|
629
|
+
return this.planInjection({ userMessage: e }).text;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Search every tier for memories relevant to `query`.
|
|
633
|
+
*
|
|
634
|
+
* This is the on-demand path, exposed to the agent as a tool. Pre-staging into
|
|
635
|
+
* the prompt is a best-effort optimisation; a model that does not read the
|
|
636
|
+
* block, or whose question shares no words with it, can still ask. Ranking is
|
|
637
|
+
* deterministic — no model involved — so recall does not depend on model
|
|
638
|
+
* quality.
|
|
639
|
+
*/
|
|
640
|
+
search(e, t = 8) {
|
|
641
|
+
const s = g(e);
|
|
642
|
+
if (s.size === 0) return [];
|
|
643
|
+
const n = [
|
|
644
|
+
...this.l1HotCache.values(),
|
|
645
|
+
...this.l2WarmStore.values(),
|
|
646
|
+
...this.l3ColdArchive.values()
|
|
647
|
+
].map((l) => ({
|
|
648
|
+
item: l,
|
|
649
|
+
score: v(s, g(`${l.content} ${l.metadata.domains.join(" ")}`))
|
|
650
|
+
})).filter((l) => l.score > 0).sort((l, h) => h.score - l.score || l.item.content.length - h.item.content.length), r = [], a = [];
|
|
651
|
+
for (const l of n)
|
|
652
|
+
if (!r.some((h) => X(h, l.item.content) >= K) && (r.push(l.item.content), a.push(l), a.length >= Math.max(1, t)))
|
|
653
|
+
break;
|
|
654
|
+
return a;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Post-turn asynchronous execution.
|
|
658
|
+
* Fired when the agent finishes streaming its response to the user.
|
|
659
|
+
* Evaluates turn, manages L1 cache, detects tensions, updates self-model.
|
|
660
|
+
*/
|
|
661
|
+
async postTurnAsync(e) {
|
|
662
|
+
this.stats.totalTurnsProcessed += 1, e.detectedDomains && e.detectedDomains.length > 0 && (this.selfModel.activeDomains = Array.from(
|
|
663
|
+
/* @__PURE__ */ new Set([...this.selfModel.activeDomains, ...e.detectedDomains])
|
|
664
|
+
));
|
|
665
|
+
const t = Array.from(this.l2WarmStore.values()).slice(0, 20).map((n) => ({
|
|
666
|
+
id: n.id,
|
|
667
|
+
bookmark: n.bookmark,
|
|
668
|
+
domains: n.metadata.domains,
|
|
669
|
+
hasTension: !1
|
|
670
|
+
})), s = Array.from(this.l1HotCache.values()).map((n) => ({
|
|
671
|
+
id: n.id,
|
|
672
|
+
bookmark: n.bookmark,
|
|
673
|
+
domains: n.metadata.domains
|
|
674
|
+
})), o = this.arbiter ? await this.arbiter({
|
|
675
|
+
turnText: e.userMessage,
|
|
676
|
+
assistantReply: e.assistantResponse,
|
|
677
|
+
l0Prompt: this.activeTaskTrace,
|
|
678
|
+
l1Summaries: s,
|
|
679
|
+
candidates: t
|
|
680
|
+
}) : this.runHeuristicArbiter(e, t);
|
|
681
|
+
if (this.applyArbiterDecision(o), this.autoExtractMemories)
|
|
682
|
+
if (this.extract)
|
|
683
|
+
try {
|
|
684
|
+
await this.applyExtraction(
|
|
685
|
+
await this.extract({ userMessage: e.userMessage, assistantResponse: e.assistantResponse })
|
|
686
|
+
);
|
|
687
|
+
} catch {
|
|
688
|
+
this.autoExtractTurnMemory(e.userMessage, e.assistantResponse);
|
|
689
|
+
}
|
|
690
|
+
else e.assistantResponse.length > 50 && this.autoExtractTurnMemory(e.userMessage, e.assistantResponse);
|
|
691
|
+
if (this.enforceBudgets(), this.onPersist)
|
|
692
|
+
try {
|
|
693
|
+
await this.onPersist(this.getSnapshot());
|
|
694
|
+
} catch {
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Fold extracted memories and tensions in, skipping anything already known.
|
|
699
|
+
*
|
|
700
|
+
* Extracted items land in L2 rather than L1: they are candidates, and the
|
|
701
|
+
* arbiter decides what is worth pre-staging. That is what gives the tiering
|
|
702
|
+
* something to actually do — previously nothing ever wrote to L2, so every
|
|
703
|
+
* arbiter run evaluated an empty candidate set.
|
|
704
|
+
*/
|
|
705
|
+
async applyExtraction(e) {
|
|
706
|
+
const t = [];
|
|
707
|
+
for (const s of e.memories ?? []) {
|
|
708
|
+
const o = s.content.trim();
|
|
709
|
+
if (o.length < 8 || o.length > 600 || A(o)) continue;
|
|
710
|
+
let n = o;
|
|
711
|
+
const r = this.recallSimilar(o);
|
|
712
|
+
if (r.length > 0) {
|
|
713
|
+
let h = { action: "add" };
|
|
714
|
+
if (this.reconcile ? t.push({ content: o, candidates: r }) : r.some((u) => u.content.trim().toLowerCase() === o.trim().toLowerCase()) && (h = { action: "merge" }), h.action === "reject" || h.action === "merge" && r[0] && this.mergeInto(r[0], h.content, { text: o, stored: !1 }))
|
|
715
|
+
continue;
|
|
716
|
+
h.action === "replace" && this.supersede(r.map((u) => u.content)), n = h.content?.trim() || o;
|
|
717
|
+
}
|
|
718
|
+
const a = Date.now(), l = `mem-${a}-${Math.random().toString(36).slice(2, 7)}`;
|
|
719
|
+
this.addMemory(
|
|
720
|
+
{
|
|
721
|
+
id: l,
|
|
722
|
+
content: n,
|
|
723
|
+
bookmark: n.slice(0, 80),
|
|
724
|
+
tier: "L1",
|
|
725
|
+
metadata: {
|
|
726
|
+
domains: s.domains ?? [],
|
|
727
|
+
createdAt: a,
|
|
728
|
+
lastAccessedAt: a,
|
|
729
|
+
accessCount: 1
|
|
730
|
+
}
|
|
731
|
+
},
|
|
732
|
+
"L1"
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
if (t.length > 0 && this.reconcile) {
|
|
736
|
+
let s = [];
|
|
737
|
+
try {
|
|
738
|
+
s = await this.reconcile({
|
|
739
|
+
items: t.map((o) => ({ candidate: o.content, remember: o.candidates.map((n) => n.content) }))
|
|
740
|
+
});
|
|
741
|
+
} catch {
|
|
742
|
+
s = [];
|
|
743
|
+
}
|
|
744
|
+
for (const [o, n] of t.entries()) {
|
|
745
|
+
const r = s[o];
|
|
746
|
+
if (r)
|
|
747
|
+
if (r.action === "reject") this.removeByContent(n.content);
|
|
748
|
+
else if (r.action === "merge") {
|
|
749
|
+
const a = n.candidates[0];
|
|
750
|
+
if (a && this.mergeInto(a, r.content, { text: n.content, stored: !0 }))
|
|
751
|
+
continue;
|
|
752
|
+
} else r.action === "replace" && (this.supersede(n.candidates.map((a) => a.content)), r.content && this.replaceByContent(n.content, r.content));
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
for (const s of e.tensions ?? []) {
|
|
756
|
+
const o = `${s.claimA}::${s.claimB}`.toLowerCase();
|
|
757
|
+
this.activeTensions.has(o) || this.addTension({
|
|
758
|
+
id: o,
|
|
759
|
+
status: "active",
|
|
760
|
+
claimA: { source: "user", statement: s.claimA, timestamp: Date.now() },
|
|
761
|
+
claimB: { source: "conversation", statement: s.claimB, timestamp: Date.now() },
|
|
762
|
+
impact: s.impact,
|
|
763
|
+
taskRelevance: 1,
|
|
764
|
+
actionableQuestion: s.actionableQuestion
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
/** Drop an entry by its exact content. */
|
|
769
|
+
removeByContent(e) {
|
|
770
|
+
const t = e.trim().toLowerCase();
|
|
771
|
+
for (const s of [this.l1HotCache, this.l2WarmStore, this.l3ColdArchive])
|
|
772
|
+
for (const [o, n] of s)
|
|
773
|
+
n.content.trim().toLowerCase() === t && s.delete(o);
|
|
774
|
+
}
|
|
775
|
+
/** Rewrite one entry's text in place, matched on its old value. */
|
|
776
|
+
replaceByContent(e, t) {
|
|
777
|
+
const s = e.trim().toLowerCase();
|
|
778
|
+
for (const o of [this.l1HotCache, this.l2WarmStore, this.l3ColdArchive])
|
|
779
|
+
for (const n of o.values())
|
|
780
|
+
n.content.trim().toLowerCase() === s && !b(n.content, t) && this.enrich(n, t);
|
|
781
|
+
}
|
|
782
|
+
/** Replace an entry's content, keeping its identity and tags. */
|
|
783
|
+
enrich(e, t) {
|
|
784
|
+
e.content = t, e.bookmark = t.slice(0, 80), e.metadata.lastAccessedAt = Date.now();
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Fold a duplicate into its survivor, refusing rewrites that drop anything.
|
|
788
|
+
*
|
|
789
|
+
* `merge` returns the model's idea of the fuller statement, and overwriting
|
|
790
|
+
* with it deletes whatever the model happened to leave out. The under-merge
|
|
791
|
+
* bias covers merging the *wrong* pair, but nothing covered a lossy rewrite
|
|
792
|
+
* of the right pair, so containment is checked first.
|
|
793
|
+
*
|
|
794
|
+
* @returns false when the rewrite would lose information, in which case the
|
|
795
|
+
* caller keeps both entries.
|
|
796
|
+
*/
|
|
797
|
+
mergeInto(e, t, s) {
|
|
798
|
+
const o = t?.trim();
|
|
799
|
+
return o && (b(e.content, o) || b(s.text, o)) ? !1 : (s.stored && this.removeByContent(s.text), o && this.enrich(e, o), !0);
|
|
800
|
+
}
|
|
801
|
+
/** Retire the entries a replacement supersedes, keeping them out of retrieval. */
|
|
802
|
+
supersede(e) {
|
|
803
|
+
const t = new Set(e.map((s) => s.trim().toLowerCase()));
|
|
804
|
+
for (const s of [this.l1HotCache, this.l2WarmStore, this.l3ColdArchive])
|
|
805
|
+
for (const [o, n] of s)
|
|
806
|
+
t.has(n.content.trim().toLowerCase()) && s.delete(o);
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Existing memories worth adjudicating a candidate against.
|
|
810
|
+
*
|
|
811
|
+
* Cheap and deliberately over-inclusive. An identical string short-circuits
|
|
812
|
+
* because it is never worth a model call; anything else above the recall floor
|
|
813
|
+
* is offered to the adjudicator, which decides.
|
|
814
|
+
*/
|
|
815
|
+
recallSimilar(e) {
|
|
816
|
+
const t = p(e);
|
|
817
|
+
return t.size === 0 ? [] : [
|
|
818
|
+
...this.l1HotCache.values(),
|
|
819
|
+
...this.l2WarmStore.values(),
|
|
820
|
+
...this.l3ColdArchive.values()
|
|
821
|
+
].map((n) => {
|
|
822
|
+
const r = p(n.content);
|
|
823
|
+
let a = 0;
|
|
824
|
+
for (const l of t) r.has(l) && (a += 1);
|
|
825
|
+
return { item: n, exact: n.content.trim().toLowerCase() === e.trim().toLowerCase(), score: r.size ? a / Math.min(t.size, r.size) : 0 };
|
|
826
|
+
}).filter((n) => n.exact || n.score >= G).sort((n, r) => Number(r.exact) - Number(n.exact) || r.score - n.score).slice(0, Q).map((n) => n.item);
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Keep the pinned tiers within budget.
|
|
830
|
+
*
|
|
831
|
+
* `maxL0Tokens`/`maxL1Tokens` were stored but never read, so L1 grew for the
|
|
832
|
+
* lifetime of the process. Evict least-recently-accessed first, and demote to
|
|
833
|
+
* L2 rather than dropping, so nothing is lost.
|
|
834
|
+
*/
|
|
835
|
+
enforceBudgets() {
|
|
836
|
+
((t, s) => {
|
|
837
|
+
if (s <= 0) return;
|
|
838
|
+
let o = 0;
|
|
839
|
+
for (const r of t.values()) o += r.content.length / 4;
|
|
840
|
+
if (o <= s) return;
|
|
841
|
+
const n = [...t.values()].sort(
|
|
842
|
+
(r, a) => r.metadata.lastAccessedAt - a.metadata.lastAccessedAt
|
|
843
|
+
);
|
|
844
|
+
for (const r of n) {
|
|
845
|
+
if (o <= s) break;
|
|
846
|
+
t.delete(r.id), o -= r.content.length / 4, this.l2WarmStore.set(r.id, { ...r, tier: "L2" });
|
|
847
|
+
}
|
|
848
|
+
})(this.l1HotCache, this.maxL1Tokens);
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Add a known tension manually or from an external source.
|
|
852
|
+
*/
|
|
853
|
+
addTension(e) {
|
|
854
|
+
this.activeTensions.set(e.id, e), this.stats.tensionsDetected += 1;
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Mark a tension resolved with an optional reusable pattern.
|
|
858
|
+
*/
|
|
859
|
+
resolveTension(e, t) {
|
|
860
|
+
const s = this.activeTensions.get(e);
|
|
861
|
+
return s ? (s.status = "resolved", s.resolution = {
|
|
862
|
+
resolvedAt: Date.now(),
|
|
863
|
+
resolvedBy: t.resolvedBy,
|
|
864
|
+
pattern: t.pattern
|
|
865
|
+
}, !0) : !1;
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Add a memory directly to L2 warm storage or promote to L1.
|
|
869
|
+
*/
|
|
870
|
+
addMemory(e, t = "L2") {
|
|
871
|
+
e.tier = t, t === "L1" ? this.l1HotCache.set(e.id, e) : t === "L2" ? this.l2WarmStore.set(e.id, e) : t === "L3" && this.l3ColdArchive.set(e.id, e);
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Drop one memory by id, from whichever tier holds it.
|
|
875
|
+
*
|
|
876
|
+
* The counterpart to `addMemory`, and the only way to retire a fact that has
|
|
877
|
+
* turned out to be wrong. One map is not enough to look in: a promotion or a
|
|
878
|
+
* demotion moves an id between them, so an id that is absent from the hot
|
|
879
|
+
* cache may still be in the warm store or the archive. Short-circuiting on the
|
|
880
|
+
* first hit is therefore safe — an id lives in exactly one of the three.
|
|
881
|
+
*/
|
|
882
|
+
removeMemory(e) {
|
|
883
|
+
return this.l1HotCache.delete(e) || this.l2WarmStore.delete(e) || this.l3ColdArchive.delete(e);
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Update the proprioceptive capability record for a domain.
|
|
887
|
+
*/
|
|
888
|
+
recordDomainOutcome(e, t, s) {
|
|
889
|
+
const o = this.selfModel.domains[e] ?? {
|
|
890
|
+
reliabilityScore: 0.8,
|
|
891
|
+
sampleCount: 0,
|
|
892
|
+
knownFailurePatterns: [],
|
|
893
|
+
recommendedStrategies: []
|
|
894
|
+
}, n = o.sampleCount + 1, r = 1 / Math.min(n, 10), a = t ? 1 : 0, l = o.reliabilityScore * (1 - r) + a * r;
|
|
895
|
+
!t && s && !o.knownFailurePatterns.includes(s) && o.knownFailurePatterns.push(s), this.selfModel.domains[e] = {
|
|
896
|
+
reliabilityScore: Number(l.toFixed(3)),
|
|
897
|
+
sampleCount: n,
|
|
898
|
+
knownFailurePatterns: o.knownFailurePatterns,
|
|
899
|
+
recommendedStrategies: o.recommendedStrategies
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
getSnapshot() {
|
|
903
|
+
return {
|
|
904
|
+
l0: {
|
|
905
|
+
tensions: Array.from(this.activeTensions.values()),
|
|
906
|
+
selfModel: JSON.parse(JSON.stringify(this.selfModel)),
|
|
907
|
+
activeTaskTrace: this.activeTaskTrace
|
|
908
|
+
},
|
|
909
|
+
l1: Array.from(this.l1HotCache.values()),
|
|
910
|
+
l2: Array.from(this.l2WarmStore.values()),
|
|
911
|
+
l3: Array.from(this.l3ColdArchive.values()),
|
|
912
|
+
stats: { ...this.stats }
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
loadSnapshot(e) {
|
|
916
|
+
this.activeTensions.clear();
|
|
917
|
+
for (const t of e.l0.tensions)
|
|
918
|
+
this.activeTensions.set(t.id, t);
|
|
919
|
+
this.selfModel = e.l0.selfModel, this.activeTaskTrace = e.l0.activeTaskTrace, this.l1HotCache.clear();
|
|
920
|
+
for (const t of e.l1)
|
|
921
|
+
this.l1HotCache.set(t.id, t);
|
|
922
|
+
this.l2WarmStore.clear();
|
|
923
|
+
for (const t of e.l2)
|
|
924
|
+
this.l2WarmStore.set(t.id, t);
|
|
925
|
+
this.l3ColdArchive.clear();
|
|
926
|
+
for (const t of e.l3)
|
|
927
|
+
this.l3ColdArchive.set(t.id, t);
|
|
928
|
+
this.stats = { ...e.stats };
|
|
929
|
+
}
|
|
930
|
+
applyArbiterDecision(e) {
|
|
931
|
+
for (const t of e.promotions) {
|
|
932
|
+
const s = this.l2WarmStore.get(t.memoryId) ?? this.l3ColdArchive.get(t.memoryId);
|
|
933
|
+
s && (s.tier = "L1", s.metadata.lastAccessedAt = Date.now(), s.metadata.accessCount += 1, this.l1HotCache.set(s.id, s), this.l2WarmStore.delete(s.id), this.l3ColdArchive.delete(s.id));
|
|
934
|
+
}
|
|
935
|
+
for (const t of e.demotions) {
|
|
936
|
+
const s = this.l1HotCache.get(t.memoryId);
|
|
937
|
+
s && (s.tier = "L2", this.l2WarmStore.set(s.id, s), this.l1HotCache.delete(s.id));
|
|
938
|
+
}
|
|
939
|
+
for (const t of e.detectedTensions) {
|
|
940
|
+
const s = `tension-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
941
|
+
this.addTension({
|
|
942
|
+
id: s,
|
|
943
|
+
status: "active",
|
|
944
|
+
claimA: { source: "current conversation", statement: t.claimA, timestamp: Date.now() },
|
|
945
|
+
claimB: { source: "known state / files", statement: t.claimB, timestamp: Date.now() },
|
|
946
|
+
impact: t.impact,
|
|
947
|
+
taskRelevance: 1,
|
|
948
|
+
actionableQuestion: t.actionableQuestion
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
if (e.selfModelUpdate) {
|
|
952
|
+
const { domain: t, success: s, failurePatternObserved: o } = e.selfModelUpdate;
|
|
953
|
+
s !== void 0 && this.recordDomainOutcome(t, s, o);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Fast default heuristic arbiter when an LLM arbiter model is not supplied.
|
|
958
|
+
*/
|
|
959
|
+
runHeuristicArbiter(e, t) {
|
|
960
|
+
const s = [], o = [], n = g(`${e.userMessage} ${e.assistantResponse}`), r = t.map((a) => ({
|
|
961
|
+
candidate: a,
|
|
962
|
+
score: v(
|
|
963
|
+
n,
|
|
964
|
+
g(`${a.bookmark} ${a.domains.join(" ")}`)
|
|
965
|
+
)
|
|
966
|
+
})).filter((a) => a.score > Z).sort((a, l) => l.score - a.score).slice(0, 3);
|
|
967
|
+
for (const { candidate: a, score: l } of r)
|
|
968
|
+
s.push({
|
|
969
|
+
memoryId: a.id,
|
|
970
|
+
targetTier: "L1",
|
|
971
|
+
signalType: "anticipatory",
|
|
972
|
+
urgency: Math.min(1, l)
|
|
973
|
+
});
|
|
974
|
+
for (const a of this.l1HotCache.values())
|
|
975
|
+
!(v(n, g(`${a.bookmark} ${a.metadata.domains.join(" ")}`)) > 0) && this.l1HotCache.size > te && o.push({
|
|
976
|
+
memoryId: a.id,
|
|
977
|
+
targetTier: "L2",
|
|
978
|
+
reason: "Not referenced in recent turns"
|
|
979
|
+
});
|
|
980
|
+
return process.env.NAH_MEMORY_DEBUG && console.log(
|
|
981
|
+
` [arbiter] scored=${JSON.stringify(r.map((a) => ({ id: a.candidate.id, score: Number(a.score.toFixed(2)) })))} promotions=${s.length} demotions=${o.length}`
|
|
982
|
+
), { promotions: s, demotions: o, pins: [], detectedTensions: [] };
|
|
983
|
+
}
|
|
984
|
+
autoExtractTurnMemory(e, t) {
|
|
985
|
+
const s = e.match(/(?:always|never|make sure to|remember to|use)\s+([^\.\n]+)/i);
|
|
986
|
+
if (s && s[1]) {
|
|
987
|
+
const o = `mem-pref-${Date.now()}`, n = s[1].trim();
|
|
988
|
+
if (A(`User preference: ${n}`)) return;
|
|
989
|
+
this.addMemory({
|
|
990
|
+
id: o,
|
|
991
|
+
content: `User preference: ${n}`,
|
|
992
|
+
bookmark: `User preference: ${n.slice(0, 80)}`,
|
|
993
|
+
tier: "L1",
|
|
994
|
+
// Pre-stage directly into hot cache
|
|
995
|
+
metadata: {
|
|
996
|
+
domains: this.selfModel.activeDomains,
|
|
997
|
+
createdAt: Date.now(),
|
|
998
|
+
lastAccessedAt: Date.now(),
|
|
999
|
+
accessCount: 1
|
|
1000
|
+
}
|
|
1001
|
+
}, "L1");
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
const se = /\bhttps?:\/\/[^\s<>()[\]"'`]+/g, oe = /\b(always|never|must|should|make sure to|remember to)\b[\s:]+([^.?!\n]{4,160})/gi, ne = /\b([A-Za-z][\w ()-]{2,60}?)\s+(?:is|are)\s+([^\n?!]{3,200})/g, ie = /[A-Z0-9_./:-]/, re = (i) => {
|
|
1006
|
+
const e = i.trim();
|
|
1007
|
+
return e.length < 3 || e.length > 200 || /^(not|nothing|unclear|unknown|the same|that|this|it)\b/i.test(e) ? !1 : ie.test(e);
|
|
1008
|
+
}, ae = (i) => {
|
|
1009
|
+
const e = /\.(?=\s+[A-Z(])/.exec(i);
|
|
1010
|
+
return e ? i.slice(0, e.index) : i;
|
|
1011
|
+
}, w = (i) => ae(i).replace(/\s+/g, " ").replace(/^(?:that|the|a|an|it|this)\s+/i, "").replace(/[,;:\s.。]+$/, "").trim(), we = (i) => {
|
|
1012
|
+
const e = [], t = (o) => {
|
|
1013
|
+
const n = w(o);
|
|
1014
|
+
n.length < 8 || n.length > 300 || /^(yes|no|ok|okay|sure|thanks|thank you|got it|noted)\b/i.test(n) || e.some((r) => r.content.toLowerCase() === n.toLowerCase()) || e.push({ content: n, domains: [] });
|
|
1015
|
+
};
|
|
1016
|
+
for (const o of i.match(se) ?? [])
|
|
1017
|
+
t(`User-provided URL: ${o}`);
|
|
1018
|
+
for (const o of i.matchAll(oe)) {
|
|
1019
|
+
const n = (o[1] ?? "").toLowerCase(), r = w(o[2] ?? "");
|
|
1020
|
+
r && t(`User requirement (${n}): ${r}`);
|
|
1021
|
+
}
|
|
1022
|
+
for (const o of i.matchAll(ne)) {
|
|
1023
|
+
const n = w(o[1] ?? ""), r = w(o[2] ?? "");
|
|
1024
|
+
!n || !re(r) || t(`${n} is ${r}`);
|
|
1025
|
+
}
|
|
1026
|
+
return e.filter(
|
|
1027
|
+
(o, n) => !e.some((r, a) => a === n || r.content.length <= o.content.length ? !1 : ce(o.content, r.content))
|
|
1028
|
+
).slice(0, 6);
|
|
1029
|
+
}, ce = (i, e) => {
|
|
1030
|
+
const t = new Set(e.toLowerCase().split(/[^a-z0-9]+/).filter((o) => o.length >= 4)), s = i.toLowerCase().split(/[^a-z0-9]+/).filter((o) => o.length >= 4);
|
|
1031
|
+
return s.length > 0 && s.every((o) => t.has(o));
|
|
1032
|
+
};
|
|
1033
|
+
export {
|
|
1034
|
+
G as CANDIDATE_FLOOR,
|
|
1035
|
+
K as COLLAPSE_THRESHOLD,
|
|
1036
|
+
U as Cogmem,
|
|
1037
|
+
ge as CognitiveMemory,
|
|
1038
|
+
T as CognitiveMemoryError,
|
|
1039
|
+
R as ContextResource,
|
|
1040
|
+
Q as MAX_CANDIDATES,
|
|
1041
|
+
I as MemoriesResource,
|
|
1042
|
+
Z as PROMOTE_THRESHOLD,
|
|
1043
|
+
O as RecallResource,
|
|
1044
|
+
j as SelfModelResource,
|
|
1045
|
+
H as StatsResource,
|
|
1046
|
+
z as TensionsResource,
|
|
1047
|
+
N as TurnsResource,
|
|
1048
|
+
he as createClient,
|
|
1049
|
+
L as createHttpClient,
|
|
1050
|
+
p as distinctiveTokens,
|
|
1051
|
+
ee as estimateTokens,
|
|
1052
|
+
we as extractDeterministic,
|
|
1053
|
+
k as extractDomains,
|
|
1054
|
+
fe as extractIdentifiers,
|
|
1055
|
+
P as fetchHealth,
|
|
1056
|
+
$ as gistOf,
|
|
1057
|
+
A as isInteractionScoped,
|
|
1058
|
+
b as isLossyRewrite,
|
|
1059
|
+
pe as normalise,
|
|
1060
|
+
v as overlapScore,
|
|
1061
|
+
me as recallOrExplain,
|
|
1062
|
+
g as relevanceTokens,
|
|
1063
|
+
F as runFastGate,
|
|
1064
|
+
de as runTurn,
|
|
1065
|
+
ue as seedMemories,
|
|
1066
|
+
X as similarity
|
|
1067
|
+
};
|
|
1068
|
+
//# sourceMappingURL=cognitive-memory.js.map
|