@seanhogg/builderforce-memory-mcp 2026.7.14 → 2026.9.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/README.md +47 -5
- package/dist/backend.d.ts +32 -0
- package/dist/backend.d.ts.map +1 -1
- package/dist/backends/memory-store.d.ts +32 -9
- package/dist/backends/memory-store.d.ts.map +1 -1
- package/dist/backends/memory-store.js +155 -33
- package/dist/backends/memory-store.js.map +1 -1
- package/dist/bin/http.d.ts +5 -0
- package/dist/bin/http.d.ts.map +1 -1
- package/dist/bin/http.js +11 -0
- package/dist/bin/http.js.map +1 -1
- package/dist/bin/install.js +3 -2
- package/dist/bin/install.js.map +1 -1
- package/dist/bin/stdio.d.ts +17 -7
- package/dist/bin/stdio.d.ts.map +1 -1
- package/dist/bin/stdio.js +24 -8
- package/dist/bin/stdio.js.map +1 -1
- package/dist/compaction.d.ts +76 -0
- package/dist/compaction.d.ts.map +1 -0
- package/dist/compaction.js +77 -0
- package/dist/compaction.js.map +1 -0
- package/dist/embedding/evermind-embedder.d.ts +119 -0
- package/dist/embedding/evermind-embedder.d.ts.map +1 -0
- package/dist/embedding/evermind-embedder.js +201 -0
- package/dist/embedding/evermind-embedder.js.map +1 -0
- package/dist/embedding/index.d.ts +38 -0
- package/dist/embedding/index.d.ts.map +1 -0
- package/dist/embedding/index.js +44 -0
- package/dist/embedding/index.js.map +1 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/install/claude-hooks.d.ts +42 -2
- package/dist/install/claude-hooks.d.ts.map +1 -1
- package/dist/install/claude-hooks.js +35 -3
- package/dist/install/claude-hooks.js.map +1 -1
- package/dist/install/server-spec.d.ts +12 -0
- package/dist/install/server-spec.d.ts.map +1 -1
- package/dist/install/server-spec.js +18 -0
- package/dist/install/server-spec.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +117 -6
- package/dist/tools.js.map +1 -1
- package/package.json +15 -4
- package/server.json +47 -0
- package/src/backend.ts +36 -0
- package/src/backends/memory-store.ts +184 -35
- package/src/bin/http.ts +12 -0
- package/src/bin/install.ts +3 -2
- package/src/bin/stdio.ts +25 -8
- package/src/compaction.ts +126 -0
- package/src/embedding/evermind-embedder.ts +276 -0
- package/src/embedding/index.ts +54 -0
- package/src/index.ts +18 -4
- package/src/install/claude-hooks.ts +40 -4
- package/src/install/server-spec.ts +25 -0
- package/src/tools.ts +133 -12
package/dist/bin/stdio.d.ts
CHANGED
|
@@ -5,17 +5,27 @@
|
|
|
5
5
|
* Env:
|
|
6
6
|
* BUILDERFORCE_MEMORY_DB IndexedDB database name (default: MemoryStore default).
|
|
7
7
|
* BUILDERFORCE_MEMORY_READONLY '1' to disable remember/forget tools.
|
|
8
|
-
* BUILDERFORCE_MEMORY_FILE Absolute path to a JSON snapshot.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* BUILDERFORCE_MEMORY_FILE Absolute path to a JSON snapshot. Defaults to the
|
|
9
|
+
* shared per-machine store (~/.builderforce-memory/
|
|
10
|
+
* memory.json) so memory survives the respawn every
|
|
11
|
+
* MCP client does between sessions — without a
|
|
12
|
+
* snapshot fake-indexeddb loses everything on exit.
|
|
13
|
+
* BUILDERFORCE_MEMORY_MODEL Absolute path to a `.evermind` package (an
|
|
14
|
+
* `evermind-lm`). When set, recall is ranked by SSM
|
|
15
|
+
* embedding cosine FUSED with the lexical ranking
|
|
16
|
+
* instead of lexically alone. Pure CPU — no GPU is
|
|
17
|
+
* involved; see src/embedding/evermind-embedder.ts.
|
|
18
|
+
* BUILDERFORCE_MEMORY_TOKENIZER Tokenizer JSON matching that checkpoint. Defaults
|
|
19
|
+
* to `<model>.tokenizer.json`.
|
|
20
|
+
* BUILDERFORCE_MEMORY_VECTORS Persistent vector-cache path. Defaults beside the
|
|
21
|
+
* snapshot, so a respawned subprocess does not
|
|
22
|
+
* re-embed the whole store on its first recall.
|
|
12
23
|
* BUILDERFORCE_GATEWAY_URL Gateway base URL (default https://api.builderforce.ai).
|
|
13
24
|
* BUILDERFORCE_API_KEY `bfk_*` tenant key. When set, exposes the cost tools
|
|
14
25
|
* (token_usage, model_efficiency).
|
|
15
26
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* pass `runtime` to createLocalMemoryStoreBackend (see README).
|
|
27
|
+
* With no model configured recall ranks lexically (BM25) and the tool result SAYS
|
|
28
|
+
* so ("Ranked by: lexical recall (fallback …)"), so a degrade is never silent.
|
|
19
29
|
*/
|
|
20
30
|
export {};
|
|
21
31
|
//# sourceMappingURL=stdio.d.ts.map
|
package/dist/bin/stdio.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/bin/stdio.ts"],"names":[],"mappings":";AACA
|
|
1
|
+
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/bin/stdio.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG"}
|
package/dist/bin/stdio.js
CHANGED
|
@@ -5,23 +5,39 @@
|
|
|
5
5
|
* Env:
|
|
6
6
|
* BUILDERFORCE_MEMORY_DB IndexedDB database name (default: MemoryStore default).
|
|
7
7
|
* BUILDERFORCE_MEMORY_READONLY '1' to disable remember/forget tools.
|
|
8
|
-
* BUILDERFORCE_MEMORY_FILE Absolute path to a JSON snapshot.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* BUILDERFORCE_MEMORY_FILE Absolute path to a JSON snapshot. Defaults to the
|
|
9
|
+
* shared per-machine store (~/.builderforce-memory/
|
|
10
|
+
* memory.json) so memory survives the respawn every
|
|
11
|
+
* MCP client does between sessions — without a
|
|
12
|
+
* snapshot fake-indexeddb loses everything on exit.
|
|
13
|
+
* BUILDERFORCE_MEMORY_MODEL Absolute path to a `.evermind` package (an
|
|
14
|
+
* `evermind-lm`). When set, recall is ranked by SSM
|
|
15
|
+
* embedding cosine FUSED with the lexical ranking
|
|
16
|
+
* instead of lexically alone. Pure CPU — no GPU is
|
|
17
|
+
* involved; see src/embedding/evermind-embedder.ts.
|
|
18
|
+
* BUILDERFORCE_MEMORY_TOKENIZER Tokenizer JSON matching that checkpoint. Defaults
|
|
19
|
+
* to `<model>.tokenizer.json`.
|
|
20
|
+
* BUILDERFORCE_MEMORY_VECTORS Persistent vector-cache path. Defaults beside the
|
|
21
|
+
* snapshot, so a respawned subprocess does not
|
|
22
|
+
* re-embed the whole store on its first recall.
|
|
12
23
|
* BUILDERFORCE_GATEWAY_URL Gateway base URL (default https://api.builderforce.ai).
|
|
13
24
|
* BUILDERFORCE_API_KEY `bfk_*` tenant key. When set, exposes the cost tools
|
|
14
25
|
* (token_usage, model_efficiency).
|
|
15
26
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* pass `runtime` to createLocalMemoryStoreBackend (see README).
|
|
27
|
+
* With no model configured recall ranks lexically (BM25) and the tool result SAYS
|
|
28
|
+
* so ("Ranked by: lexical recall (fallback …)"), so a degrade is never silent.
|
|
19
29
|
*/
|
|
20
30
|
import { createLocalMemoryStoreBackend } from "../backends/memory-store.js";
|
|
31
|
+
import { createRecallEmbedder } from "../embedding/index.js";
|
|
32
|
+
import { resolveMemoryFile } from "../install/server-spec.js";
|
|
21
33
|
import { runStdio } from "../transports/stdio.js";
|
|
34
|
+
const memoryFile = resolveMemoryFile();
|
|
22
35
|
const backend = await createLocalMemoryStoreBackend({
|
|
23
36
|
dbName: process.env["BUILDERFORCE_MEMORY_DB"],
|
|
24
|
-
persistFile:
|
|
37
|
+
persistFile: memoryFile,
|
|
38
|
+
// Null when no model is configured (or it fails to load) — recall then stays
|
|
39
|
+
// lexical rather than the server refusing to start over an optional model.
|
|
40
|
+
runtime: (await createRecallEmbedder({ memoryFile })) ?? undefined,
|
|
25
41
|
});
|
|
26
42
|
await runStdio(backend, {
|
|
27
43
|
writable: process.env["BUILDERFORCE_MEMORY_READONLY"] !== "1",
|
package/dist/bin/stdio.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/bin/stdio.ts"],"names":[],"mappings":";AACA
|
|
1
|
+
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/bin/stdio.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAC;AAEvC,MAAM,OAAO,GAAG,MAAM,6BAA6B,CAAC;IAChD,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;IAC7C,WAAW,EAAE,UAAU;IACvB,6EAA6E;IAC7E,2EAA2E;IAC3E,OAAO,EAAE,CAAC,MAAM,oBAAoB,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,IAAI,SAAS;CACrE,CAAC,CAAC;AAEH,MAAM,QAAQ,CAAC,OAAO,EAAE;IACpB,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,KAAK,GAAG;IAC7D,2EAA2E;IAC3E,0EAA0E;IAC1E,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,IAAI,6BAA6B;IACpF,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;CACrD,CAAC,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compaction — turning an ABSORBED memory into a terse pointer stub.
|
|
3
|
+
*
|
|
4
|
+
* Once a fact has been folded into an Evermind (or any downstream learner) the
|
|
5
|
+
* full body has done its job: keeping it verbatim in the store means every
|
|
6
|
+
* subsequent recall pays for text the model already carries in its weights.
|
|
7
|
+
* Compaction replaces the body with a one-line `[absorbed→Evermind vN] <first
|
|
8
|
+
* line>` pointer, so the fact stops filling context while it stays findable.
|
|
9
|
+
*
|
|
10
|
+
* The rules live here — not in the tool handler and not in a transport — because
|
|
11
|
+
* BOTH the in-process `memory_compact` tool and any external compactor (the
|
|
12
|
+
* BuilderForce VS Code extension rewrites the snapshot directly) must produce
|
|
13
|
+
* byte-identical stubs; otherwise re-running either one re-stubs the other's work.
|
|
14
|
+
*
|
|
15
|
+
* Dependency-free and published as its own subpath
|
|
16
|
+
* (`@seanhogg/builderforce-memory-mcp/compaction`), so an external compactor imports
|
|
17
|
+
* these rules instead of re-implementing them — the marker below is the shared
|
|
18
|
+
* idempotency contract between the two processes.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Marker that opens every compacted stub — also the idempotency guard. An entry
|
|
22
|
+
* whose content already starts with this is skipped, so compacting twice (or
|
|
23
|
+
* from two processes) never double-stubs.
|
|
24
|
+
*/
|
|
25
|
+
export declare const STUB_PREFIX = "[absorbed\u2192Evermind";
|
|
26
|
+
/** Default cap on the pointer line carried inside a stub. */
|
|
27
|
+
export declare const DEFAULT_STUB_CHARS = 140;
|
|
28
|
+
/** True when a body is already a compaction stub. */
|
|
29
|
+
export declare function isStub(content: string): boolean;
|
|
30
|
+
/** The first non-empty line of a body, trimmed to `max` chars — the stub's pointer text. */
|
|
31
|
+
export declare function firstLine(content: string, max?: number): string;
|
|
32
|
+
/** Build the terse stub that replaces an absorbed entry's body. */
|
|
33
|
+
export declare function memoryStub(content: string, version: number, maxChars?: number): string;
|
|
34
|
+
/** One entry considered for compaction. */
|
|
35
|
+
export interface CompactionCandidate {
|
|
36
|
+
key: string;
|
|
37
|
+
content: string;
|
|
38
|
+
}
|
|
39
|
+
/** A body rewrite the caller should apply. */
|
|
40
|
+
export interface CompactionWrite {
|
|
41
|
+
key: string;
|
|
42
|
+
/** The stub that replaces the entry's body. */
|
|
43
|
+
content: string;
|
|
44
|
+
/** Characters recovered by this rewrite. */
|
|
45
|
+
bytesSaved: number;
|
|
46
|
+
}
|
|
47
|
+
/** Why a candidate was left alone. */
|
|
48
|
+
export interface CompactionSkip {
|
|
49
|
+
key: string;
|
|
50
|
+
reason: "not_found" | "already_compacted" | "not_smaller";
|
|
51
|
+
}
|
|
52
|
+
/** The decided plan: what to write, what to leave, and the total recovered. */
|
|
53
|
+
export interface CompactionPlan {
|
|
54
|
+
writes: CompactionWrite[];
|
|
55
|
+
skipped: CompactionSkip[];
|
|
56
|
+
bytesSaved: number;
|
|
57
|
+
}
|
|
58
|
+
/** Options controlling how a stub body is produced. */
|
|
59
|
+
export interface CompactionOptions {
|
|
60
|
+
/** Explicit replacement body. Omit to generate a pointer from each entry's own first line. */
|
|
61
|
+
stub?: string;
|
|
62
|
+
/** Evermind version that absorbed the facts; appears in a generated stub. Default 0. */
|
|
63
|
+
version?: number;
|
|
64
|
+
/** Max characters of a generated pointer line. Default {@link DEFAULT_STUB_CHARS}. */
|
|
65
|
+
maxChars?: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Decide the compaction for a set of candidates. Pure — no I/O — so the same
|
|
69
|
+
* rules are unit-testable and reusable by any caller that can read and write
|
|
70
|
+
* memories. `undefined` content means the key was not found.
|
|
71
|
+
*/
|
|
72
|
+
export declare function planCompaction(candidates: Array<CompactionCandidate | {
|
|
73
|
+
key: string;
|
|
74
|
+
content: undefined;
|
|
75
|
+
}>, opts?: CompactionOptions): CompactionPlan;
|
|
76
|
+
//# sourceMappingURL=compaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../src/compaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW,4BAAuB,CAAC;AAEhD,6DAA6D;AAC7D,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC,qDAAqD;AACrD,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED,4FAA4F;AAC5F,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,SAAqB,GAAG,MAAM,CAO3E;AAED,mEAAmE;AACnE,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,SAAqB,GAAG,MAAM,CAElG;AAED,2CAA2C;AAC3C,MAAM,WAAW,mBAAmB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,8CAA8C;AAC9C,MAAM,WAAW,eAAe;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,sCAAsC;AACtC,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,WAAW,GAAG,mBAAmB,GAAG,aAAa,CAAC;CAC7D;AAED,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,uDAAuD;AACvD,MAAM,WAAW,iBAAiB;IAC9B,8FAA8F;IAC9F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC1B,UAAU,EAAE,KAAK,CAAC,mBAAmB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,CAAA;CAAE,CAAC,EAC5E,IAAI,GAAE,iBAAsB,GAC7B,cAAc,CA6BhB"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compaction — turning an ABSORBED memory into a terse pointer stub.
|
|
3
|
+
*
|
|
4
|
+
* Once a fact has been folded into an Evermind (or any downstream learner) the
|
|
5
|
+
* full body has done its job: keeping it verbatim in the store means every
|
|
6
|
+
* subsequent recall pays for text the model already carries in its weights.
|
|
7
|
+
* Compaction replaces the body with a one-line `[absorbed→Evermind vN] <first
|
|
8
|
+
* line>` pointer, so the fact stops filling context while it stays findable.
|
|
9
|
+
*
|
|
10
|
+
* The rules live here — not in the tool handler and not in a transport — because
|
|
11
|
+
* BOTH the in-process `memory_compact` tool and any external compactor (the
|
|
12
|
+
* BuilderForce VS Code extension rewrites the snapshot directly) must produce
|
|
13
|
+
* byte-identical stubs; otherwise re-running either one re-stubs the other's work.
|
|
14
|
+
*
|
|
15
|
+
* Dependency-free and published as its own subpath
|
|
16
|
+
* (`@seanhogg/builderforce-memory-mcp/compaction`), so an external compactor imports
|
|
17
|
+
* these rules instead of re-implementing them — the marker below is the shared
|
|
18
|
+
* idempotency contract between the two processes.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Marker that opens every compacted stub — also the idempotency guard. An entry
|
|
22
|
+
* whose content already starts with this is skipped, so compacting twice (or
|
|
23
|
+
* from two processes) never double-stubs.
|
|
24
|
+
*/
|
|
25
|
+
export const STUB_PREFIX = "[absorbed→Evermind";
|
|
26
|
+
/** Default cap on the pointer line carried inside a stub. */
|
|
27
|
+
export const DEFAULT_STUB_CHARS = 140;
|
|
28
|
+
/** True when a body is already a compaction stub. */
|
|
29
|
+
export function isStub(content) {
|
|
30
|
+
return content.trimStart().startsWith(STUB_PREFIX);
|
|
31
|
+
}
|
|
32
|
+
/** The first non-empty line of a body, trimmed to `max` chars — the stub's pointer text. */
|
|
33
|
+
export function firstLine(content, max = DEFAULT_STUB_CHARS) {
|
|
34
|
+
const line = content
|
|
35
|
+
.split(/\r?\n/)
|
|
36
|
+
.map((l) => l.trim())
|
|
37
|
+
.find((l) => l.length > 0) ?? "";
|
|
38
|
+
return line.length > max ? `${line.slice(0, max - 1).trimEnd()}…` : line;
|
|
39
|
+
}
|
|
40
|
+
/** Build the terse stub that replaces an absorbed entry's body. */
|
|
41
|
+
export function memoryStub(content, version, maxChars = DEFAULT_STUB_CHARS) {
|
|
42
|
+
return `${STUB_PREFIX} v${version}] ${firstLine(content, maxChars)}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Decide the compaction for a set of candidates. Pure — no I/O — so the same
|
|
46
|
+
* rules are unit-testable and reusable by any caller that can read and write
|
|
47
|
+
* memories. `undefined` content means the key was not found.
|
|
48
|
+
*/
|
|
49
|
+
export function planCompaction(candidates, opts = {}) {
|
|
50
|
+
const version = opts.version ?? 0;
|
|
51
|
+
const maxChars = Math.max(20, opts.maxChars ?? DEFAULT_STUB_CHARS);
|
|
52
|
+
const writes = [];
|
|
53
|
+
const skipped = [];
|
|
54
|
+
let bytesSaved = 0;
|
|
55
|
+
for (const c of candidates) {
|
|
56
|
+
const content = c.content;
|
|
57
|
+
if (typeof content !== "string" || !content.trim()) {
|
|
58
|
+
skipped.push({ key: c.key, reason: "not_found" });
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (isStub(content)) {
|
|
62
|
+
skipped.push({ key: c.key, reason: "already_compacted" });
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const stub = opts.stub ?? memoryStub(content, version, maxChars);
|
|
66
|
+
// Never grow an entry — a "compaction" that costs tokens is a regression.
|
|
67
|
+
if (stub.length >= content.length) {
|
|
68
|
+
skipped.push({ key: c.key, reason: "not_smaller" });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const saved = content.length - stub.length;
|
|
72
|
+
writes.push({ key: c.key, content: stub, bytesSaved: saved });
|
|
73
|
+
bytesSaved += saved;
|
|
74
|
+
}
|
|
75
|
+
return { writes, skipped, bytesSaved };
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=compaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compaction.js","sourceRoot":"","sources":["../src/compaction.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* evermind-embedder — SSM-embedding recall for a HEADLESS host, with no GPU.
|
|
3
|
+
*
|
|
4
|
+
* The stdio/HTTP servers used to rank recall by word overlap because standing up
|
|
5
|
+
* the embedding model was assumed to mean standing up WebGPU. It does not:
|
|
6
|
+
* `EvermindLM` is a pure-CPU model and `EvermindLM.embed()` mean-pools + L2-
|
|
7
|
+
* normalises the same hidden state the GPU `HybridMambaModel.embed()` does, to the
|
|
8
|
+
* same contract. So a headless Node process can embed — it just needs the
|
|
9
|
+
* checkpoint and the tokenizer that produced its token ids.
|
|
10
|
+
*
|
|
11
|
+
* This module is the loader for that pair, plus the ONE thing a per-session
|
|
12
|
+
* subprocess needs to make embedding affordable: a vector cache that OUTLIVES the
|
|
13
|
+
* process. Recall embeds the query plus every stored memory; recomputing 200
|
|
14
|
+
* memory embeddings on the first recall of every session would cost more than the
|
|
15
|
+
* lexical ranking it replaces. The cache is stamped with the model's fingerprint,
|
|
16
|
+
* so an adapted checkpoint invalidates it wholesale rather than silently mixing
|
|
17
|
+
* vectors from two different models.
|
|
18
|
+
*
|
|
19
|
+
* It mirrors how this package already persists memories: a JSON file alongside the
|
|
20
|
+
* memory snapshot, written by the process that owns it, tolerant of a corrupt or
|
|
21
|
+
* absent file (drop it and recompute — a cache is never the source of truth).
|
|
22
|
+
*/
|
|
23
|
+
/** The seam `MemoryStore.recallRanked(query, k, runtime)` consumes. */
|
|
24
|
+
export interface TextEmbedderLike {
|
|
25
|
+
embed(text: string): Promise<Float32Array>;
|
|
26
|
+
/** Vector width. */
|
|
27
|
+
readonly dimensions: number;
|
|
28
|
+
/** Model identity — vectors are only comparable within one fingerprint. */
|
|
29
|
+
readonly fingerprint: string;
|
|
30
|
+
}
|
|
31
|
+
/** A text embedder that also owns a persistent cache. */
|
|
32
|
+
export interface PersistentTextEmbedder extends TextEmbedderLike {
|
|
33
|
+
/** Write any pending vectors to disk now. Safe to call repeatedly. */
|
|
34
|
+
flush(): void;
|
|
35
|
+
}
|
|
36
|
+
export interface EvermindEmbedderOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Absolute path to a `.evermind` package holding an `evermind-lm` checkpoint.
|
|
39
|
+
* This is the model whose hidden state becomes the embedding.
|
|
40
|
+
*/
|
|
41
|
+
modelFile: string;
|
|
42
|
+
/**
|
|
43
|
+
* Absolute path to the tokenizer JSON that produced the checkpoint's token ids
|
|
44
|
+
* (`BPETokenizer.toObject()`, or a Hugging Face `tokenizer.json`).
|
|
45
|
+
*
|
|
46
|
+
* Only needed for a package that does NOT embed its own tokenizer. Token ids are
|
|
47
|
+
* meaningless without the exact vocabulary behind them, so an `.evermind` built
|
|
48
|
+
* with `PackageMeta.tokenizer` carries one and is self-contained — that is
|
|
49
|
+
* preferred over any file, because a file can be the WRONG vocabulary while the
|
|
50
|
+
* embedded one is checksummed and vocab-size-checked against the checkpoint.
|
|
51
|
+
* Resolution order: the package's own tokenizer, then this path, then a sibling
|
|
52
|
+
* `<modelFile>.tokenizer.json`.
|
|
53
|
+
*/
|
|
54
|
+
tokenizerFile?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Absolute path for the persistent vector cache. Omit to disable persistence
|
|
57
|
+
* (vectors are still cached in memory for the life of the process).
|
|
58
|
+
*/
|
|
59
|
+
cacheFile?: string;
|
|
60
|
+
/** Max vectors retained in the cache (LRU past this). Default 4000. */
|
|
61
|
+
maxEntries?: number;
|
|
62
|
+
/** Debounce before a dirty cache is written back, ms. Default 500. */
|
|
63
|
+
flushDelayMs?: number;
|
|
64
|
+
}
|
|
65
|
+
type FsLike = {
|
|
66
|
+
readFileSync(path: string, enc: "utf8"): string;
|
|
67
|
+
readFileSync(path: string): Uint8Array;
|
|
68
|
+
writeFileSync(path: string, data: string): void;
|
|
69
|
+
existsSync(path: string): boolean;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Build a CPU text embedder from a `.evermind` package + its tokenizer, wrapped in
|
|
73
|
+
* a persistent vector cache.
|
|
74
|
+
*
|
|
75
|
+
* Returns `null` — never throws — when the engine peer is absent, a file is
|
|
76
|
+
* missing, or the package is not an `evermind-lm`. Recall then stays on the
|
|
77
|
+
* lexical ranking and says so, which is the correct degrade: a memory server that
|
|
78
|
+
* refuses to start because an OPTIONAL model is unavailable is worse than one that
|
|
79
|
+
* ranks by word overlap.
|
|
80
|
+
*/
|
|
81
|
+
export declare function createEvermindEmbedder(opts: EvermindEmbedderOptions): Promise<PersistentTextEmbedder | null>;
|
|
82
|
+
/**
|
|
83
|
+
* Wraps a text embedder with an LRU vector cache mirrored to a JSON file.
|
|
84
|
+
*
|
|
85
|
+
* Exported for hosts that already hold an embedder (a GPU runtime, say) and want
|
|
86
|
+
* the same cross-process cache — there is one implementation of "cache these
|
|
87
|
+
* vectors on disk", not one per embedder.
|
|
88
|
+
*/
|
|
89
|
+
export declare class CachedTextEmbedder implements PersistentTextEmbedder {
|
|
90
|
+
private readonly inner;
|
|
91
|
+
private readonly fs;
|
|
92
|
+
readonly dimensions: number;
|
|
93
|
+
readonly fingerprint: string;
|
|
94
|
+
/** text → vector. Map insertion order is the LRU order. */
|
|
95
|
+
private readonly cache;
|
|
96
|
+
private readonly file;
|
|
97
|
+
private readonly maxEntries;
|
|
98
|
+
private readonly flushDelayMs;
|
|
99
|
+
private dirty;
|
|
100
|
+
private timer;
|
|
101
|
+
constructor(inner: TextEmbedderLike, fs: FsLike, opts: Pick<EvermindEmbedderOptions, "cacheFile" | "maxEntries" | "flushDelayMs">);
|
|
102
|
+
embed(text: string): Promise<Float32Array>;
|
|
103
|
+
/** Write pending vectors now, cancelling any scheduled write. */
|
|
104
|
+
flush(): void;
|
|
105
|
+
private put;
|
|
106
|
+
/**
|
|
107
|
+
* Debounce the write-back: one recall embeds the query plus every new memory,
|
|
108
|
+
* and that burst should cost ONE file write, not one per vector.
|
|
109
|
+
*/
|
|
110
|
+
private schedule;
|
|
111
|
+
/**
|
|
112
|
+
* Read the cache back. A cache stamped with a DIFFERENT fingerprint is from a
|
|
113
|
+
* different (or adapted) model, so its vectors are not comparable with this
|
|
114
|
+
* model's — it is dropped whole rather than mixed in.
|
|
115
|
+
*/
|
|
116
|
+
private hydrate;
|
|
117
|
+
}
|
|
118
|
+
export {};
|
|
119
|
+
//# sourceMappingURL=evermind-embedder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evermind-embedder.d.ts","sourceRoot":"","sources":["../../src/embedding/evermind-embedder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,uEAAuE;AACvE,MAAM,WAAW,gBAAgB;IAC7B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAC3C,oBAAoB;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAChC;AAED,yDAAyD;AACzD,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC5D,sEAAsE;IACtE,KAAK,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACpC;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAkBD,KAAK,MAAM,GAAG;IACV,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAChD,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IACvC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CACrC,CAAC;AA+BF;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CACxC,IAAI,EAAE,uBAAuB,GAC9B,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAgCxC;AAED;;;;;;GAMG;AACH,qBAAa,kBAAmB,YAAW,sBAAsB;IAazD,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,EAAE;IAbvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,2DAA2D;IAC3D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmC;IACzD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;IAC1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,KAAK,CAA8C;gBAGtC,KAAK,EAAE,gBAAgB,EACvB,EAAE,EAAE,MAAM,EAC3B,IAAI,EAAE,IAAI,CAAC,uBAAuB,EAAE,WAAW,GAAG,YAAY,GAAG,cAAc,CAAC;IAU9E,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAahD,iEAAiE;IACjE,KAAK,IAAI,IAAI;IAmBb,OAAO,CAAC,GAAG;IAUX;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAUhB;;;;OAIG;IACH,OAAO,CAAC,OAAO;CAelB"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* evermind-embedder — SSM-embedding recall for a HEADLESS host, with no GPU.
|
|
3
|
+
*
|
|
4
|
+
* The stdio/HTTP servers used to rank recall by word overlap because standing up
|
|
5
|
+
* the embedding model was assumed to mean standing up WebGPU. It does not:
|
|
6
|
+
* `EvermindLM` is a pure-CPU model and `EvermindLM.embed()` mean-pools + L2-
|
|
7
|
+
* normalises the same hidden state the GPU `HybridMambaModel.embed()` does, to the
|
|
8
|
+
* same contract. So a headless Node process can embed — it just needs the
|
|
9
|
+
* checkpoint and the tokenizer that produced its token ids.
|
|
10
|
+
*
|
|
11
|
+
* This module is the loader for that pair, plus the ONE thing a per-session
|
|
12
|
+
* subprocess needs to make embedding affordable: a vector cache that OUTLIVES the
|
|
13
|
+
* process. Recall embeds the query plus every stored memory; recomputing 200
|
|
14
|
+
* memory embeddings on the first recall of every session would cost more than the
|
|
15
|
+
* lexical ranking it replaces. The cache is stamped with the model's fingerprint,
|
|
16
|
+
* so an adapted checkpoint invalidates it wholesale rather than silently mixing
|
|
17
|
+
* vectors from two different models.
|
|
18
|
+
*
|
|
19
|
+
* It mirrors how this package already persists memories: a JSON file alongside the
|
|
20
|
+
* memory snapshot, written by the process that owns it, tolerant of a corrupt or
|
|
21
|
+
* absent file (drop it and recompute — a cache is never the source of truth).
|
|
22
|
+
*/
|
|
23
|
+
const DEFAULT_MAX_ENTRIES = 4000;
|
|
24
|
+
const DEFAULT_FLUSH_DELAY_MS = 500;
|
|
25
|
+
/** Indirect import so the optional engine peer is never statically resolved. */
|
|
26
|
+
function dynamicImport(m) {
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
|
|
28
|
+
return new Function("m", "return import(m)")(m);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Load the tokenizer spec from `file`, accepting either `BPETokenizer.toObject()`
|
|
32
|
+
* output or a Hugging Face `tokenizer.json`.
|
|
33
|
+
*/
|
|
34
|
+
function loadCodec(engine, fs, file) {
|
|
35
|
+
const spec = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
36
|
+
const tok = new engine.BPETokenizer();
|
|
37
|
+
if (spec.vocab && spec.merges)
|
|
38
|
+
tok.loadFromSpec(spec);
|
|
39
|
+
else
|
|
40
|
+
tok.loadHuggingFace(spec);
|
|
41
|
+
return tok;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build a CPU text embedder from a `.evermind` package + its tokenizer, wrapped in
|
|
45
|
+
* a persistent vector cache.
|
|
46
|
+
*
|
|
47
|
+
* Returns `null` — never throws — when the engine peer is absent, a file is
|
|
48
|
+
* missing, or the package is not an `evermind-lm`. Recall then stays on the
|
|
49
|
+
* lexical ranking and says so, which is the correct degrade: a memory server that
|
|
50
|
+
* refuses to start because an OPTIONAL model is unavailable is worse than one that
|
|
51
|
+
* ranks by word overlap.
|
|
52
|
+
*/
|
|
53
|
+
export async function createEvermindEmbedder(opts) {
|
|
54
|
+
try {
|
|
55
|
+
const engine = (await dynamicImport("@seanhogg/builderforce-memory-engine"));
|
|
56
|
+
if (!engine?.EvermindModelPackage || !engine.BPETokenizer || !engine.EvermindTextEmbedder)
|
|
57
|
+
return null;
|
|
58
|
+
const fs = (await dynamicImport("node:fs"));
|
|
59
|
+
if (!fs.existsSync(opts.modelFile))
|
|
60
|
+
return null;
|
|
61
|
+
const bytes = fs.readFileSync(opts.modelFile);
|
|
62
|
+
const blob = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
63
|
+
const pkg = engine.EvermindModelPackage.fromBlob(blob);
|
|
64
|
+
if (pkg.manifest.modelType !== "evermind-lm")
|
|
65
|
+
return null;
|
|
66
|
+
// Prefer the package's OWN tokenizer. It is checksummed and cross-checked
|
|
67
|
+
// against the checkpoint's vocab size at package time, so it cannot be the
|
|
68
|
+
// wrong vocabulary — which a separate file silently can be. Falling back to a
|
|
69
|
+
// file keeps every package written before the section existed working.
|
|
70
|
+
const embedded = pkg.loadTokenizer?.() ?? null;
|
|
71
|
+
let codec;
|
|
72
|
+
if (embedded) {
|
|
73
|
+
codec = embedded;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const tokenizerFile = opts.tokenizerFile ?? `${opts.modelFile}.tokenizer.json`;
|
|
77
|
+
if (!fs.existsSync(tokenizerFile))
|
|
78
|
+
return null;
|
|
79
|
+
codec = loadCodec(engine, fs, tokenizerFile);
|
|
80
|
+
}
|
|
81
|
+
const inner = new engine.EvermindTextEmbedder(pkg.loadLM(), codec);
|
|
82
|
+
return new CachedTextEmbedder(inner, fs, opts);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Any failure to stand the model up is a degrade, not a fault.
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Wraps a text embedder with an LRU vector cache mirrored to a JSON file.
|
|
91
|
+
*
|
|
92
|
+
* Exported for hosts that already hold an embedder (a GPU runtime, say) and want
|
|
93
|
+
* the same cross-process cache — there is one implementation of "cache these
|
|
94
|
+
* vectors on disk", not one per embedder.
|
|
95
|
+
*/
|
|
96
|
+
export class CachedTextEmbedder {
|
|
97
|
+
inner;
|
|
98
|
+
fs;
|
|
99
|
+
dimensions;
|
|
100
|
+
fingerprint;
|
|
101
|
+
/** text → vector. Map insertion order is the LRU order. */
|
|
102
|
+
cache = new Map();
|
|
103
|
+
file;
|
|
104
|
+
maxEntries;
|
|
105
|
+
flushDelayMs;
|
|
106
|
+
dirty = false;
|
|
107
|
+
timer = null;
|
|
108
|
+
constructor(inner, fs, opts) {
|
|
109
|
+
this.inner = inner;
|
|
110
|
+
this.fs = fs;
|
|
111
|
+
this.dimensions = inner.dimensions;
|
|
112
|
+
this.fingerprint = inner.fingerprint;
|
|
113
|
+
this.file = opts.cacheFile;
|
|
114
|
+
this.maxEntries = Math.max(1, opts.maxEntries ?? DEFAULT_MAX_ENTRIES);
|
|
115
|
+
this.flushDelayMs = Math.max(0, opts.flushDelayMs ?? DEFAULT_FLUSH_DELAY_MS);
|
|
116
|
+
this.hydrate();
|
|
117
|
+
}
|
|
118
|
+
async embed(text) {
|
|
119
|
+
const hit = this.cache.get(text);
|
|
120
|
+
if (hit) {
|
|
121
|
+
// LRU touch — re-insert so this entry becomes most-recently-used.
|
|
122
|
+
this.cache.delete(text);
|
|
123
|
+
this.cache.set(text, hit);
|
|
124
|
+
return hit;
|
|
125
|
+
}
|
|
126
|
+
const vec = await this.inner.embed(text);
|
|
127
|
+
this.put(text, vec);
|
|
128
|
+
return vec;
|
|
129
|
+
}
|
|
130
|
+
/** Write pending vectors now, cancelling any scheduled write. */
|
|
131
|
+
flush() {
|
|
132
|
+
if (this.timer) {
|
|
133
|
+
clearTimeout(this.timer);
|
|
134
|
+
this.timer = null;
|
|
135
|
+
}
|
|
136
|
+
if (!this.dirty || !this.file)
|
|
137
|
+
return;
|
|
138
|
+
const payload = {
|
|
139
|
+
fingerprint: this.fingerprint,
|
|
140
|
+
dimensions: this.dimensions,
|
|
141
|
+
vectors: [...this.cache.entries()].map(([text, v]) => [text, Array.from(v)]),
|
|
142
|
+
};
|
|
143
|
+
try {
|
|
144
|
+
this.fs.writeFileSync(this.file, JSON.stringify(payload));
|
|
145
|
+
this.dirty = false;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// A cache that cannot be written is still a working in-memory cache.
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
put(text, vec) {
|
|
152
|
+
if (this.cache.size >= this.maxEntries) {
|
|
153
|
+
const lru = this.cache.keys().next().value;
|
|
154
|
+
if (lru !== undefined)
|
|
155
|
+
this.cache.delete(lru);
|
|
156
|
+
}
|
|
157
|
+
this.cache.set(text, vec);
|
|
158
|
+
this.dirty = true;
|
|
159
|
+
this.schedule();
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Debounce the write-back: one recall embeds the query plus every new memory,
|
|
163
|
+
* and that burst should cost ONE file write, not one per vector.
|
|
164
|
+
*/
|
|
165
|
+
schedule() {
|
|
166
|
+
if (!this.file || this.timer)
|
|
167
|
+
return;
|
|
168
|
+
this.timer = setTimeout(() => {
|
|
169
|
+
this.timer = null;
|
|
170
|
+
this.flush();
|
|
171
|
+
}, this.flushDelayMs);
|
|
172
|
+
// Never hold the process open for a cache write.
|
|
173
|
+
this.timer.unref?.();
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Read the cache back. A cache stamped with a DIFFERENT fingerprint is from a
|
|
177
|
+
* different (or adapted) model, so its vectors are not comparable with this
|
|
178
|
+
* model's — it is dropped whole rather than mixed in.
|
|
179
|
+
*/
|
|
180
|
+
hydrate() {
|
|
181
|
+
if (!this.file || !this.fs.existsSync(this.file))
|
|
182
|
+
return;
|
|
183
|
+
let parsed;
|
|
184
|
+
try {
|
|
185
|
+
parsed = JSON.parse(this.fs.readFileSync(this.file, "utf8"));
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (!parsed || parsed.fingerprint !== this.fingerprint || !Array.isArray(parsed.vectors))
|
|
191
|
+
return;
|
|
192
|
+
for (const row of parsed.vectors) {
|
|
193
|
+
if (!Array.isArray(row) || typeof row[0] !== "string" || !Array.isArray(row[1]))
|
|
194
|
+
continue;
|
|
195
|
+
if (row[1].length !== this.dimensions)
|
|
196
|
+
continue;
|
|
197
|
+
this.cache.set(row[0], Float32Array.from(row[1]));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=evermind-embedder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evermind-embedder.js","sourceRoot":"","sources":["../../src/embedding/evermind-embedder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* embedding — stand up the recall embedder for a headless server.
|
|
3
|
+
*
|
|
4
|
+
* Both out-of-process bins (stdio and HTTP) need the SAME decision: is an
|
|
5
|
+
* embedding model configured, and if so where do its vectors get cached? That
|
|
6
|
+
* decision lives here once, so the two servers cannot drift into ranking recall
|
|
7
|
+
* differently from one another.
|
|
8
|
+
*/
|
|
9
|
+
import { type PersistentTextEmbedder } from "./evermind-embedder.js";
|
|
10
|
+
export { createEvermindEmbedder, CachedTextEmbedder } from "./evermind-embedder.js";
|
|
11
|
+
export type { EvermindEmbedderOptions, PersistentTextEmbedder, TextEmbedderLike } from "./evermind-embedder.js";
|
|
12
|
+
/** Absolute path to a `.evermind` package (an `evermind-lm`) to embed recall with. */
|
|
13
|
+
export declare const MODEL_FILE_ENV = "BUILDERFORCE_MEMORY_MODEL";
|
|
14
|
+
/** Absolute path to the tokenizer JSON matching that checkpoint. */
|
|
15
|
+
export declare const TOKENIZER_FILE_ENV = "BUILDERFORCE_MEMORY_TOKENIZER";
|
|
16
|
+
/** Absolute path for the persistent vector cache; defaults beside the snapshot. */
|
|
17
|
+
export declare const VECTOR_CACHE_ENV = "BUILDERFORCE_MEMORY_VECTORS";
|
|
18
|
+
/**
|
|
19
|
+
* Where vectors are cached by default: beside the memory snapshot, under the same
|
|
20
|
+
* name. They belong together — the cache is only meaningful for the memories in
|
|
21
|
+
* that snapshot, and pointing a second store at the same vectors would be a cache
|
|
22
|
+
* for the wrong corpus.
|
|
23
|
+
*/
|
|
24
|
+
export declare function defaultVectorCacheFile(memoryFile: string): string;
|
|
25
|
+
export interface RecallEmbedderEnv {
|
|
26
|
+
env?: Record<string, string | undefined>;
|
|
27
|
+
/** The snapshot path in effect — used to site the default vector cache. */
|
|
28
|
+
memoryFile: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The embedder this server should rank recall with, or `null` when none is
|
|
32
|
+
* configured (recall then ranks lexically and says so).
|
|
33
|
+
*
|
|
34
|
+
* Opt-in by design: no model path, no model. A memory server must start and serve
|
|
35
|
+
* recall on a host that has never downloaded a checkpoint.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createRecallEmbedder(opts: RecallEmbedderEnv): Promise<PersistentTextEmbedder | null>;
|
|
38
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/embedding/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAA0B,KAAK,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAE7F,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACpF,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAEhH,sFAAsF;AACtF,eAAO,MAAM,cAAc,8BAA8B,CAAC;AAC1D,oEAAoE;AACpE,eAAO,MAAM,kBAAkB,kCAAkC,CAAC;AAClE,mFAAmF;AACnF,eAAO,MAAM,gBAAgB,gCAAgC,CAAC;AAE9D;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,MAAM,WAAW,iBAAiB;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAS1G"}
|