@seanhogg/builderforce-memory 2026.6.20 → 2026.6.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cognition/EvermindCognition.d.ts +61 -0
- package/dist/cognition/EvermindCognition.d.ts.map +1 -0
- package/dist/cognition/EvermindCognition.js +109 -0
- package/dist/cognition/EvermindCognition.js.map +1 -0
- package/dist/cognition/gatherers.d.ts +22 -0
- package/dist/cognition/gatherers.d.ts.map +1 -0
- package/dist/cognition/gatherers.js +29 -0
- package/dist/cognition/gatherers.js.map +1 -0
- package/dist/cognition/index.d.ts +12 -0
- package/dist/cognition/index.d.ts.map +1 -0
- package/dist/cognition/index.js +9 -0
- package/dist/cognition/index.js.map +1 -0
- package/dist/cognition/types.d.ts +84 -0
- package/dist/cognition/types.d.ts.map +1 -0
- package/dist/cognition/types.js +12 -0
- package/dist/cognition/types.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/limbic/LimbicSession.d.ts +63 -0
- package/dist/limbic/LimbicSession.d.ts.map +1 -0
- package/dist/limbic/LimbicSession.js +188 -0
- package/dist/limbic/LimbicSession.js.map +1 -0
- package/dist/memory/MemoryStore.d.ts +12 -0
- package/dist/memory/MemoryStore.d.ts.map +1 -1
- package/dist/memory/MemoryStore.js +28 -0
- package/dist/memory/MemoryStore.js.map +1 -1
- package/dist/retrieval/HybridRetriever.d.ts +56 -0
- package/dist/retrieval/HybridRetriever.d.ts.map +1 -0
- package/dist/retrieval/HybridRetriever.js +75 -0
- package/dist/retrieval/HybridRetriever.js.map +1 -0
- package/dist/retrieval/bm25.d.ts +32 -0
- package/dist/retrieval/bm25.d.ts.map +1 -0
- package/dist/retrieval/bm25.js +66 -0
- package/dist/retrieval/bm25.js.map +1 -0
- package/dist/retrieval/chunk.d.ts +33 -0
- package/dist/retrieval/chunk.d.ts.map +1 -0
- package/dist/retrieval/chunk.js +83 -0
- package/dist/retrieval/chunk.js.map +1 -0
- package/dist/retrieval/fusion.d.ts +40 -0
- package/dist/retrieval/fusion.d.ts.map +1 -0
- package/dist/retrieval/fusion.js +64 -0
- package/dist/retrieval/fusion.js.map +1 -0
- package/dist/retrieval/index.d.ts +16 -0
- package/dist/retrieval/index.d.ts.map +1 -0
- package/dist/retrieval/index.js +12 -0
- package/dist/retrieval/index.js.map +1 -0
- package/package.json +8 -4
- package/src/cognition/EvermindCognition.ts +156 -0
- package/src/cognition/gatherers.ts +40 -0
- package/src/cognition/index.ts +20 -0
- package/src/cognition/types.ts +88 -0
- package/src/index.ts +90 -0
- package/src/limbic/LimbicSession.ts +253 -0
- package/src/memory/MemoryStore.ts +36 -0
- package/src/retrieval/HybridRetriever.ts +122 -0
- package/src/retrieval/bm25.ts +83 -0
- package/src/retrieval/chunk.ts +101 -0
- package/src/retrieval/fusion.ts +84 -0
- package/src/retrieval/index.ts +24 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document chunking — recursive character text splitter with overlap.
|
|
3
|
+
*
|
|
4
|
+
* The classic RAG ingestion step the memory layer was missing: large documents
|
|
5
|
+
* are split into smaller, semantically-coherent chunks before embedding so that
|
|
6
|
+
* retrieval returns precise passages rather than whole files. Mirrors the
|
|
7
|
+
* behaviour of LangChain's RecursiveCharacterTextSplitter (split on the largest
|
|
8
|
+
* natural boundary that fits, fall back to finer ones) but is zero-dependency.
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_SEPARATORS = ['\n\n', '\n', '. ', ' ', ''];
|
|
11
|
+
/**
|
|
12
|
+
* Splits `text` into overlapping chunks no larger than `chunkSize` characters,
|
|
13
|
+
* preferring the largest natural separator that keeps a piece under the limit.
|
|
14
|
+
* Returns `[]` for empty/whitespace input. Deterministic and pure.
|
|
15
|
+
*/
|
|
16
|
+
export function chunkText(text, opts = {}) {
|
|
17
|
+
const chunkSize = Math.max(1, opts.chunkSize ?? 1000);
|
|
18
|
+
const overlap = Math.min(Math.max(0, opts.chunkOverlap ?? 200), chunkSize - 1);
|
|
19
|
+
const separators = opts.separators ?? DEFAULT_SEPARATORS;
|
|
20
|
+
const trimmed = text.trim();
|
|
21
|
+
if (trimmed.length === 0)
|
|
22
|
+
return [];
|
|
23
|
+
if (trimmed.length <= chunkSize)
|
|
24
|
+
return [{ text: trimmed, index: 0, start: 0 }];
|
|
25
|
+
const pieces = splitRecursive(trimmed, chunkSize, separators);
|
|
26
|
+
// Merge adjacent pieces up to chunkSize, then stitch overlap between chunks.
|
|
27
|
+
const chunks = [];
|
|
28
|
+
let buf = '';
|
|
29
|
+
const flush = () => {
|
|
30
|
+
const t = buf.trim();
|
|
31
|
+
if (t.length > 0) {
|
|
32
|
+
const start = chunks.length === 0 ? 0 : Math.max(0, trimmed.indexOf(t));
|
|
33
|
+
chunks.push({ text: t, index: chunks.length, start });
|
|
34
|
+
}
|
|
35
|
+
buf = '';
|
|
36
|
+
};
|
|
37
|
+
for (const piece of pieces) {
|
|
38
|
+
if (buf.length + piece.length <= chunkSize) {
|
|
39
|
+
buf += piece;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
flush();
|
|
43
|
+
// Carry overlap from the previous chunk's tail.
|
|
44
|
+
const prev = chunks[chunks.length - 1]?.text ?? '';
|
|
45
|
+
buf = (overlap > 0 ? prev.slice(-overlap) : '') + piece;
|
|
46
|
+
// A single piece longer than chunkSize is hard-split.
|
|
47
|
+
while (buf.length > chunkSize) {
|
|
48
|
+
const head = buf.slice(0, chunkSize);
|
|
49
|
+
chunks.push({ text: head.trim(), index: chunks.length, start: 0 });
|
|
50
|
+
buf = (overlap > 0 ? head.slice(-overlap) : '') + buf.slice(chunkSize);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
flush();
|
|
55
|
+
return chunks.map((c, i) => ({ ...c, index: i }));
|
|
56
|
+
}
|
|
57
|
+
/** Recursively splits text on the first separator that yields sub-chunkSize pieces. */
|
|
58
|
+
function splitRecursive(text, chunkSize, separators) {
|
|
59
|
+
/* istanbul ignore next -- defensive base case; callers only recurse on parts > chunkSize */
|
|
60
|
+
if (text.length <= chunkSize)
|
|
61
|
+
return [text];
|
|
62
|
+
const [sep, ...rest] = separators;
|
|
63
|
+
if (sep === undefined)
|
|
64
|
+
return [text];
|
|
65
|
+
if (sep === '') {
|
|
66
|
+
// Last resort: hard character split.
|
|
67
|
+
const out = [];
|
|
68
|
+
for (let i = 0; i < text.length; i += chunkSize)
|
|
69
|
+
out.push(text.slice(i, i + chunkSize));
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
const parts = text.split(sep);
|
|
73
|
+
const out = [];
|
|
74
|
+
for (let i = 0; i < parts.length; i++) {
|
|
75
|
+
const part = i < parts.length - 1 ? parts[i] + sep : parts[i];
|
|
76
|
+
if (part.length > chunkSize)
|
|
77
|
+
out.push(...splitRecursive(part, chunkSize, rest));
|
|
78
|
+
else
|
|
79
|
+
out.push(part);
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=chunk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk.js","sourceRoot":"","sources":["../../src/retrieval/chunk.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rank fusion + diversity reranking.
|
|
3
|
+
*
|
|
4
|
+
* • Reciprocal Rank Fusion (RRF) merges the dense (vector) and sparse (BM25)
|
|
5
|
+
* rankings into one list without needing the two score scales to be
|
|
6
|
+
* commensurable — it fuses on RANK, not raw score. This is the standard,
|
|
7
|
+
* parameter-light way to combine hybrid retrieval signals.
|
|
8
|
+
* • Maximal Marginal Relevance (MMR) reranks the fused list to trade off
|
|
9
|
+
* relevance against novelty, so the top-k isn't five near-duplicate chunks.
|
|
10
|
+
*
|
|
11
|
+
* Pure and zero-dependency.
|
|
12
|
+
*/
|
|
13
|
+
export interface RankedList {
|
|
14
|
+
/** Ordered ids, most relevant first. */
|
|
15
|
+
ids: string[];
|
|
16
|
+
/** Optional weight for this list in the fusion (default 1). */
|
|
17
|
+
weight?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface FusedHit {
|
|
20
|
+
id: string;
|
|
21
|
+
score: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Reciprocal Rank Fusion over any number of ranked lists.
|
|
25
|
+
* score(d) = Σ_lists weight / (k + rank(d)). `k` (default 60) damps the
|
|
26
|
+
* contribution of low-ranked items; the canonical TREC value.
|
|
27
|
+
*/
|
|
28
|
+
export declare function reciprocalRankFusion(lists: RankedList[], k?: number): FusedHit[];
|
|
29
|
+
export interface MmrCandidate {
|
|
30
|
+
id: string;
|
|
31
|
+
vector: Float32Array;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Maximal Marginal Relevance rerank. Greedily selects up to `topK` candidates,
|
|
35
|
+
* each step maximising `λ·sim(query, d) − (1−λ)·max sim(d, already-selected)`.
|
|
36
|
+
* λ=1 is pure relevance; lower λ injects diversity. Candidates without vectors
|
|
37
|
+
* should be filtered out by the caller (they cannot be MMR-scored).
|
|
38
|
+
*/
|
|
39
|
+
export declare function maximalMarginalRelevance(queryVec: Float32Array, candidates: MmrCandidate[], topK: number, lambda?: number): string[];
|
|
40
|
+
//# sourceMappingURL=fusion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fusion.d.ts","sourceRoot":"","sources":["../../src/retrieval/fusion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,WAAW,UAAU;IACvB,wCAAwC;IACxC,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC,SAAK,GAAG,QAAQ,EAAE,CAW5E;AAED,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,YAAY,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACpC,QAAQ,EAAE,YAAY,EACtB,UAAU,EAAE,YAAY,EAAE,EAC1B,IAAI,EAAE,MAAM,EACZ,MAAM,SAAM,GACb,MAAM,EAAE,CAsBV"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rank fusion + diversity reranking.
|
|
3
|
+
*
|
|
4
|
+
* • Reciprocal Rank Fusion (RRF) merges the dense (vector) and sparse (BM25)
|
|
5
|
+
* rankings into one list without needing the two score scales to be
|
|
6
|
+
* commensurable — it fuses on RANK, not raw score. This is the standard,
|
|
7
|
+
* parameter-light way to combine hybrid retrieval signals.
|
|
8
|
+
* • Maximal Marginal Relevance (MMR) reranks the fused list to trade off
|
|
9
|
+
* relevance against novelty, so the top-k isn't five near-duplicate chunks.
|
|
10
|
+
*
|
|
11
|
+
* Pure and zero-dependency.
|
|
12
|
+
*/
|
|
13
|
+
import { cosineSimilarity } from '../similarity/index.js';
|
|
14
|
+
/**
|
|
15
|
+
* Reciprocal Rank Fusion over any number of ranked lists.
|
|
16
|
+
* score(d) = Σ_lists weight / (k + rank(d)). `k` (default 60) damps the
|
|
17
|
+
* contribution of low-ranked items; the canonical TREC value.
|
|
18
|
+
*/
|
|
19
|
+
export function reciprocalRankFusion(lists, k = 60) {
|
|
20
|
+
const acc = new Map();
|
|
21
|
+
for (const list of lists) {
|
|
22
|
+
const weight = list.weight ?? 1;
|
|
23
|
+
list.ids.forEach((id, rank) => {
|
|
24
|
+
acc.set(id, (acc.get(id) ?? 0) + weight / (k + rank + 1));
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return [...acc.entries()]
|
|
28
|
+
.map(([id, score]) => ({ id, score }))
|
|
29
|
+
.sort((a, b) => b.score - a.score);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Maximal Marginal Relevance rerank. Greedily selects up to `topK` candidates,
|
|
33
|
+
* each step maximising `λ·sim(query, d) − (1−λ)·max sim(d, already-selected)`.
|
|
34
|
+
* λ=1 is pure relevance; lower λ injects diversity. Candidates without vectors
|
|
35
|
+
* should be filtered out by the caller (they cannot be MMR-scored).
|
|
36
|
+
*/
|
|
37
|
+
export function maximalMarginalRelevance(queryVec, candidates, topK, lambda = 0.7) {
|
|
38
|
+
const remaining = [...candidates];
|
|
39
|
+
const selected = [];
|
|
40
|
+
const relevance = new Map();
|
|
41
|
+
for (const c of remaining)
|
|
42
|
+
relevance.set(c.id, cosineSimilarity(queryVec, c.vector));
|
|
43
|
+
while (selected.length < topK && remaining.length > 0) {
|
|
44
|
+
let bestIdx = 0;
|
|
45
|
+
let bestScore = -Infinity;
|
|
46
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
47
|
+
const c = remaining[i];
|
|
48
|
+
let maxSimToSelected = 0;
|
|
49
|
+
for (const s of selected) {
|
|
50
|
+
const sim = cosineSimilarity(c.vector, s.vector);
|
|
51
|
+
if (sim > maxSimToSelected)
|
|
52
|
+
maxSimToSelected = sim;
|
|
53
|
+
}
|
|
54
|
+
const mmr = lambda * relevance.get(c.id) - (1 - lambda) * maxSimToSelected;
|
|
55
|
+
if (mmr > bestScore) {
|
|
56
|
+
bestScore = mmr;
|
|
57
|
+
bestIdx = i;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
selected.push(remaining.splice(bestIdx, 1)[0]);
|
|
61
|
+
}
|
|
62
|
+
return selected.map(s => s.id);
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=fusion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fusion.js","sourceRoot":"","sources":["../../src/retrieval/fusion.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieval layer — chunking, BM25, rank fusion, and the HybridRetriever.
|
|
3
|
+
*
|
|
4
|
+
* The classic RAG pieces the memory stack previously lacked (chunking, hybrid
|
|
5
|
+
* dense+sparse search, reranking), implemented zero-dependency so they run in the
|
|
6
|
+
* browser, Node, and the SSM runtime alike.
|
|
7
|
+
*/
|
|
8
|
+
export { chunkText } from './chunk.js';
|
|
9
|
+
export type { Chunk, ChunkOptions } from './chunk.js';
|
|
10
|
+
export { bm25Search } from './bm25.js';
|
|
11
|
+
export type { Bm25Doc, Bm25Hit, Bm25Options } from './bm25.js';
|
|
12
|
+
export { reciprocalRankFusion, maximalMarginalRelevance } from './fusion.js';
|
|
13
|
+
export type { RankedList, FusedHit, MmrCandidate } from './fusion.js';
|
|
14
|
+
export { hybridRetrieve } from './HybridRetriever.js';
|
|
15
|
+
export type { RetrievalCandidate, HybridQuery, HybridRetrieveOptions, HybridHit, } from './HybridRetriever.js';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/retrieval/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE/D,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC7E,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EACR,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,SAAS,GACZ,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieval layer — chunking, BM25, rank fusion, and the HybridRetriever.
|
|
3
|
+
*
|
|
4
|
+
* The classic RAG pieces the memory stack previously lacked (chunking, hybrid
|
|
5
|
+
* dense+sparse search, reranking), implemented zero-dependency so they run in the
|
|
6
|
+
* browser, Node, and the SSM runtime alike.
|
|
7
|
+
*/
|
|
8
|
+
export { chunkText } from './chunk.js';
|
|
9
|
+
export { bm25Search } from './bm25.js';
|
|
10
|
+
export { reciprocalRankFusion, maximalMarginalRelevance } from './fusion.js';
|
|
11
|
+
export { hybridRetrieve } from './HybridRetriever.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/retrieval/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAGvC,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAG7E,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seanhogg/builderforce-memory",
|
|
3
|
-
"version": "2026.6.
|
|
4
|
-
"description": "BuilderForce Agent Memory — runtime layer. SSM execution, Transformer orchestration, online distillation, and persistent agent memory for BuilderForce.ai agents.",
|
|
3
|
+
"version": "2026.6.28",
|
|
4
|
+
"description": "BuilderForce Agent Memory — runtime layer. SSM execution, Transformer orchestration, online distillation, hybrid RAG retrieval, and persistent agent memory for BuilderForce.ai agents.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./retrieval": {
|
|
14
|
+
"types": "./dist/retrieval/index.d.ts",
|
|
15
|
+
"import": "./dist/retrieval/index.js"
|
|
12
16
|
}
|
|
13
17
|
},
|
|
14
18
|
"type": "module",
|
|
@@ -49,7 +53,7 @@
|
|
|
49
53
|
},
|
|
50
54
|
"homepage": "https://github.com/SeanHogg/builderforce-memory/tree/main/packages/memory#readme",
|
|
51
55
|
"peerDependencies": {
|
|
52
|
-
"@seanhogg/builderforce-memory-engine": "^2026.6.
|
|
56
|
+
"@seanhogg/builderforce-memory-engine": "^2026.6.28"
|
|
53
57
|
},
|
|
54
58
|
"devDependencies": {
|
|
55
59
|
"@jest/globals": "^29.7.0",
|
|
@@ -62,7 +66,7 @@
|
|
|
62
66
|
"jest": "^29.7.0",
|
|
63
67
|
"ts-jest": "^29.2.0",
|
|
64
68
|
"typescript": "^5.0.0",
|
|
65
|
-
"@seanhogg/builderforce-memory-engine": "2026.6.
|
|
69
|
+
"@seanhogg/builderforce-memory-engine": "2026.6.28"
|
|
66
70
|
},
|
|
67
71
|
"jest": {
|
|
68
72
|
"preset": "ts-jest/presets/default-esm",
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evermind — Write-Through Cognition engine.
|
|
3
|
+
*
|
|
4
|
+
* `commit()` is the model-knowledge analogue of a write-through cache write with
|
|
5
|
+
* a conflict resolver: Canonicalize (stable subject key) → Recall incumbent →
|
|
6
|
+
* Evaluate evidence → Reconcile (augment | confirm | supersede | reject) →
|
|
7
|
+
* write-through. `recall()` is the read side, served through a version-token
|
|
8
|
+
* cache that invalidates for free whenever knowledge changes.
|
|
9
|
+
*
|
|
10
|
+
* This is the layer the append-only knowledge loop skipped: it derives a STABLE
|
|
11
|
+
* subject key (not a per-run id) and replaces-on-write, so beliefs never drift
|
|
12
|
+
* into a manual reconciliation step.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type {
|
|
16
|
+
Claim,
|
|
17
|
+
CognitionFactStore,
|
|
18
|
+
CommitResult,
|
|
19
|
+
EvidenceGatherer,
|
|
20
|
+
EvidenceResult,
|
|
21
|
+
Verdict,
|
|
22
|
+
} from './types.js';
|
|
23
|
+
|
|
24
|
+
export interface EvermindCognitionOptions {
|
|
25
|
+
store: CognitionFactStore;
|
|
26
|
+
/** Default evidence gatherer used when `commit()` is not given one. */
|
|
27
|
+
gather?: EvidenceGatherer;
|
|
28
|
+
/**
|
|
29
|
+
* Optional embedding-capable runtime (e.g. SSMRuntime) forwarded to the
|
|
30
|
+
* store's `recallSimilar` so recall sharpens as the model adapts.
|
|
31
|
+
*/
|
|
32
|
+
runtime?: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Subset of a store that can rank facts by semantic similarity. */
|
|
36
|
+
interface SimilarityCapableStore {
|
|
37
|
+
recallSimilar(query: string, topK: number, runtime?: unknown): Promise<Array<{ content: string }>>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function hasRecallSimilar(store: unknown): store is SimilarityCapableStore {
|
|
41
|
+
return typeof (store as SimilarityCapableStore).recallSimilar === 'function';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class EvermindCognition {
|
|
45
|
+
private readonly _store: CognitionFactStore;
|
|
46
|
+
private readonly _defaultGather?: EvidenceGatherer;
|
|
47
|
+
private readonly _runtime?: unknown;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Knowledge-generation token. Bumped on every change to the fact set
|
|
51
|
+
* (augment / supersede). Doubles as the recall-cache namespace so a write
|
|
52
|
+
* invalidates cached reads for free — the "invalidate on write" rule applied
|
|
53
|
+
* to model knowledge.
|
|
54
|
+
*/
|
|
55
|
+
private _version = 0;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* L1 recall cache, namespaced by `_version`. After any knowledge change the
|
|
59
|
+
* version moves, so prior-generation entries are never read again (and are
|
|
60
|
+
* cleared to bound growth) — not an ad-hoc TTL map; a version-token cache.
|
|
61
|
+
*/
|
|
62
|
+
private readonly _recallCache = new Map<string, string[]>();
|
|
63
|
+
|
|
64
|
+
constructor(opts: EvermindCognitionOptions) {
|
|
65
|
+
this._store = opts.store;
|
|
66
|
+
this._defaultGather = opts.gather;
|
|
67
|
+
this._runtime = opts.runtime;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Current knowledge-generation token. */
|
|
71
|
+
get version(): number {
|
|
72
|
+
return this._version;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Commit a candidate fact write-through. Evidence decides conflicts; a
|
|
77
|
+
* supersede replaces the incumbent under the same stable key and invalidates
|
|
78
|
+
* recall. Never appends a competing belief for the same subject.
|
|
79
|
+
*/
|
|
80
|
+
async commit(claim: Claim, gather: EvidenceGatherer | undefined = this._defaultGather): Promise<CommitResult> {
|
|
81
|
+
const incumbent = await this._store.recall(claim.subjectKey);
|
|
82
|
+
const audit: string[] = [];
|
|
83
|
+
|
|
84
|
+
// ── Brand-new subject ────────────────────────────────────────────────
|
|
85
|
+
if (!incumbent) {
|
|
86
|
+
if (gather && claim.requireEvidence) {
|
|
87
|
+
const e = await gather({ claim });
|
|
88
|
+
audit.push(...e.notes);
|
|
89
|
+
if (!e.supportsNew) {
|
|
90
|
+
return this._result('reject', claim.subjectKey, claim.content, undefined, audit);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
await this._write(claim, claim.importance ?? 0.6);
|
|
94
|
+
this._bumpVersion();
|
|
95
|
+
return this._result('augment', claim.subjectKey, claim.content, undefined, audit);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── Identical incumbent — confirm (refresh confidence, no replace) ────
|
|
99
|
+
if (incumbent.content === claim.content) {
|
|
100
|
+
await this._write(claim, Math.min(1, (claim.importance ?? 0.6) + 0.1));
|
|
101
|
+
return this._result('confirm', claim.subjectKey, claim.content, undefined, audit);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Conflict — evidence decides ──────────────────────────────────────
|
|
105
|
+
const e: EvidenceResult = gather
|
|
106
|
+
? await gather({ claim, incumbent: incumbent.content })
|
|
107
|
+
: { supportsNew: true, notes: ['no evidence gatherer supplied; trusting newer observation'] };
|
|
108
|
+
audit.push(...e.notes);
|
|
109
|
+
|
|
110
|
+
if (e.supportsNew) {
|
|
111
|
+
await this._write(claim, Math.max(claim.importance ?? 0.6, 0.9));
|
|
112
|
+
this._bumpVersion();
|
|
113
|
+
return this._result('supersede', claim.subjectKey, claim.content, incumbent.content, audit);
|
|
114
|
+
}
|
|
115
|
+
return this._result('reject', claim.subjectKey, incumbent.content, undefined, audit);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Write-through recall: the top-K most relevant facts for `query`, served
|
|
120
|
+
* from a version-namespaced cache that invalidates on the next knowledge
|
|
121
|
+
* change. Falls back to an empty set when the store can't rank similarity.
|
|
122
|
+
*/
|
|
123
|
+
async recall(query: string, topK = 5): Promise<string[]> {
|
|
124
|
+
const cacheKey = `${this._version}:${topK}:${query}`;
|
|
125
|
+
const cached = this._recallCache.get(cacheKey);
|
|
126
|
+
if (cached) return cached;
|
|
127
|
+
|
|
128
|
+
const facts = hasRecallSimilar(this._store)
|
|
129
|
+
? (await this._store.recallSimilar(query, topK, this._runtime)).map((e) => e.content)
|
|
130
|
+
: [];
|
|
131
|
+
|
|
132
|
+
this._recallCache.set(cacheKey, facts);
|
|
133
|
+
return facts;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── internals ────────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
private async _write(claim: Claim, importance: number): Promise<void> {
|
|
139
|
+
await this._store.remember(claim.subjectKey, claim.content, { tags: claim.tags, importance });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private _bumpVersion(): void {
|
|
143
|
+
this._version++;
|
|
144
|
+
this._recallCache.clear();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private _result(
|
|
148
|
+
verdict: Verdict,
|
|
149
|
+
subjectKey: string,
|
|
150
|
+
content: string,
|
|
151
|
+
superseded: string | undefined,
|
|
152
|
+
evidence: string[],
|
|
153
|
+
): CommitResult {
|
|
154
|
+
return { verdict, subjectKey, content, superseded, evidence, version: this._version };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evermind — reusable evidence gatherers.
|
|
3
|
+
*
|
|
4
|
+
* Concrete, surface-agnostic evidence rules. The presence rule is the one the
|
|
5
|
+
* IDE self-correction loop uses (ground a claim by listing the workspace), kept
|
|
6
|
+
* here so the IDE, the proof harness, and tests share one implementation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { EvidenceGatherer } from './types.js';
|
|
10
|
+
|
|
11
|
+
export interface WorkspacePresenceRule {
|
|
12
|
+
/** Lists the workspace (e.g. the IDE `list_files('.')` control tool). */
|
|
13
|
+
list: () => Promise<string[]>;
|
|
14
|
+
/** Entries that MUST be present for the new claim to hold. */
|
|
15
|
+
mustExist?: string[];
|
|
16
|
+
/** Entries that MUST be absent for the new claim to hold. */
|
|
17
|
+
mustBeAbsent?: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Evidence rule: the new claim is supported iff every `mustExist` entry is
|
|
22
|
+
* present and every `mustBeAbsent` entry is gone from the listing.
|
|
23
|
+
*/
|
|
24
|
+
export function workspacePresenceGatherer(rule: WorkspacePresenceRule): EvidenceGatherer {
|
|
25
|
+
const mustExist = rule.mustExist ?? [];
|
|
26
|
+
const mustBeAbsent = rule.mustBeAbsent ?? [];
|
|
27
|
+
return async () => {
|
|
28
|
+
const listing = await rule.list();
|
|
29
|
+
const present = mustExist.filter((d) => listing.includes(d));
|
|
30
|
+
const absent = mustBeAbsent.filter((d) => !listing.includes(d));
|
|
31
|
+
const supportsNew = present.length === mustExist.length && absent.length === mustBeAbsent.length;
|
|
32
|
+
return {
|
|
33
|
+
supportsNew,
|
|
34
|
+
notes: [
|
|
35
|
+
`present: ${present.join(', ') || '—'}`,
|
|
36
|
+
`absent (as expected): ${absent.join(', ') || '—'}`,
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evermind — Write-Through Cognition.
|
|
3
|
+
*
|
|
4
|
+
* The layer that keeps model knowledge current without a reconciliation step:
|
|
5
|
+
* stable-subject-key beliefs, evidence-gated conflict resolution, replace-on-write.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { EvermindCognition } from './EvermindCognition.js';
|
|
9
|
+
export type { EvermindCognitionOptions } from './EvermindCognition.js';
|
|
10
|
+
export { workspacePresenceGatherer } from './gatherers.js';
|
|
11
|
+
export type { WorkspacePresenceRule } from './gatherers.js';
|
|
12
|
+
export type {
|
|
13
|
+
Claim,
|
|
14
|
+
CognitionFactStore,
|
|
15
|
+
CommitResult,
|
|
16
|
+
EvidenceContext,
|
|
17
|
+
EvidenceGatherer,
|
|
18
|
+
EvidenceResult,
|
|
19
|
+
Verdict,
|
|
20
|
+
} from './types.js';
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evermind — Write-Through Cognition types.
|
|
3
|
+
*
|
|
4
|
+
* The cognition layer is what lets the model's KNOWLEDGE stay current without a
|
|
5
|
+
* reconciliation step: every incoming fact is evaluated against EVIDENCE and the
|
|
6
|
+
* incumbent belief, then committed write-through (replace-on-write by a STABLE
|
|
7
|
+
* subject key) instead of being appended under a fresh per-run key. These types
|
|
8
|
+
* are intentionally store- and surface-agnostic so the same logic runs in the
|
|
9
|
+
* IDE, on-prem, cloud, and the browser.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Outcome of evaluating a candidate fact against its incumbent + evidence. */
|
|
13
|
+
export type Verdict =
|
|
14
|
+
/** No incumbent on this subject — stored as a new belief. */
|
|
15
|
+
| 'augment'
|
|
16
|
+
/** Incumbent identical — recency/confidence refreshed, nothing replaced. */
|
|
17
|
+
| 'confirm'
|
|
18
|
+
/** Incumbent conflicted and evidence favoured the new fact — replaced. */
|
|
19
|
+
| 'supersede'
|
|
20
|
+
/** Incumbent conflicted but evidence did NOT favour the new fact — dropped. */
|
|
21
|
+
| 'reject';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Minimal write-through fact store the cognition layer needs. `MemoryStore`
|
|
25
|
+
* satisfies this structurally (no adapter required) — kept narrow so cloud
|
|
26
|
+
* (Postgres) / browser (IndexedDB) backends can satisfy it too.
|
|
27
|
+
*/
|
|
28
|
+
export interface CognitionFactStore {
|
|
29
|
+
remember(
|
|
30
|
+
key: string,
|
|
31
|
+
content: string,
|
|
32
|
+
opts?: { tags?: string[]; importance?: number; ttlMs?: number },
|
|
33
|
+
): Promise<void>;
|
|
34
|
+
recall(key: string): Promise<{ content: string } | undefined>;
|
|
35
|
+
forget(key: string): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A fact asserted about a subject, keyed by a STABLE canonical key. */
|
|
39
|
+
export interface Claim {
|
|
40
|
+
/**
|
|
41
|
+
* STABLE canonical key naming the SUBJECT of the fact (e.g. `pkg:ssm-stack`).
|
|
42
|
+
* This is the anti-drift fix: logically-superseding facts collide on this key
|
|
43
|
+
* and replace, instead of accumulating under per-run keys.
|
|
44
|
+
*/
|
|
45
|
+
subjectKey: string;
|
|
46
|
+
/** The asserted fact content. */
|
|
47
|
+
content: string;
|
|
48
|
+
tags?: string[];
|
|
49
|
+
importance?: number;
|
|
50
|
+
/**
|
|
51
|
+
* When true, a brand-new subject is only stored if the gatherer's evidence
|
|
52
|
+
* supports it (guards against recording unverified first-observations).
|
|
53
|
+
*/
|
|
54
|
+
requireEvidence?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The verdict of an evidence probe: does ground truth favour the new claim? */
|
|
58
|
+
export interface EvidenceResult {
|
|
59
|
+
supportsNew: boolean;
|
|
60
|
+
/** Audit-readable lines describing what was checked and found. */
|
|
61
|
+
notes: string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface EvidenceContext {
|
|
65
|
+
claim: Claim;
|
|
66
|
+
/** Incumbent belief content for this subject, when one exists. */
|
|
67
|
+
incumbent?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Gathers ground-truth evidence for a claim. Injected by the caller because the
|
|
72
|
+
* evidence source is surface-specific (IDE file tools, cloud DB, HTTP probe…).
|
|
73
|
+
*/
|
|
74
|
+
export type EvidenceGatherer = (ctx: EvidenceContext) => Promise<EvidenceResult>;
|
|
75
|
+
|
|
76
|
+
/** Result of committing a claim through the cognition pipeline. */
|
|
77
|
+
export interface CommitResult {
|
|
78
|
+
verdict: Verdict;
|
|
79
|
+
subjectKey: string;
|
|
80
|
+
/** The belief now held for this subject (incumbent's content on reject). */
|
|
81
|
+
content: string;
|
|
82
|
+
/** Prior content, present only when `verdict === 'supersede'`. */
|
|
83
|
+
superseded?: string;
|
|
84
|
+
/** Evidence audit trail. */
|
|
85
|
+
evidence: string[];
|
|
86
|
+
/** Knowledge-generation token after this commit (bumps on augment/supersede). */
|
|
87
|
+
version: number;
|
|
88
|
+
}
|