@avantf/dsh-mem 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/README.md +89 -28
  2. package/cordis.patch.yml +17 -0
  3. package/lib/client.js +155 -14
  4. package/lib/engine/mem/config/config_files.d.ts +26 -0
  5. package/lib/engine/mem/config/domains.d.ts +20 -0
  6. package/lib/engine/mem/config/loader.d.ts +35 -0
  7. package/lib/engine/mem/config/paths.d.ts +53 -0
  8. package/lib/engine/mem/db/chunk.d.ts +26 -0
  9. package/lib/engine/mem/db/conn.d.ts +13 -0
  10. package/lib/engine/mem/db/dao/chunks.d.ts +150 -0
  11. package/lib/engine/mem/db/dao/contradictions.d.ts +98 -0
  12. package/lib/engine/mem/db/dao/documents.d.ts +51 -0
  13. package/lib/engine/mem/db/dao/entities.d.ts +67 -0
  14. package/lib/engine/mem/db/dao/facts.d.ts +472 -0
  15. package/lib/engine/mem/db/dao/stats.d.ts +14 -0
  16. package/lib/engine/mem/db/dao/triples.d.ts +80 -0
  17. package/lib/engine/mem/db/hash.d.ts +2 -0
  18. package/lib/engine/mem/db/knowledge.d.ts +17 -0
  19. package/lib/engine/mem/db/migrations.d.ts +71 -0
  20. package/lib/engine/mem/db/port.d.ts +57 -0
  21. package/lib/engine/mem/db/schema.d.ts +26 -0
  22. package/lib/engine/mem/db/sqlite.d.ts +89 -0
  23. package/lib/engine/mem/db/store.d.ts +35 -0
  24. package/lib/engine/mem/db/tokenizer.d.ts +61 -0
  25. package/lib/engine/mem/db/vectors.d.ts +50 -0
  26. package/lib/engine/mem/dispatch.d.ts +51 -0
  27. package/lib/engine/mem/entities/extract.d.ts +84 -0
  28. package/lib/engine/mem/eval/loader.d.ts +19 -0
  29. package/lib/engine/mem/eval/metrics.d.ts +25 -0
  30. package/lib/engine/mem/eval/runner.d.ts +10 -0
  31. package/lib/engine/mem/eval/write_metrics.d.ts +88 -0
  32. package/lib/engine/mem/git.d.ts +73 -0
  33. package/lib/engine/mem/hrr/algebra.d.ts +30 -0
  34. package/lib/engine/mem/hrr/atoms.d.ts +22 -0
  35. package/lib/engine/mem/hrr/encode.d.ts +32 -0
  36. package/lib/engine/mem/hrr/index.d.ts +3 -0
  37. package/lib/engine/mem/index.d.ts +27 -0
  38. package/lib/engine/mem/lifecycle/contradiction.d.ts +156 -0
  39. package/lib/engine/mem/lifecycle/index.d.ts +5 -0
  40. package/lib/engine/mem/lifecycle/maintenance.d.ts +48 -0
  41. package/lib/engine/mem/lifecycle/presence.d.ts +29 -0
  42. package/lib/engine/mem/lifecycle/tick.d.ts +43 -0
  43. package/lib/engine/mem/lifecycle/trust.d.ts +90 -0
  44. package/lib/engine/mem/modelBootstrap.d.ts +207 -0
  45. package/lib/engine/mem/router.d.ts +51 -0
  46. package/lib/engine/mem/runtime.d.ts +232 -0
  47. package/lib/engine/mem/store/common.d.ts +82 -0
  48. package/lib/engine/mem/store/doc_files.d.ts +125 -0
  49. package/lib/engine/mem/store/document_text.d.ts +47 -0
  50. package/lib/engine/mem/store/floors.d.ts +191 -0
  51. package/lib/engine/mem/store/hybrid.d.ts +232 -0
  52. package/lib/engine/mem/store/ingest_guard.d.ts +25 -0
  53. package/lib/engine/mem/store/knowledge.d.ts +483 -0
  54. package/lib/engine/mem/store/lexical.d.ts +67 -0
  55. package/lib/engine/mem/store/memory.d.ts +543 -0
  56. package/lib/engine/mem/store/source_picker.d.ts +43 -0
  57. package/lib/engine/mem-contract/config.d.ts +129 -0
  58. package/lib/engine/mem-contract/env.d.ts +31 -0
  59. package/lib/engine/mem-contract/family.d.ts +25 -0
  60. package/lib/engine/mem-contract/index.d.ts +8 -0
  61. package/lib/engine/mem-contract/log.d.ts +29 -0
  62. package/lib/engine/mem-contract/remote.d.ts +80 -0
  63. package/lib/engine/mem-contract/text.d.ts +92 -0
  64. package/lib/engine/mem-contract/tools.d.ts +284 -0
  65. package/lib/engine/mem-contract/types.d.ts +615 -0
  66. package/lib/engine/mem-retrieval/adapters/auto_vstore.d.ts +47 -0
  67. package/lib/engine/mem-retrieval/adapters/hnswlib.d.ts +174 -0
  68. package/lib/engine/mem-retrieval/adapters/local_bge.d.ts +121 -0
  69. package/lib/engine/mem-retrieval/adapters/local_numpy.d.ts +28 -0
  70. package/lib/engine/mem-retrieval/adapters/local_reranker.d.ts +52 -0
  71. package/lib/engine/mem-retrieval/adapters/none_reranker.d.ts +10 -0
  72. package/lib/engine/mem-retrieval/adapters/warm_gate.d.ts +46 -0
  73. package/lib/engine/mem-retrieval/budget.d.ts +30 -0
  74. package/lib/engine/mem-retrieval/fusion.d.ts +53 -0
  75. package/lib/engine/mem-retrieval/index.d.ts +14 -0
  76. package/lib/engine/mem-retrieval/interfaces.d.ts +82 -0
  77. package/lib/engine/mem-retrieval/log.d.ts +11 -0
  78. package/lib/engine/mem-retrieval/registry.d.ts +23 -0
  79. package/lib/engine/mem-retrieval/rerank.d.ts +13 -0
  80. package/lib/engine/mem-retrieval/stats.d.ts +64 -0
  81. package/lib/engine/mem-retrieval/text_budget.d.ts +52 -0
  82. package/lib/index.js +1773 -473
  83. package/lib/interface-version.json +2 -2
  84. package/lib/types/client/domains.d.ts +5 -4
  85. package/lib/types/client/domains.d.ts.map +1 -1
  86. package/lib/types/client/index.d.ts.map +1 -1
  87. package/lib/types/client/ingest.d.ts +1 -1
  88. package/lib/types/client/wire.d.ts +36 -0
  89. package/lib/types/client/wire.d.ts.map +1 -0
  90. package/lib/types/envinit-bootstrap.d.ts +3 -2
  91. package/lib/types/envinit.d.ts +2 -19
  92. package/lib/types/envinit.d.ts.map +1 -1
  93. package/lib/types/hints.d.ts +1 -1
  94. package/lib/types/index.d.ts +8 -3
  95. package/lib/types/index.d.ts.map +1 -1
  96. package/lib/types/open.d.ts +61 -11
  97. package/lib/types/open.d.ts.map +1 -1
  98. package/lib/types/prompt.d.ts +19 -1
  99. package/lib/types/prompt.d.ts.map +1 -1
  100. package/lib/types/provision.d.ts +1 -1
  101. package/lib/types/provision.d.ts.map +1 -1
  102. package/lib/types/reconcile.d.ts +64 -0
  103. package/lib/types/reconcile.d.ts.map +1 -0
  104. package/lib/types/remote.d.ts +53 -0
  105. package/lib/types/remote.d.ts.map +1 -1
  106. package/lib/types/render.d.ts +35 -0
  107. package/lib/types/render.d.ts.map +1 -0
  108. package/lib/types/tool_schema.d.ts +3 -13
  109. package/lib/types/tool_schema.d.ts.map +1 -1
  110. package/package.json +24 -15
  111. package/lib/client.js.map +0 -1
  112. package/lib/types/client/domains.js +0 -25
  113. package/lib/types/client/domains.js.map +0 -1
  114. package/lib/types/client/index.js +0 -1371
  115. package/lib/types/client/index.js.map +0 -1
  116. package/lib/types/client/ingest.js +0 -30
  117. package/lib/types/client/ingest.js.map +0 -1
  118. package/lib/types/client/paging.js +0 -36
  119. package/lib/types/client/paging.js.map +0 -1
  120. package/lib/types/client/summarize.js +0 -79
  121. package/lib/types/client/summarize.js.map +0 -1
  122. package/lib/types/data_home.js +0 -26
  123. package/lib/types/data_home.js.map +0 -1
  124. package/lib/types/envinit-bootstrap.js +0 -333
  125. package/lib/types/envinit.js +0 -402
  126. package/lib/types/envinit.js.map +0 -1
  127. package/lib/types/hints.js +0 -53
  128. package/lib/types/hints.js.map +0 -1
  129. package/lib/types/index.js +0 -755
  130. package/lib/types/index.js.map +0 -1
  131. package/lib/types/inject.js +0 -14
  132. package/lib/types/inject.js.map +0 -1
  133. package/lib/types/interface_gate.js +0 -84
  134. package/lib/types/interface_gate.js.map +0 -1
  135. package/lib/types/open.js +0 -133
  136. package/lib/types/open.js.map +0 -1
  137. package/lib/types/prompt.js +0 -148
  138. package/lib/types/prompt.js.map +0 -1
  139. package/lib/types/provision.js +0 -177
  140. package/lib/types/provision.js.map +0 -1
  141. package/lib/types/remote.js +0 -247
  142. package/lib/types/remote.js.map +0 -1
  143. package/lib/types/tool_schema.js +0 -149
  144. package/lib/types/tool_schema.js.map +0 -1
@@ -0,0 +1,174 @@
1
+ import type { VectorStore } from '../interfaces.js';
2
+ /**
3
+ * Search-time beam width, when the config does not say otherwise.
4
+ *
5
+ * hnswlib defaults `ef` to the value passed to `initIndex` (10 here), and that default is a
6
+ * QUALITY CLIFF rather than a neutral choice. Measured through this adapter (2000 random unit
7
+ * vectors, dim 512, true top-10 as ground truth):
8
+ *
9
+ * ef 10 64 128 256 400
10
+ * R@10 0.45 0.78 0.93 1.00 1.00
11
+ * ms 0.10 0.36 0.53 0.74 0.90
12
+ *
13
+ * Recall falls as the corpus grows (at n=8000, R@50 was 0.89 even at `ef = 400`), and random
14
+ * vectors are the WORST case — real embeddings cluster, so production recall is higher than
15
+ * these numbers. 256 keeps the small/medium corpora exact without giving up the speedup; the
16
+ * knob is `vectorStore.hnswlib_ef_search`.
17
+ */
18
+ export declare const DEFAULT_EF_SEARCH = 256;
19
+ /**
20
+ * `hnswlib` — real ANN backend via `hnswlib-node` (cosine space, HierarchicalNSW).
21
+ * Uses the stable v2 string-space + initIndex + Array API. Wraps every native call
22
+ * in a try/catch and falls back to the in-memory brute-force store so the plugin
23
+ * never crashes on a pathological native binding — and a fallback that is TAKEN is
24
+ * always logged, because "never crashes" must not become "silently returns nothing".
25
+ */
26
+ export declare class HnswlibVectorStore implements VectorStore {
27
+ readonly name = "hnswlib";
28
+ readonly dim: number;
29
+ private lib;
30
+ private fallback;
31
+ private readonly vectors;
32
+ /**
33
+ * The beam width in effect, and the index it was applied to: `ef` is in force on `efOn`.
34
+ *
35
+ * The index is part of the key, not just the number. `reindex()` and `rebuild()` REPLACE the
36
+ * native index, and a replacement starts at hnswlib's own default `ef` (10), so a bare number
37
+ * would compare equal and skip the re-apply — silently dropping ANN recall to ~0.44 of the
38
+ * true top-k after any batch eviction, which is the cliff this whole knob exists to avoid.
39
+ */
40
+ private efOn;
41
+ private ef;
42
+ private readonly efSearch;
43
+ /**
44
+ * Elements in the native graph, INCLUDING the ones `markDelete` has tombstoned.
45
+ *
46
+ * The live set is `vectors` (authoritative for `count`/`fetch`), so the number of dead points is
47
+ * `graphElements - vectors.size` — a derived quantity rather than a set of ids, because the graph
48
+ * OUTLIVES the process: a snapshot restored from disk already contains every dead point earlier
49
+ * sessions created, and a per-session `Set` could not see them (measured across 4 sessions of
50
+ * 100 evictions: live 1200→800 while the graph stayed at 1200, with the counter reading 100).
51
+ *
52
+ * Bookkeeping: a snapshot restore reads `getIdsList().length`; a build from rows sets it to the
53
+ * row count; `add` increments it only when the id was NOT already in the graph (an update in
54
+ * place, which also un-deletes a tombstone, does not grow the graph).
55
+ */
56
+ private graphElements;
57
+ /** Snapshot path PREFIX (set by the owning store via {@link attachPersistence}); see `snapshotPath`. */
58
+ private cachePath;
59
+ /** Something changed since the snapshot was written. */
60
+ private dirty;
61
+ private restores;
62
+ private rebuilds;
63
+ constructor(dim: number, efSearch?: number);
64
+ add(id: number, vec: Float32Array): void;
65
+ /** Whether the real hnswlib native index is active (false = brute-force fallback). */
66
+ get native(): boolean;
67
+ /**
68
+ * Counters a caller (test, benchmark, diagnostics) can assert on without guessing from timing:
69
+ * `restores` = snapshots loaded, `rebuilds` = full native builds, `tombstones` = pending deletes.
70
+ */
71
+ stats(): {
72
+ live: number;
73
+ tombstones: number;
74
+ graphElements: number;
75
+ restores: number;
76
+ rebuilds: number;
77
+ native: boolean;
78
+ };
79
+ /** Point the native index at an on-disk snapshot (see {@link VectorStore.attachPersistence}). */
80
+ attachPersistence(path: string): void;
81
+ /**
82
+ * The snapshot file for a row set: `prefix.<fingerprint>.hnsw`.
83
+ *
84
+ * Putting the fingerprint in the NAME is what makes the commit atomic. The previous shape wrote
85
+ * two files (index, then a JSON sidecar with the fingerprint) and validated the sidecar against
86
+ * the CURRENT rows — so an interleaved pair `(index_B, meta_A)` could describe two different
87
+ * corpora and still pass the check (the metadata was never bound to the graph it accompanied).
88
+ * With one file there is nothing to desynchronize: a snapshot either exists under the name of the
89
+ * row set it was built from, or it does not exist at all.
90
+ */
91
+ private snapshotPath;
92
+ /**
93
+ * Widen the search beam, skipping the native call only when this exact width is already in
94
+ * force on THIS index (see {@link efOn}).
95
+ */
96
+ private setEf;
97
+ private rebuildFallback;
98
+ topk(vec: Float32Array, k: number): {
99
+ id: number;
100
+ score: number;
101
+ }[];
102
+ fetch(ids: number[]): Map<number, Float32Array>;
103
+ remove(id: number): void;
104
+ /**
105
+ * Batch removal via TOMBSTONES — O(k), not O(N).
106
+ *
107
+ * `markDelete` excludes a label from `searchKnn` without touching the graph (measured: 1000
108
+ * tombstones = 0.19 ms on an 8000-vector index, against 1.3 s for the rebuild this used to
109
+ * do). Deleted points are still traversed and still occupy space, so {@link maybeCompact}
110
+ * rebuilds once tombstones become a meaningful fraction of the corpus.
111
+ */
112
+ removeMany(ids: number[]): void;
113
+ /**
114
+ * Rebuild once the dead points are worth a full pass (see {@link COMPACT_RATIO}).
115
+ *
116
+ * The count is DERIVED from the graph, so it includes what earlier sessions left behind: that is
117
+ * what makes accumulation across CLI invocations self-correcting instead of monotonic.
118
+ */
119
+ private maybeCompact;
120
+ /**
121
+ * Recreate the serving index (native when possible) from the tracked vectors.
122
+ *
123
+ * Public because it is the "compact now" operation: it drops every tombstone and leaves a
124
+ * clean graph. Called by `maybeCompact()` and by `rebuild()`'s failure paths.
125
+ */
126
+ compact(): void;
127
+ /** Recreate the serving index (native when possible) from the tracked vectors. */
128
+ private reindex;
129
+ count(): number;
130
+ /**
131
+ * Build from the store's rows, reusing the on-disk snapshot when it provably describes them.
132
+ *
133
+ * The snapshot check is a FINGERPRINT over the live ids plus the count, not just a count: a
134
+ * session that evicted three facts and added three others has the same count with different
135
+ * contents, and loading that would silently serve vectors that no longer exist. The path
136
+ * carries the vector space (see `attachPersistence`), so a model/dim swap cannot load at all.
137
+ */
138
+ rebuild(rows: Iterable<{
139
+ id: number;
140
+ vec: Float32Array;
141
+ }>): void;
142
+ /** Persist pending state (called on shutdown); a no-op without a cache path or change. */
143
+ flush(): void;
144
+ /**
145
+ * Load the snapshot that describes EXACTLY these rows, if it exists.
146
+ *
147
+ * There is no validity check to get wrong: the file is named by the fingerprint of the row set
148
+ * it was built from, so a snapshot for a different corpus is a different file (and simply will
149
+ * not be found). That is the whole reason the fingerprint moved into the name.
150
+ *
151
+ * `allowReplaceDeleted` is deliberately FALSE: with it enabled, `addPoint` on a label that the
152
+ * snapshot recorded as deleted throws ("Can't use addPoint to update deleted elements if
153
+ * replacement of deleted elements is enabled"), which would break the very first re-add of an
154
+ * archived-then-restored fact. Verified against the binding; deleted entries stay excluded from
155
+ * `searchKnn` either way.
156
+ */
157
+ private restore;
158
+ /** Live rows, as `fingerprintOf` wants them. */
159
+ private liveRows;
160
+ /**
161
+ * Write the native graph, atomically: a temp file plus `rename`, so a reader (the other
162
+ * process sharing this data home) sees either the old snapshot or the complete new one.
163
+ * Best-effort by design — a failing cache must never fail a write that already hit the DB.
164
+ */
165
+ private persist;
166
+ /**
167
+ * Delete snapshots for OTHER row sets of this store (best-effort).
168
+ *
169
+ * They can never be loaded again — the name IS the fingerprint — so keeping them only leaks disk.
170
+ * A concurrent process may have just written one; unlinking an open file is safe on POSIX, and
171
+ * the worst case is that the other process rebuilds next start.
172
+ */
173
+ private dropStaleSnapshots;
174
+ }
@@ -0,0 +1,121 @@
1
+ import type { SemanticBackend } from '../interfaces.js';
2
+ /**
3
+ * The injected pipeline surface: one text OR an array of texts in, a tensor out.
4
+ *
5
+ * The array form matters: transformers.js runs a batch through the model in one call (its own
6
+ * per-call overhead — session dispatch, tokenizer batching — is amortized), while a serial loop of
7
+ * single-text calls pays it per chunk. `dims` is the tensor shape (`[n, dim]`); the single-text
8
+ * form may omit it.
9
+ */
10
+ export type PipelineFn = (text: string | string[], opts?: Record<string, unknown>) => Promise<{
11
+ data: Float32Array | number[];
12
+ dims?: number[];
13
+ }>;
14
+ /** The slice of the transformers.js module this project reads and configures. */
15
+ export type TransformersModule = {
16
+ env?: Record<string, unknown>;
17
+ pipeline?: (task: string, model: string) => Promise<unknown>;
18
+ };
19
+ /** Injectable seam: tests supply a fake pipeline instead of the transformers.js import. */
20
+ export type PipeFactory = (model: string, env: ModelEnv) => Promise<PipelineFn>;
21
+ export interface ModelEnv {
22
+ /** Mirror host for model downloads (default domestic). */
23
+ mirror: string;
24
+ /** Local cache dir for downloaded weights. */
25
+ cacheDir: string;
26
+ /** Whether to auto-download on warmup. */
27
+ autoDownload: boolean;
28
+ /**
29
+ * Upper bound on tokens handed to the model; `0`/unset = auto (the loaded model's declared
30
+ * window). Past the window transformers.js truncates SILENTLY, so the adapter bounds the
31
+ * text first and logs it once (`text_budget.ts`).
32
+ */
33
+ maxInputTokens?: number;
34
+ /**
35
+ * Do NOT warm in the constructor: the first `encode()` / `encodeBatch()` warms lazily.
36
+ *
37
+ * Set when something ELSE owns the model root and will put the files there asynchronously — the
38
+ * DSH plugin does, because the family framework fetches the model as a background item. Warming
39
+ * in the constructor would race that install and let this process fetch its own copy; the caller
40
+ * warms explicitly once the item settles. Unset (the CLI / MCP / every direct construction) keeps
41
+ * the eager constructor warm.
42
+ */
43
+ deferWarm?: boolean;
44
+ }
45
+ /**
46
+ * Local BGE embedder via `@huggingface/transformers` (transformers.js, ONNX).
47
+ * Lazily imports the package; `warmUp()` (async, idempotent) sets the mirror +
48
+ * cache dir, then triggers a model load/download. Until warmup completes,
49
+ * `isAvailable()` is false and the retrieval pipeline stays degraded.
50
+ *
51
+ * A warmup that fails is not final: `ensureWarm()` lets observing call sites
52
+ * retry it cheaply (see `warm_gate.ts`), so a transient mirror outage does not
53
+ * degrade a long-lived host until the next restart.
54
+ */
55
+ export declare class LocalBgeBackend implements SemanticBackend {
56
+ readonly name = "local_bge";
57
+ readonly dim: number;
58
+ private _available;
59
+ private _pipe;
60
+ private _warming;
61
+ private _dimWarned;
62
+ private _lengthWarned;
63
+ private _window;
64
+ private readonly configuredMax;
65
+ private readonly model;
66
+ private readonly env;
67
+ private readonly pipeFactory;
68
+ private readonly gate;
69
+ constructor(model: string, dim: number, env?: Partial<ModelEnv>, pipeFactory?: PipeFactory);
70
+ isAvailable(): boolean;
71
+ /**
72
+ * Fire-and-forget retry for call sites that only observe availability. No-op
73
+ * while the model is ready or an attempt is already in flight; otherwise
74
+ * throttled by {@link WarmGate}.
75
+ */
76
+ ensureWarm(): void;
77
+ /** Idempotent warmup: configure transformers.js env and load/download the model. */
78
+ warmUp(): Promise<void>;
79
+ private _doWarmup;
80
+ /** Tell the operator whether the failure is recoverable in-process. */
81
+ private retryHint;
82
+ /**
83
+ * The model's usable input window, resolved once per process: an explicit
84
+ * `semantic.max_input_tokens` cap clamped to the loaded tokenizer's declared
85
+ * `model_max_length`, or the declared window itself when the config says "auto" (0).
86
+ */
87
+ private window;
88
+ /**
89
+ * Bound `text` to the window BEFORE it reaches transformers.js.
90
+ *
91
+ * This is the only place the guard can live: the feature-extraction pipeline's `_call`
92
+ * accepts `{pooling, normalize, quantize, precision}` and nothing else, so `max_length`
93
+ * cannot be raised or lowered from the call site — past `model_max_length` the tokenizer
94
+ * truncates and says nothing.
95
+ */
96
+ private bound;
97
+ encode(text: string): Promise<Float32Array>;
98
+ /**
99
+ * Encode several texts in ONE model call.
100
+ *
101
+ * This used to be a serial loop over `encode()`, and production code never called it — so
102
+ * ingest paid the per-call overhead once per chunk (measured: the dominant cost of `kb ingest`,
103
+ * 33.7 ms/chunk at 450 characters). Every text is bounded individually first (the window
104
+ * belongs to the model, not to the batch), and a failed batch falls back to per-text calls so
105
+ * an injected single-string pipeline (tests) and a memory-bound real batch both keep working.
106
+ */
107
+ encodeBatch(texts: string[]): Promise<Float32Array[]>;
108
+ /** One model call for a length-homogeneous group; falls back to per-text calls on failure. */
109
+ private encodeGroup;
110
+ }
111
+ /**
112
+ * Apply this project's cache/mirror policy to transformers.js.
113
+ *
114
+ * `mod.env` is PROCESS-GLOBAL and last-writer-wins: the embedder and the reranker are two adapters
115
+ * over ONE module, so whichever warms second decides what both see. Setting the keys from a single
116
+ * place is what keeps the two from disagreeing — the reranker used to set `allowLocalModels` on its
117
+ * own, which reads as a policy difference between the adapters and was really an accident of which
118
+ * file got edited last. `allowLocalModels` is already the library default; it is stated here so the
119
+ * policy is explicit rather than inherited, and identical for both adapters.
120
+ */
121
+ export declare function applyModelEnv(mod: TransformersModule, env: ModelEnv): void;
@@ -0,0 +1,28 @@
1
+ import type { VectorStore } from '../interfaces.js';
2
+ /**
3
+ * `local_numpy` — in-memory brute-force vector store.
4
+ * Default / dev backend; persists via the caller (the store DB owns the vectors).
5
+ * The fallback every backend without a real adapter resolves to; `hnswlib` is the implemented ANN
6
+ * store, while `faiss`/`pgvector`/`qdrant` warn and land here.
7
+ */
8
+ export declare class LocalNumpyVectorStore implements VectorStore {
9
+ readonly name = "local_numpy";
10
+ readonly dim: number;
11
+ private readonly vectors;
12
+ constructor(dim: number);
13
+ add(id: number, vec: Float32Array): void;
14
+ topk(vec: Float32Array, k: number): {
15
+ id: number;
16
+ score: number;
17
+ }[];
18
+ fetch(ids: number[]): Map<number, Float32Array>;
19
+ remove(id: number): void;
20
+ removeMany(ids: number[]): void;
21
+ count(): number;
22
+ /** Iterate all stored (id, vector) pairs — used by `auto` to migrate to an ANN backend. */
23
+ entries(): IterableIterator<[number, Float32Array]>;
24
+ rebuild(rows: Iterable<{
25
+ id: number;
26
+ vec: Float32Array;
27
+ }>): void;
28
+ }
@@ -0,0 +1,52 @@
1
+ import type { Reranker } from '../interfaces.js';
2
+ import { type ModelEnv } from './local_bge.js';
3
+ /** One text-classification result: transformers.js yields `{label, score}`; tolerate a raw `{data}` tensor too. */
4
+ type ClassifyResult = {
5
+ label?: string;
6
+ score?: number;
7
+ data?: number[];
8
+ };
9
+ /** The injected pipeline surface: a batch of (query, document) pairs in, scores out. */
10
+ export type ClassifyFn = (inputs: [string, string][], opts?: Record<string, unknown>) => Promise<ClassifyResult[]>;
11
+ /** Injectable seam: tests supply a fake classifier instead of the transformers.js import. */
12
+ export type RerankerPipeFactory = (model: string, env: ModelEnv) => Promise<ClassifyFn>;
13
+ /** Never hand the classifier a degenerate document; below this the score is meaningless. */
14
+ export declare const MIN_DOC_TOKENS = 16;
15
+ /**
16
+ * Local cross-encoder reranker via `@huggingface/transformers` (`bge-reranker`).
17
+ * Lazily imported; `warmUp()` configures the mirror/cache and triggers a load.
18
+ * Degrades to identity order when the package/model is not available.
19
+ *
20
+ * Like {@link LocalBgeBackend}, a failed warmup is retryable through
21
+ * `ensureWarm()` rather than being final until the next restart.
22
+ */
23
+ export declare class LocalReranker implements Reranker {
24
+ readonly name = "bge_reranker";
25
+ private _available;
26
+ private _pipe;
27
+ private _warming;
28
+ private _lengthWarned;
29
+ private _window;
30
+ private readonly configuredMax;
31
+ private readonly model;
32
+ private readonly env;
33
+ private readonly pipeFactory;
34
+ private readonly gate;
35
+ constructor(model: string, env?: Partial<ModelEnv>, pipeFactory?: RerankerPipeFactory);
36
+ isAvailable(): boolean;
37
+ /** Fire-and-forget retry for call sites that only observe availability (see `warm_gate.ts`). */
38
+ ensureWarm(): void;
39
+ warmUp(): Promise<void>;
40
+ private _doWarmup;
41
+ private window;
42
+ /**
43
+ * Bound one document for a pair, given how much of the window the query already spends.
44
+ * Returns the document text unchanged when it fits.
45
+ */
46
+ private boundDocument;
47
+ rerank(query: string, candidates: {
48
+ id: number;
49
+ text: string;
50
+ }[]): Promise<number[]>;
51
+ }
52
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { Reranker } from '../interfaces.js';
2
+ /** `none` — reranking disabled (the default). Reports unavailable so callers skip the rerank pass entirely. */
3
+ export declare class NoneReranker implements Reranker {
4
+ readonly name = "none";
5
+ isAvailable(): boolean;
6
+ rerank(_query: string, candidates: {
7
+ id: number;
8
+ text: string;
9
+ }[]): Promise<number[]>;
10
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Retry gate for the local model adapters.
3
+ *
4
+ * The model bootstrap runs exactly once, at startup (the model artifact in
5
+ * `provisionToolchainAsync`). When that single attempt fails — an unreachable
6
+ * mirror, a poisoned DNS entry, a
7
+ * proxy that is not up yet — nothing used to try again, so a long-lived host
8
+ * stayed on the FTS+entity fallback forever, even after the network recovered
9
+ * or the weights landed in the cache (`vectors_fix` kept reporting
10
+ * `semantic_available: false` on a running plugin host).
11
+ *
12
+ * Call sites that only *observe* availability (hybrid search, best-effort
13
+ * indexing) now ask for a retry through {@link WarmGate.nudge}. The gate is
14
+ * deliberately timer-free: there is no background poll, no handle to dispose,
15
+ * and no traffic while nothing is asking for semantic retrieval — recovery
16
+ * happens on the next retrieval that needs it.
17
+ */
18
+ /**
19
+ * Minimum gap between two retry attempts triggered by observing call sites.
20
+ * Comfortably above undici's 10 s connect timeout so a dead mirror is not
21
+ * re-probed on every query, and short enough that a recovered network is picked
22
+ * up without a restart.
23
+ */
24
+ export declare const ENSURE_WARM_FLOOR_MS = 30000;
25
+ /** Throttles observing-call-site retries of a failed model warmup. */
26
+ export declare class WarmGate {
27
+ private readonly canAttempt;
28
+ private lastAttemptAt;
29
+ /**
30
+ * @param canAttempt whether a retry can still help. `false` makes the gate
31
+ * inert: with downloads disabled a miss is deterministic (only a local cache
32
+ * load is possible), so retrying would only add noise.
33
+ */
34
+ constructor(canAttempt: () => boolean);
35
+ /**
36
+ * Record that an attempt actually started. Called by the adapter's warmup so
37
+ * the startup attempt also resets the floor — otherwise the first query after
38
+ * a failed boot would immediately re-attempt.
39
+ */
40
+ markAttempt(now?: number): void;
41
+ /**
42
+ * Run `attempt` unless the floor has not elapsed. Non-blocking: the caller
43
+ * passes a fire-and-forget trigger. Returns whether an attempt was started.
44
+ */
45
+ nudge(attempt: () => void, now?: number): boolean;
46
+ }
@@ -0,0 +1,30 @@
1
+ /** What the budget needs from a candidate: ranked text. */
2
+ export interface BudgetInput {
3
+ text: string;
4
+ score: number;
5
+ }
6
+ export interface BudgetOptions {
7
+ /** Total tokens to spend; `0` (or negative) means "no budget" and returns the input untouched. */
8
+ maxTokens: number;
9
+ /** Ceiling for one entry; defaults to twice the average share. */
10
+ perEntryCap?: number;
11
+ /** Token estimator override (tests, or a real tokenizer). */
12
+ estimate?: (text: string) => number;
13
+ }
14
+ export interface Budgeted<T> {
15
+ kept: T[];
16
+ used_tokens: number;
17
+ /** Entries whose text was shortened (including those left with no text at all). */
18
+ truncated: number;
19
+ }
20
+ /**
21
+ * Fit `items` into `maxTokens`, preserving their (ranked) order.
22
+ *
23
+ * Pass 1 gives every entry `min(perEntryCap, remaining)`. Pass 2 spends what is left growing the
24
+ * entries that were cut, best first. An entry that receives no budget keeps an empty `text` and
25
+ * `truncated: true` — dropping it would hide its existence, which is worse than showing a
26
+ * reference to it.
27
+ */
28
+ export declare function fitToTokenBudget<T extends BudgetInput>(items: readonly T[], opts: BudgetOptions): Budgeted<T & {
29
+ truncated?: boolean;
30
+ }>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Hybrid fusion: each path's raw scores are scaled by that path's OWN maximum, then summed with
3
+ * the configured weights and cut to the top-k.
4
+ *
5
+ * Scaling by the max (rather than min-max over the path's returned set) is what makes the result
6
+ * INVARIANT to a capped path — FOR A PATH THAT HANDS ITS ENTRIES OVER IN ITS OWN SCORE ORDER.
7
+ * The store bounds every non-semantic leg (`memory.ts` `legCap`) for cost, and a cap then removes
8
+ * that leg's weakest entries, which is exactly the minimum a min-max normalization would have
9
+ * measured against: trimming used to rescale every survivor and could reorder them (a documented,
10
+ * previously-accepted effect). With `v/max` the number is the same with or without the cap,
11
+ * because the maximum belongs to an entry a cap cannot remove.
12
+ *
13
+ * That "cannot remove" is the assumption, and the store has ONE leg that breaks it: the HRR probe.
14
+ * Its candidates arrive in another leg's order — the entity-Jaccard ratio (`candidateFactsForAnyEntity`,
15
+ * which is the JACCARD leg's score, not the phase similarity this leg scores by), or recency in the
16
+ * no-candidate fallback (see `MemoryStore.hrrPath`). Either way the cap may drop this leg's highest
17
+ * scorer and the scaling does move. Stated rather than implied, because the invariance is the whole
18
+ * reason this normalization was chosen; `docs/PROVENANCE_REVIEW.md` N5 records the missing
19
+ * differential, and `recordLegCapped()` is what makes the cap observable at all.
20
+ *
21
+ * Magnitudes are kept: a path whose scores cluster high still contributes more per entry than one
22
+ * whose scores are near zero, which is the information ranks alone would throw away.
23
+ */
24
+ export interface FusionPath {
25
+ weight: number;
26
+ /** candidate id → raw score (un-normalized). */
27
+ scores: Map<number, number>;
28
+ }
29
+ export interface Hit {
30
+ id: number;
31
+ score: number;
32
+ }
33
+ /**
34
+ * Scale raw scores by the set's maximum, into [0,1].
35
+ *
36
+ * A set whose maximum is not positive carries no ranking information, so every entry maps to 0 —
37
+ * it still participates in the pool (with no contribution) instead of being dropped, which is what
38
+ * keeps a degenerate path from emptying the result.
39
+ *
40
+ * A NON-FINITE score is treated the same way (mapped to 0, kept in the pool). It cannot come from a
41
+ * healthy leg, but a corrupt HRR blob decodes to NaN without a length check failing, and one NaN
42
+ * would otherwise do two kinds of damage: poison that candidate's merged total, and make `fuse`'s
43
+ * sort comparator return NaN — an undefined order, so the whole ranking (not just one entry)
44
+ * becomes arbitrary.
45
+ */
46
+ export declare function scaleByMax(scores: Map<number, number>): Map<number, number>;
47
+ /**
48
+ * Fuse paths by scaling each path's scores by its own maximum, summing weighted values, and
49
+ * returning the top-k descending. Ties break on ascending id so the ranking is deterministic —
50
+ * scaled scores collide far more often than min-max ones did (both legs of a pair can rank two
51
+ * candidates identically), and the previous order was Map-insertion order.
52
+ */
53
+ export declare function fuse(paths: FusionPath[], topK: number): Hit[];
@@ -0,0 +1,14 @@
1
+ export * from './interfaces.js';
2
+ export * from './log.js';
3
+ export * from './stats.js';
4
+ export * from './text_budget.js';
5
+ export * from './budget.js';
6
+ export * from './registry.js';
7
+ export * from './fusion.js';
8
+ export * from './rerank.js';
9
+ export * from './adapters/local_numpy.js';
10
+ export * from './adapters/hnswlib.js';
11
+ export * from './adapters/auto_vstore.js';
12
+ export * from './adapters/none_reranker.js';
13
+ export * from './adapters/local_bge.js';
14
+ export * from './adapters/local_reranker.js';
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The three stable contracts the business flow depends on.
3
+ * The business flow (fusion pipeline, stores, tools, router, UI) must depend only on
4
+ * these interfaces — never on a concrete backend. See DESIGN §5.
5
+ */
6
+ /** Encodes text into a normalized embedding vector. */
7
+ export interface SemanticBackend {
8
+ readonly name: string;
9
+ readonly dim: number;
10
+ encode(text: string): Promise<Float32Array>;
11
+ encodeBatch(texts: string[]): Promise<Float32Array[]>;
12
+ isAvailable(): boolean;
13
+ /** Optional async bootstrap: configure env + download/load the model. */
14
+ warmUp?(): Promise<void>;
15
+ /**
16
+ * Optional non-blocking retry for call sites that only *observe* availability
17
+ * (hybrid search, best-effort indexing): re-attempts a failed bootstrap,
18
+ * coalescing with any in-flight attempt and throttling to one attempt per
19
+ * floor. Implementations that load lazily may omit it.
20
+ */
21
+ ensureWarm?(): void;
22
+ }
23
+ /** Re-ranks candidate facts/chunks by query relevance. */
24
+ export interface Reranker {
25
+ readonly name: string;
26
+ rerank(query: string, candidates: {
27
+ id: number;
28
+ text: string;
29
+ }[]): Promise<number[]>;
30
+ isAvailable(): boolean;
31
+ /** Optional async bootstrap: configure env + download/load the model. */
32
+ warmUp?(): Promise<void>;
33
+ /** Optional non-blocking retry — see {@link SemanticBackend.ensureWarm}. */
34
+ ensureWarm?(): void;
35
+ }
36
+ /** Distance/ann index over entry vectors (per-store instance). */
37
+ export interface VectorStore {
38
+ readonly name: string;
39
+ readonly dim: number;
40
+ add(id: number, vec: Float32Array): void;
41
+ /** Cosine-similarity top-k over all stored vectors (brute force by default). */
42
+ topk(vec: Float32Array, k: number): {
43
+ id: number;
44
+ score: number;
45
+ }[];
46
+ fetch(ids: number[]): Map<number, Float32Array>;
47
+ remove(id: number): void;
48
+ /**
49
+ * Optional batch removal. A backend that cannot delete incrementally MUST implement this so
50
+ * eviction loops stay O(N) instead of O(N²); callers prefer it whenever they evict more than one
51
+ * id. (hnswlib deletes by tombstone, so its `removeMany` is O(k) — it implements this to share
52
+ * the "compact once for the whole batch" path, not because a single delete would rebuild.)
53
+ */
54
+ removeMany?(ids: number[]): void;
55
+ /**
56
+ * Optional: drop any dead/tombstoned entries the backend is carrying, now. An explicit
57
+ * "reclaim it" hook for maintenance; implementations that cannot accumulate dead state omit it.
58
+ */
59
+ compact?(): void;
60
+ count(): number;
61
+ rebuild(rows: Iterable<{
62
+ id: number;
63
+ vec: Float32Array;
64
+ }>): void;
65
+ /**
66
+ * Optional: materialize any structure the backend built LAZILY. `topk` must always be correct
67
+ * without it (it builds on demand); this exists so callers that need deterministic first-query
68
+ * latency — benchmarks, and tests that assert on build cost — can force the mission.
69
+ */
70
+ prepare?(): void;
71
+ /**
72
+ * Optional: point the backend at an on-disk cache of its native index. The path is the
73
+ * STORE's choice and must embed the vector space (model/dim), so a model swap cannot load a
74
+ * snapshot built from another space. Best-effort: a missing/corrupt/stale file is rebuilt.
75
+ */
76
+ attachPersistence?(path: string): void;
77
+ /**
78
+ * Optional: persist any state that is only in memory. Called on shutdown; implementations that
79
+ * have nothing pending may omit it.
80
+ */
81
+ flush?(): void;
82
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Process-wide logger seam for the retrieval core. The DSH plugin installs the
3
+ * host logger through {@link setRetrievalLogger}; standalone runs (CLI / MCP /
4
+ * tests) keep the prefixed console default. The core deliberately has no Cordis
5
+ * dependency, so a module seam is the only way to reach every adapter.
6
+ */
7
+ import { type AvantfLogger } from '../mem-contract/index.js';
8
+ /** Route retrieval-core logs (model warm / degrade) to a host logger. */
9
+ export declare function setRetrievalLogger(logger?: AvantfLogger): void;
10
+ /** The logger every adapter reports through. */
11
+ export declare function retrievalLogger(): AvantfLogger;
@@ -0,0 +1,23 @@
1
+ import type { Config } from '../mem-contract/index.js';
2
+ import type { Reranker, SemanticBackend, VectorStore } from './interfaces.js';
3
+ type Factory<T> = (cfg: Config, opts?: ResolveOptions) => T;
4
+ /**
5
+ * How a backend is resolved.
6
+ *
7
+ * Only `deferWarm` exists, and only because the DSH plugin cannot let a backend warm itself: the
8
+ * family framework installs the model into the managed root ASYNCHRONOUSLY, so a constructor-time
9
+ * warm would race that install and fetch a second copy. The plugin passes `deferWarm: true` and
10
+ * warms from the model item's `onSettled`; every other caller (CLI, MCP, tests) keeps the eager
11
+ * constructor warm. `encode()` / `rerank()` still warm lazily, so deferring never loses the model.
12
+ */
13
+ export interface ResolveOptions {
14
+ /** Skip the constructor warm; the caller warms explicitly (see `ModelEnv.deferWarm`). */
15
+ readonly deferWarm?: boolean;
16
+ }
17
+ export declare function registerSemanticBackend(name: string, factory: Factory<SemanticBackend>): void;
18
+ export declare function registerReranker(name: string, factory: Factory<Reranker>): void;
19
+ export declare function registerVectorStore(name: string, factory: Factory<VectorStore>): void;
20
+ export declare function resolveSemantic(cfg: Config, opts?: ResolveOptions): SemanticBackend;
21
+ export declare function resolveReranker(cfg: Config, opts?: ResolveOptions): Reranker;
22
+ export declare function resolveVStore(cfg: Config): VectorStore;
23
+ export {};