@avantf/dsh-mem 0.3.0 → 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 (145) 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/dsh-build.json +5 -5
  5. package/lib/engine/mem/config/config_files.d.ts +26 -0
  6. package/lib/engine/mem/config/domains.d.ts +20 -0
  7. package/lib/engine/mem/config/loader.d.ts +35 -0
  8. package/lib/engine/mem/config/paths.d.ts +53 -0
  9. package/lib/engine/mem/db/chunk.d.ts +26 -0
  10. package/lib/engine/mem/db/conn.d.ts +13 -0
  11. package/lib/engine/mem/db/dao/chunks.d.ts +150 -0
  12. package/lib/engine/mem/db/dao/contradictions.d.ts +98 -0
  13. package/lib/engine/mem/db/dao/documents.d.ts +51 -0
  14. package/lib/engine/mem/db/dao/entities.d.ts +67 -0
  15. package/lib/engine/mem/db/dao/facts.d.ts +472 -0
  16. package/lib/engine/mem/db/dao/stats.d.ts +14 -0
  17. package/lib/engine/mem/db/dao/triples.d.ts +80 -0
  18. package/lib/engine/mem/db/hash.d.ts +2 -0
  19. package/lib/engine/mem/db/knowledge.d.ts +17 -0
  20. package/lib/engine/mem/db/migrations.d.ts +71 -0
  21. package/lib/engine/mem/db/port.d.ts +57 -0
  22. package/lib/engine/mem/db/schema.d.ts +26 -0
  23. package/lib/engine/mem/db/sqlite.d.ts +89 -0
  24. package/lib/engine/mem/db/store.d.ts +35 -0
  25. package/lib/engine/mem/db/tokenizer.d.ts +61 -0
  26. package/lib/engine/mem/db/vectors.d.ts +50 -0
  27. package/lib/engine/mem/dispatch.d.ts +51 -0
  28. package/lib/engine/mem/entities/extract.d.ts +84 -0
  29. package/lib/engine/mem/eval/loader.d.ts +19 -0
  30. package/lib/engine/mem/eval/metrics.d.ts +25 -0
  31. package/lib/engine/mem/eval/runner.d.ts +10 -0
  32. package/lib/engine/mem/eval/write_metrics.d.ts +88 -0
  33. package/lib/engine/mem/git.d.ts +73 -0
  34. package/lib/engine/mem/hrr/algebra.d.ts +30 -0
  35. package/lib/engine/mem/hrr/atoms.d.ts +22 -0
  36. package/lib/engine/mem/hrr/encode.d.ts +32 -0
  37. package/lib/engine/mem/hrr/index.d.ts +3 -0
  38. package/lib/engine/mem/index.d.ts +27 -0
  39. package/lib/engine/mem/lifecycle/contradiction.d.ts +156 -0
  40. package/lib/engine/mem/lifecycle/index.d.ts +5 -0
  41. package/lib/engine/mem/lifecycle/maintenance.d.ts +48 -0
  42. package/lib/engine/mem/lifecycle/presence.d.ts +29 -0
  43. package/lib/engine/mem/lifecycle/tick.d.ts +43 -0
  44. package/lib/engine/mem/lifecycle/trust.d.ts +90 -0
  45. package/lib/engine/mem/modelBootstrap.d.ts +207 -0
  46. package/lib/engine/mem/router.d.ts +51 -0
  47. package/lib/engine/mem/runtime.d.ts +232 -0
  48. package/lib/engine/mem/store/common.d.ts +82 -0
  49. package/lib/engine/mem/store/doc_files.d.ts +125 -0
  50. package/lib/engine/mem/store/document_text.d.ts +47 -0
  51. package/lib/engine/mem/store/floors.d.ts +191 -0
  52. package/lib/engine/mem/store/hybrid.d.ts +232 -0
  53. package/lib/engine/mem/store/ingest_guard.d.ts +25 -0
  54. package/lib/engine/mem/store/knowledge.d.ts +483 -0
  55. package/lib/engine/mem/store/lexical.d.ts +67 -0
  56. package/lib/engine/mem/store/memory.d.ts +543 -0
  57. package/lib/engine/mem/store/source_picker.d.ts +43 -0
  58. package/lib/engine/mem-contract/config.d.ts +129 -0
  59. package/lib/engine/mem-contract/env.d.ts +31 -0
  60. package/lib/engine/mem-contract/family.d.ts +25 -0
  61. package/lib/engine/mem-contract/index.d.ts +8 -0
  62. package/lib/engine/mem-contract/log.d.ts +29 -0
  63. package/lib/engine/mem-contract/remote.d.ts +80 -0
  64. package/lib/engine/mem-contract/text.d.ts +92 -0
  65. package/lib/engine/mem-contract/tools.d.ts +284 -0
  66. package/lib/engine/mem-contract/types.d.ts +615 -0
  67. package/lib/engine/mem-retrieval/adapters/auto_vstore.d.ts +47 -0
  68. package/lib/engine/mem-retrieval/adapters/hnswlib.d.ts +174 -0
  69. package/lib/engine/mem-retrieval/adapters/local_bge.d.ts +121 -0
  70. package/lib/engine/mem-retrieval/adapters/local_numpy.d.ts +28 -0
  71. package/lib/engine/mem-retrieval/adapters/local_reranker.d.ts +52 -0
  72. package/lib/engine/mem-retrieval/adapters/none_reranker.d.ts +10 -0
  73. package/lib/engine/mem-retrieval/adapters/warm_gate.d.ts +46 -0
  74. package/lib/engine/mem-retrieval/budget.d.ts +30 -0
  75. package/lib/engine/mem-retrieval/fusion.d.ts +53 -0
  76. package/lib/engine/mem-retrieval/index.d.ts +14 -0
  77. package/lib/engine/mem-retrieval/interfaces.d.ts +82 -0
  78. package/lib/engine/mem-retrieval/log.d.ts +11 -0
  79. package/lib/engine/mem-retrieval/registry.d.ts +23 -0
  80. package/lib/engine/mem-retrieval/rerank.d.ts +13 -0
  81. package/lib/engine/mem-retrieval/stats.d.ts +64 -0
  82. package/lib/engine/mem-retrieval/text_budget.d.ts +52 -0
  83. package/lib/index.js +1773 -473
  84. package/lib/interface-version.json +2 -2
  85. package/lib/types/client/domains.d.ts +5 -4
  86. package/lib/types/client/domains.d.ts.map +1 -1
  87. package/lib/types/client/index.d.ts.map +1 -1
  88. package/lib/types/client/ingest.d.ts +1 -1
  89. package/lib/types/client/wire.d.ts +36 -0
  90. package/lib/types/client/wire.d.ts.map +1 -0
  91. package/lib/types/envinit-bootstrap.d.ts +3 -2
  92. package/lib/types/envinit.d.ts +2 -19
  93. package/lib/types/envinit.d.ts.map +1 -1
  94. package/lib/types/hints.d.ts +1 -1
  95. package/lib/types/index.d.ts +8 -3
  96. package/lib/types/index.d.ts.map +1 -1
  97. package/lib/types/open.d.ts +61 -11
  98. package/lib/types/open.d.ts.map +1 -1
  99. package/lib/types/prompt.d.ts +19 -1
  100. package/lib/types/prompt.d.ts.map +1 -1
  101. package/lib/types/provision.d.ts +1 -1
  102. package/lib/types/provision.d.ts.map +1 -1
  103. package/lib/types/reconcile.d.ts +64 -0
  104. package/lib/types/reconcile.d.ts.map +1 -0
  105. package/lib/types/remote.d.ts +53 -0
  106. package/lib/types/remote.d.ts.map +1 -1
  107. package/lib/types/render.d.ts +35 -0
  108. package/lib/types/render.d.ts.map +1 -0
  109. package/lib/types/tool_schema.d.ts +3 -13
  110. package/lib/types/tool_schema.d.ts.map +1 -1
  111. package/package.json +27 -18
  112. package/lib/client.js.map +0 -1
  113. package/lib/types/client/domains.js +0 -25
  114. package/lib/types/client/domains.js.map +0 -1
  115. package/lib/types/client/index.js +0 -1371
  116. package/lib/types/client/index.js.map +0 -1
  117. package/lib/types/client/ingest.js +0 -30
  118. package/lib/types/client/ingest.js.map +0 -1
  119. package/lib/types/client/paging.js +0 -36
  120. package/lib/types/client/paging.js.map +0 -1
  121. package/lib/types/client/summarize.js +0 -79
  122. package/lib/types/client/summarize.js.map +0 -1
  123. package/lib/types/data_home.js +0 -26
  124. package/lib/types/data_home.js.map +0 -1
  125. package/lib/types/envinit-bootstrap.js +0 -333
  126. package/lib/types/envinit.js +0 -402
  127. package/lib/types/envinit.js.map +0 -1
  128. package/lib/types/hints.js +0 -53
  129. package/lib/types/hints.js.map +0 -1
  130. package/lib/types/index.js +0 -755
  131. package/lib/types/index.js.map +0 -1
  132. package/lib/types/inject.js +0 -14
  133. package/lib/types/inject.js.map +0 -1
  134. package/lib/types/interface_gate.js +0 -84
  135. package/lib/types/interface_gate.js.map +0 -1
  136. package/lib/types/open.js +0 -133
  137. package/lib/types/open.js.map +0 -1
  138. package/lib/types/prompt.js +0 -148
  139. package/lib/types/prompt.js.map +0 -1
  140. package/lib/types/provision.js +0 -177
  141. package/lib/types/provision.js.map +0 -1
  142. package/lib/types/remote.js +0 -247
  143. package/lib/types/remote.js.map +0 -1
  144. package/lib/types/tool_schema.js +0 -149
  145. package/lib/types/tool_schema.js.map +0 -1
@@ -0,0 +1,207 @@
1
+ import type { AvantfRuntime } from './runtime.js';
2
+ /** The statuses the base's verdict can carry (kept structural — see the note above). */
3
+ export type DshCompatStatus = 'ok' | 'version-mismatch' | 'version-unknown' | 'probe-skipped' | 'probe-failed';
4
+ /**
5
+ * The slice of the base's verdict this package reads.
6
+ *
7
+ * `load: false` means a probe PROVED the host API incompatible (only `probe-failed`); a version
8
+ * difference is a warning, never a skip. Structural on purpose: a full `CompatVerdict` from the base
9
+ * satisfies it, and this package needs no import of (or dependency on) the base to say so.
10
+ */
11
+ export interface DshCompatVerdict {
12
+ readonly load: boolean;
13
+ readonly status: DshCompatStatus;
14
+ readonly reason: string;
15
+ }
16
+ /** Options for {@link warmModels}. */
17
+ export interface WarmOptions {
18
+ /**
19
+ * Resolve before the tokenizer parse starts. The DSH plugin passes its host's readiness signal
20
+ * (`loader.await()`), the same one that gates the `dsh web` URL line, so the synchronous parse
21
+ * runs once the host is up instead of delaying it.
22
+ */
23
+ waitFor?: Promise<unknown>;
24
+ /**
25
+ * Hard cap on {@link waitFor} (default {@link WAIT_FOR_HOST_MS}).
26
+ *
27
+ * The host's own readiness signal is not bounded by the host (its URL print relies on the same
28
+ * promise), so a loader row that never settles would leave the tokenizer un-warmed for the whole
29
+ * process life — every first query then pays the ~1.2 s. Bounded here on principle, and
30
+ * injectable so a caller can shorten it without waiting the real interval.
31
+ */
32
+ waitForCapMs?: number;
33
+ /**
34
+ * Wait for an idle event loop before loading nodejieba (the host-boot path). Off for the CLI,
35
+ * which calls `warmModels` inline: there the process is already idle and the gate would only add
36
+ * its quiet window to every command. The name is tokenizer-shaped for history, but the gate is the
37
+ * general "do not run synchronous startup mission during the boot burst" one — the plugin's
38
+ * mount-time entity sweep passes the same flag through {@link awaitStartupGate}.
39
+ */
40
+ deferTokenizerUntilIdle?: boolean;
41
+ }
42
+ /**
43
+ * Fire-and-forget model bootstrap for DSH startup. Configures the semantic /
44
+ * rerank model env (mirror + cache) and triggers the model download/load
45
+ * asynchronously, without blocking the plugin's `apply`. Returns the availability
46
+ * of the semantic path once the download/load completes.
47
+ *
48
+ * Every stage logs through the runtime logger so a plugin mount is traceable in
49
+ * the DSH host output: warm start (mirror/cache/model), then ready or degraded.
50
+ *
51
+ * @param rt - the runtime whose memory store owns the semantic backend.
52
+ * @param options - host-readiness and idle-gating options (see {@link WarmOptions}).
53
+ * @param reportFinal - log the one-line verdict (`true`, the default). The provisioning sweep turns
54
+ * the same verdict into the artifact's result line and would otherwise print it twice; the CLI
55
+ * path keeps it, because there the caller only sees this logger.
56
+ */
57
+ export declare function warmModels(rt: AvantfRuntime, options?: WarmOptions, reportFinal?: boolean): Promise<boolean>;
58
+ /**
59
+ * Warm ONLY the embedding model: load it from `semantic.cache_dir` and mark the engine ready.
60
+ *
61
+ * Split out of {@link warmModels} for the plugin's envinit path. There the framework fetches the
62
+ * model in the BACKGROUND, so the warm has to wait for the item to settle instead of running at
63
+ * mount — and the runtime's own constructor warm is deferred for exactly that reason
64
+ * (`RuntimeOptions.managedRoots` → `deferWarm`), which is what stops it racing the install and
65
+ * leaving a second copy behind. While the item is unsettled NOTHING warms; this function is the
66
+ * explicit "files are there now, look" step, and it still runs when the item skipped/failed so
67
+ * `semantic.auto_download` can decide between the runtime's own fetch and degradation.
68
+ *
69
+ * @param rt - the runtime whose memory store owns the semantic backend.
70
+ * @param reportFinal - log the one-line verdict (default `true`).
71
+ * @returns whether the semantic path is available.
72
+ */
73
+ export declare function warmSemantic(rt: AvantfRuntime, reportFinal?: boolean): Promise<boolean>;
74
+ /**
75
+ * Wait out the startup gate — the host readiness signal (bounded by {@link WAIT_FOR_HOST_MS}) and,
76
+ * when asked, one quiet turn of the event loop.
77
+ *
78
+ * Extracted so there is exactly ONE definition of "a job that must not run inside the host's boot
79
+ * window". The tokenizer warm below uses it, and so does the plugin's mount-time entity sweep:
80
+ * `reindexEntities` tags stale rows through nodejieba, whose dictionary parse is the same ~1.2 s
81
+ * SYNCHRONOUS mission the warm defers, so a sweep fired at mount (before this) could pull that parse
82
+ * back into the startup window through a second door (performance review §7.5 / P5).
83
+ *
84
+ * The waits are NOT preconditions: a host whose boot failed still resolves them, and a host whose
85
+ * boot hung is bounded here rather than trusted (see {@link WarmOptions.waitForCapMs}).
86
+ *
87
+ * @param options - the same options the tokenizer warm takes (the idle gate is opt-in).
88
+ * @returns how long the idle gate waited (ms), or `0` when it was not requested.
89
+ */
90
+ export declare function awaitStartupGate(options?: WarmOptions): Promise<number>;
91
+ /**
92
+ * Warm ONLY the tokenizer, after the host-readiness signal and (when asked) an idle event loop.
93
+ *
94
+ * nodejieba loads its dictionary lazily on the FIRST `tag()` (measured ~950 ms, after which a call
95
+ * is 0.1 ms), so without this the first write/query of a session pays it while the user waits. Its
96
+ * cost belongs to startup, and an unavailable jieba is worth saying out loud — entity extraction
97
+ * silently degrades to the regex fallback (weaker entity/jaccard legs).
98
+ *
99
+ * @param rt - the runtime whose logger reports the outcome.
100
+ * @param options - the host-readiness signal and the idle gate (see {@link WarmOptions}).
101
+ */
102
+ export declare function warmTokenizer(rt: AvantfRuntime, options?: WarmOptions): Promise<void>;
103
+ /**
104
+ * Non-blocking embedding warm, for a caller that knows the model files are already on disk.
105
+ *
106
+ * The plugin's envinit path calls this from the model item's `onSettled`, which is the "continue
107
+ * initialisation once the background resource is there" step (framework DESIGN §7.2 step 5).
108
+ *
109
+ * @param rt - the runtime to warm.
110
+ * @param reportFinal - log the one-line verdict (default `true`).
111
+ */
112
+ export declare function warmSemanticAsync(rt: AvantfRuntime, reportFinal?: boolean): void;
113
+ /**
114
+ * Non-blocking tokenizer warm for the plugin startup path: the host is still mounting plugins and
115
+ * composing its client bundles, so the synchronous dictionary parse waits for the host's readiness
116
+ * signal (when one is available) and then for a quiet turn, instead of competing with the boot.
117
+ *
118
+ * @param rt - the runtime to warm.
119
+ * @param options - the host-readiness signal and the idle gate (see {@link WarmOptions}).
120
+ */
121
+ export declare function warmTokenizerAsync(rt: AvantfRuntime, options?: WarmOptions): void;
122
+ /**
123
+ * Options for {@link provisionToolchainAsync}: the warm-up options plus the host-side dsh verdict.
124
+ */
125
+ export interface ProvisionSweepOptions extends WarmOptions {
126
+ /**
127
+ * The verdict the DSH plugin ALREADY computed (see `packages/plugin/src/provision.ts`), as plain
128
+ * data. The plugin computes it once and this package reads it — no second derivation, and no
129
+ * import of the base. Absent for callers with no dsh context (the standalone MCP entry), in which
130
+ * case no gate runs.
131
+ */
132
+ dshCompat?: DshCompatVerdict;
133
+ }
134
+ /** Outcome of {@link provisionToolchainAsync}. */
135
+ export interface ProvisionSweepResult {
136
+ /**
137
+ * `true` when the sweep did NOT run because the host dsh is incompatible: no artifact was
138
+ * resolved, installed or warmed. `false` means the sweep was dispatched as usual.
139
+ */
140
+ skipped: boolean;
141
+ /** Verdict status when the gate ran (`probe-failed` is the only value that skips). */
142
+ status?: DshCompatStatus;
143
+ /** Why it was skipped (the proven incompatibility detail). */
144
+ reason?: string;
145
+ }
146
+ /**
147
+ * The ONE startup initialization for this plugin: register every artifact this build needs, then run
148
+ * the provisioning sweep (non-blocking).
149
+ *
150
+ * It replaced a one-shot model warm-up that sat beside a separate tools sweep, which meant two
151
+ * initialization paths, two logging vocabularies, and no single answer to "is this host fully
152
+ * provisioned?". Callers (the DSH plugin mount, the MCP entry) now make one call, and the per-artifact
153
+ * result lines in the host log come from the same mechanism that installs pandoc.
154
+ *
155
+ * The FIRST step is the dsh compatibility gate: an incompatible host means NOTHING is resolved,
156
+ * installed or warmed — preparing an environment for an API we cannot drive is wasted mission.
157
+ *
158
+ * @param rt - the runtime, whose resolved `tools` config and logger the sweep uses.
159
+ * @param options - the host's readiness signal and the dsh compatibility evidence.
160
+ * @returns whether the sweep ran or was skipped for incompatibility.
161
+ */
162
+ export declare function provisionToolchainAsync(rt: AvantfRuntime, options?: ProvisionSweepOptions): Promise<ProvisionSweepResult>;
163
+ /** The artifact id, used in the startup log and in the model artifact's errors. */
164
+ export declare const MODEL_ARTIFACT_ID = "model";
165
+ /** The environment a model-artifact call runs under: the runtime that owns the model cache. */
166
+ export interface ModelArtifactEnv {
167
+ runtime: AvantfRuntime;
168
+ }
169
+ /** The slice of an artifact call this module needs (`@avantf/mem-provision`'s InstallContext). */
170
+ interface ModelInstallContext {
171
+ logger: {
172
+ info(message: string): void;
173
+ warn(message: string): void;
174
+ error(message: string): void;
175
+ };
176
+ artifactEnv?: unknown;
177
+ config: {
178
+ auto_install: boolean;
179
+ };
180
+ }
181
+ /**
182
+ * The embedding model (plus the nodejieba tokenizer it shares its warm-up budget with) as an
183
+ * ARTIFACT, so the startup sweep in `@avantf/mem-provision` is the only initialization path.
184
+ *
185
+ * It used to be a parallel model warm-up call beside the tools sweep, which meant two
186
+ * startup mechanisms with two logging vocabularies and no single place that answered "is this host
187
+ * fully provisioned?". The model has no pinned version and no managed directory of its own ON THIS
188
+ * (the flat `<repo>/<file>` layout the transformers.js cache reads), so it
189
+ * declares neither; its `isPresent` is `false` because "is the model warm?" is not a question that
190
+ * can be answered without attempting the warm-up, and the warm-up is cheap when the cache is
191
+ * already there (measured ~1.2 s, in the background). The DSH plugin uses this sweep only when the
192
+ * family framework is unavailable — with the framework up, `mem:model` owns the model root instead
193
+ * and this artifact is not registered.
194
+ */
195
+ export declare const modelArtifact: {
196
+ id: string;
197
+ title: string;
198
+ verb: string;
199
+ isPresent(): Promise<boolean>;
200
+ install(ctx: ModelInstallContext): Promise<void>;
201
+ verify(ctx: {
202
+ logger: ModelInstallContext["logger"];
203
+ }): Promise<void>;
204
+ };
205
+ /** Register the model artifact once per process (idempotent for a test that mounts twice). */
206
+ export declare function registerModelArtifact(): void;
207
+ export {};
@@ -0,0 +1,51 @@
1
+ import { type RecallHit, type RecallResult, type RetrievalFloorDrops } from '../mem-contract/index.js';
2
+ interface CrossQueryOptions {
3
+ limit?: number;
4
+ kind?: 'all' | 'fact' | 'doc_chunk';
5
+ domain?: string;
6
+ source?: string;
7
+ /**
8
+ * Token budget for the merged result; `0` = unlimited, omitted = the caller already bounded
9
+ * the legs (the runtime lifts the per-store budgets for the pool and bounds the OUTCOME here,
10
+ * because fusion can reorder which entries deserve the text).
11
+ */
12
+ maxTokens?: number;
13
+ /**
14
+ * The knowledge leg's floor report. `KnowledgeStore.search` answers with hits only, so the
15
+ * runtime hands its captured `HybridResult` drops here; the memory side already arrives on
16
+ * `memory.dropped_by_floor`. Without this, the merged result would report only half the floors.
17
+ */
18
+ kbDroppedByFloor?: RetrievalFloorDrops;
19
+ }
20
+ /** The memory search result the router derives degradation/weights from. */
21
+ type CrossQueryMemory = Pick<RecallResult, 'hits' | 'degraded' | 'weights' | 'floors' | 'dropped_by_floor'>;
22
+ /**
23
+ * Joint min-max normalization over the MERGED pool — the rule the cross-store score
24
+ * contract is defined by (DESIGN §7).
25
+ *
26
+ * Degenerate case (every candidate carries the same score — including a pool of one):
27
+ * the raw score is KEPT. A collapsed range encodes no ranking information, so rescaling
28
+ * it would invent a "perfect match" out of an absolute fused score that may say
29
+ * otherwise. `minMaxNormalize` maps the same case to 1 instead, which is right there
30
+ * because it only ever feeds a relative weighted sum inside ONE fusion path; the two
31
+ * differ deliberately rather than by oversight.
32
+ *
33
+ * Returns COPIES: the inputs are the stores' own result objects, and callers still
34
+ * read their original scores.
35
+ */
36
+ export declare function normalizeMergedScores<T extends {
37
+ score: number;
38
+ }>(pool: T[]): T[];
39
+ /**
40
+ * Cross-retrieval router: merge memory + knowledge hits, joint min-max normalize
41
+ * the final fused scores over the *combined* pool (so the two stores' scores are
42
+ * comparable), then rank by score.
43
+ *
44
+ * Degradation and weights are inherited from the memory search result rather
45
+ * than hardcoded: both stores share the same semantic adapter, so
46
+ * `memory.degraded`/`memory.weights` already describe whether the semantic leg
47
+ * ran. Reporting a fixed `degraded: true` here made every `kb_query` look
48
+ * degraded even when embeddings were live.
49
+ */
50
+ export declare function crossQuery(memory: CrossQueryMemory, kb: RecallHit[], opts?: CrossQueryOptions): RecallResult;
51
+ export {};
@@ -0,0 +1,232 @@
1
+ import type { AdminRequest, KbAddRequest, KbConflictReport, KbRequest, QueryRequest, RecallRequest, RecallResult, RememberRequest } from '../mem-contract/index.js';
2
+ import { type AvantfLogger, type StatsSummary } from '../mem-contract/index.js';
3
+ import { type SemanticBackend } from '../mem-retrieval/index.js';
4
+ import { type LoadedConfig } from './config/loader.js';
5
+ import { type Db } from './db/conn.js';
6
+ import { type RelevanceHit } from './store/lexical.js';
7
+ import { MemoryStore } from './store/memory.js';
8
+ import { KnowledgeStore } from './store/knowledge.js';
9
+ export interface RuntimeOptions {
10
+ dataHome?: string;
11
+ memoryDbPath?: string;
12
+ /** Host logger; defaults to the prefixed console logger. */
13
+ logger?: AvantfLogger;
14
+ /**
15
+ * Override the semantic backend instead of resolving `semantic.backend` through the
16
+ * registry. A seam for tests and embedders, NOT a second pluggability path: the registry
17
+ * + config stays the documented way to swap backends (DESIGN §5). The override must
18
+ * produce vectors of `config.semantic.dim`, since the vector stores are built from it.
19
+ */
20
+ semantic?: SemanticBackend;
21
+ /**
22
+ * The family framework's managed roots, as the built-in defaults for `tools.dir` and
23
+ * `semantic.cache_dir` / `rerank.cache_dir` (see `LoadConfigOptions.managedRoots`).
24
+ *
25
+ * The DSH plugin passes `<family home>/tools` and `<family home>/models` while
26
+ * `@avantf/dsh-plugin-base` owns the fetching; the CLI and the MCP server pass nothing, because they
27
+ * provision through `@avantf/mem-provision` as before.
28
+ *
29
+ * This also decides WHEN the local models warm: with managed roots the framework may still be
30
+ * writing the model into that root, so the backends are resolved with `deferWarm` and the plugin
31
+ * warms them once the framework's item settles. Without managed roots the adapters keep their
32
+ * eager constructor warm.
33
+ */
34
+ managedRoots?: {
35
+ readonly tools?: string;
36
+ readonly models?: string;
37
+ };
38
+ }
39
+ /**
40
+ * The awaited result of a store method — what a dispatch overload promises.
41
+ *
42
+ * Every member below is written in terms of the METHOD that produces it rather than a type
43
+ * spelled out by hand. That is the whole point: an invented type (`Promise<RecallResult>` for
44
+ * `related`, which answers with entity counts) compiles perfectly and then lies to every caller,
45
+ * while `ReturnType<Store['method']>` cannot disagree with the store. The trailing general
46
+ * overloads keep dynamic dispatchers (the plugin's tool runner, MCP) compiling, where the request
47
+ * arrives as the whole union.
48
+ *
49
+ * Exported because the DSH plugin's Remote gateway is a dynamic dispatcher too, and it should be able
50
+ * to say what each key answers without inventing a shape: `StoreResult<MemoryStore, 'add'>` is the
51
+ * fact the store actually wrote, whatever the store's author later changes it to.
52
+ */
53
+ export type StoreResult<S, M extends keyof S> = Awaited<ReturnType<Extract<S[M], (...args: never[]) => unknown>>>;
54
+ /**
55
+ * A caller-error payload — the shape every self-made refusal in the `switch` returns.
56
+ *
57
+ * Named so the return sites can assert it (`satisfies DispatchError`): these payloads are the
58
+ * only ones NOT produced by a store method, so they have no `ReturnType` to be tied to.
59
+ */
60
+ export type DispatchError = {
61
+ error: string;
62
+ };
63
+ /**
64
+ * `admin.stats`: the store counts plus the process-wide retrieval health block (DESIGN §20) — the one
65
+ * admin payload no store method produces, because the runtime composes it.
66
+ */
67
+ export type AdminStatsResult = StatsSummary;
68
+ /**
69
+ * The payload of the four hits-returning recall actions, which one overload groups because a
70
+ * caller may hold them as a union (`action: 'chain' | 'reason'`).
71
+ *
72
+ * Grouping them is a CLAIM — that `probe`, `chain` and `reason` all answer like `search` — so it is
73
+ * proven rather than assumed: a type-level check asserts that each store method's result equals this
74
+ * one, and a divergence fails there instead of the grouped overload quietly lying about it. (Only
75
+ * `probe` holds by construction, because it calls `memory.search`.)
76
+ */
77
+ export type RecallHitsResult = StoreResult<MemoryStore, 'search'>;
78
+ /**
79
+ * `remember` dispatches to five store operations whose RESULTS differ: `add`/`update` return the
80
+ * written fact, `remove` a flag, the feedback actions the new counter (or `null`). One plain
81
+ * signature would either say `unknown` — leaving every caller to invent a cast, which is how a
82
+ * caller ends up reading a field that does not exist — or a union that forces every caller to
83
+ * narrow first. The callable type says what each ACTION returns.
84
+ */
85
+ export type RememberDispatch = {
86
+ (req: Extract<RememberRequest, {
87
+ action: 'add';
88
+ }>): Promise<StoreResult<MemoryStore, 'add'>>;
89
+ (req: Extract<RememberRequest, {
90
+ action: 'update';
91
+ }>): Promise<StoreResult<MemoryStore, 'update'>>;
92
+ (req: Extract<RememberRequest, {
93
+ action: 'remove';
94
+ }>): Promise<StoreResult<MemoryStore, 'remove'>>;
95
+ (req: Extract<RememberRequest, {
96
+ action: 'helpful' | 'unhelpful';
97
+ }>): Promise<StoreResult<MemoryStore, 'helpful'>>;
98
+ (req: RememberRequest): Promise<unknown>;
99
+ };
100
+ /**
101
+ * Same per-action idea as {@link RememberDispatch}. `ask` may also report a caller error, and
102
+ * `related` answers with ENTITY COUNTS rather than hits (`memory.related`) — a distinction that
103
+ * only showed up once this type existed, because the callers had been casting to whatever they
104
+ * assumed.
105
+ */
106
+ export type RecallDispatch = {
107
+ (req: Extract<RecallRequest, {
108
+ action: 'search' | 'probe' | 'chain' | 'reason';
109
+ }>): Promise<RecallHitsResult>;
110
+ (req: Extract<RecallRequest, {
111
+ action: 'ask';
112
+ }>): Promise<StoreResult<MemoryStore, 'ask'> | DispatchError>;
113
+ (req: Extract<RecallRequest, {
114
+ action: 'related';
115
+ }>): Promise<StoreResult<MemoryStore, 'related'>>;
116
+ (req: Extract<RecallRequest, {
117
+ action: 'contradict';
118
+ }>): Promise<StoreResult<MemoryStore, 'listContradictions'>>;
119
+ (req: RecallRequest): Promise<unknown>;
120
+ };
121
+ /**
122
+ * `admin` is the widest dispatcher: counts, a fact page, a detail, flags, and five diagnostic
123
+ * reports. `ReturnType<…>` keeps each member tied to the store method that actually produces it,
124
+ * so a change there propagates instead of leaving callers with a cast they invented.
125
+ */
126
+ export type AdminDispatch = {
127
+ (req: Extract<AdminRequest, {
128
+ action: 'stats';
129
+ }>): AdminStatsResult;
130
+ (req: Extract<AdminRequest, {
131
+ action: 'list';
132
+ }>): StoreResult<MemoryStore, 'list'>;
133
+ (req: Extract<AdminRequest, {
134
+ action: 'detail';
135
+ }>): StoreResult<MemoryStore, 'get'> | DispatchError;
136
+ (req: Extract<AdminRequest, {
137
+ action: 'archive';
138
+ }>): StoreResult<MemoryStore, 'archive'>;
139
+ (req: Extract<AdminRequest, {
140
+ action: 'restore';
141
+ }>): StoreResult<MemoryStore, 'restore'>;
142
+ (req: Extract<AdminRequest, {
143
+ action: 'pin';
144
+ }>): StoreResult<MemoryStore, 'pin'>;
145
+ (req: Extract<AdminRequest, {
146
+ action: 'unpin';
147
+ }>): StoreResult<MemoryStore, 'unpin'>;
148
+ (req: Extract<AdminRequest, {
149
+ action: 'trust_diagnose';
150
+ }>): StoreResult<MemoryStore, 'trustDiagnose'>;
151
+ (req: Extract<AdminRequest, {
152
+ action: 'vectors_diagnose';
153
+ }>): StoreResult<MemoryStore, 'vectorsDiagnose'>;
154
+ (req: Extract<AdminRequest, {
155
+ action: 'vectors_fix';
156
+ }>): StoreResult<MemoryStore, 'vectorsFix'>;
157
+ (req: Extract<AdminRequest, {
158
+ action: 'contradict_check';
159
+ }>): StoreResult<MemoryStore, 'checkContradictions'>;
160
+ (req: Extract<AdminRequest, {
161
+ action: 'contradict_resolve';
162
+ }>): StoreResult<MemoryStore, 'resolveContradiction'>;
163
+ (req: Extract<AdminRequest, {
164
+ action: 'maintenance';
165
+ }>): StoreResult<MemoryStore, 'maintenance'>;
166
+ (req: AdminRequest): unknown;
167
+ };
168
+ /**
169
+ * `kb_manage`: ingest/import report a batch, list/detail the corpus, remove a flag, reindex a
170
+ * plan. Members are pinned to the store methods that produce them (`ReturnType<…>`) so the
171
+ * shapes cannot drift into "whatever the caller assumed".
172
+ *
173
+ * `ingest`/`import` may instead answer with a {@link KbConflictReport}: this face is the UI/CLI
174
+ * REPLACE mode, where an unconfirmed collision writes nothing and comes back as a structured
175
+ * list. The add-only face (`kbAdd`) never returns one — it refuses.
176
+ */
177
+ export type KbDispatch = {
178
+ (req: Extract<KbRequest, {
179
+ action: 'ingest';
180
+ }>): Promise<StoreResult<KnowledgeStore, 'ingest'> | KbConflictReport | DispatchError>;
181
+ (req: Extract<KbRequest, {
182
+ action: 'import';
183
+ }>): Promise<StoreResult<KnowledgeStore, 'importPaths'> | KbConflictReport>;
184
+ (req: Extract<KbRequest, {
185
+ action: 'list';
186
+ }>): Promise<StoreResult<KnowledgeStore, 'list'>>;
187
+ (req: Extract<KbRequest, {
188
+ action: 'detail';
189
+ }>): Promise<StoreResult<KnowledgeStore, 'detail'>>;
190
+ (req: Extract<KbRequest, {
191
+ action: 'remove';
192
+ }>): Promise<StoreResult<KnowledgeStore, 'remove'>>;
193
+ (req: Extract<KbRequest, {
194
+ action: 'reindex';
195
+ }>): Promise<StoreResult<KnowledgeStore, 'reindex'>>;
196
+ (req: KbRequest): Promise<unknown>;
197
+ };
198
+ export interface AvantfRuntime {
199
+ config: LoadedConfig;
200
+ db: Db;
201
+ memory: MemoryStore;
202
+ knowledge: KnowledgeStore;
203
+ /** Logger every startup/lifecycle line goes through. */
204
+ logger: AvantfLogger;
205
+ remember: RememberDispatch;
206
+ recall: RecallDispatch;
207
+ admin: AdminDispatch;
208
+ kb: KbDispatch;
209
+ /**
210
+ * The ADD-ONLY face of the same shared ingestion entry (`kb_add`): same plan/classify/dispatch,
211
+ * but a collision is refused instead of replaced. Kept as its own runtime method — not a field on
212
+ * the request — so the model-facing `KB_ADD_TOOL.input` cannot even express "replace".
213
+ */
214
+ kbAdd(req: KbAddRequest): Promise<unknown>;
215
+ query(req: QueryRequest): Promise<RecallResult>;
216
+ /**
217
+ * Plugin-internal, NOT a tool and not model-facing: does either store already hold something
218
+ * relevant to `text`? A boolean (`store/lexical.ts`) because the one tool the hint names
219
+ * (`kb_query`) retrieves from both stores, so "which one matched" changes no downstream decision.
220
+ * Synchronous on purpose — the caller renders it into a prompt provider that cannot await
221
+ * (DESIGN §12).
222
+ */
223
+ relevance(text: string): RelevanceHit;
224
+ /**
225
+ * True once {@link shutdown} has run: the databases are closed and no background pass may start.
226
+ * A gated startup job checks this (as well as the plugin's fiber) before touching the stores.
227
+ */
228
+ readonly closed: boolean;
229
+ shutdown(): void;
230
+ }
231
+ /** Build the runtime: load config, open the memory DB, construct the stores. */
232
+ export declare function buildRuntime(opts?: RuntimeOptions): AvantfRuntime;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Pieces both stores need identically.
3
+ *
4
+ * Small on purpose: `MemoryStore` and `KnowledgeStore` are separate stores over separate databases
5
+ * and should stay that way (AGENTS.md). What they must NOT have is two copies of the same
6
+ * three-line helper, because a fix then lands in one of them — which is exactly what happened to the
7
+ * retrieval orchestration (see `store/hybrid.ts`) and to the foreign-vector-space warning below: the
8
+ * memory store checked whether its persisted vectors came from the CURRENT vector space and told the
9
+ * operator how to fix it, while the knowledge store checked only the width, so a model swap left two
10
+ * spaces silently mixed in one index until someone happened to run `kb_reindex`.
11
+ *
12
+ * @module store/common
13
+ */
14
+ import type { SemanticBackend, VectorStore } from '../../mem-retrieval/index.js';
15
+ /**
16
+ * The ONE write-side normalization both stores call before anything reaches the database.
17
+ *
18
+ * `@avantf/mem-contract`'s `toWellFormedText` is the implementation (well-formed + NFC); this name
19
+ * is the store-layer entry, so the write paths say WHAT they are doing ("normalize this for a
20
+ * write") rather than repeating a Unicode recipe. Every text field a store persists — fact content,
21
+ * category, archive reason, a document's domain/source/title/uri, entity names and the SPO slots of
22
+ * extracted triples — goes through here, which is what keeps a lone surrogate (and the strict-JSON
23
+ * failure it causes downstream) out of the database in the first place.
24
+ */
25
+ export declare function normalizeWrite(value: string): string;
26
+ /** {@link normalizeWrite} over a batch (entity names, caller paths, …). */
27
+ export declare function normalizeWrites(values: readonly string[]): string[];
28
+ /**
29
+ * Identity of the vector space a store currently writes into.
30
+ *
31
+ * A change of backend, model or width makes every stored vector stale, and the point of recording
32
+ * the identity is that such a change is DETECTED instead of silently mixing two spaces in one index.
33
+ */
34
+ export declare function vectorSpaceOf(semantic: SemanticBackend, model: string): string;
35
+ /**
36
+ * Drop ids from the vector index, in one batch when the backend supports it.
37
+ *
38
+ * `removeMany` is optional on the interface: a backend that cannot delete incrementally would make
39
+ * an eviction loop O(N²) without it, and hnswlib's is O(k) tombstoning.
40
+ */
41
+ export declare function evictVectors(vstore: VectorStore, ids: readonly number[]): void;
42
+ /**
43
+ * Tell the operator that persisted vectors came from ANOTHER vector space.
44
+ *
45
+ * A dim-valid vector from a different space is still RANKED — the reload cannot tell the difference
46
+ * — so a silent model swap would mix two spaces in one index and degrade every semantic query
47
+ * without an error anywhere. Said once per process; acting on it is the operator's choice because
48
+ * re-encoding is a real cost (and they should see the count first).
49
+ *
50
+ * @param kind store label for the message (`memory` / `knowledge`)
51
+ * @param space the vector space the store writes into now
52
+ * @param foreign how many persisted vectors were written in a different one
53
+ * @param fix the action that re-encodes them, as the operator would type it
54
+ */
55
+ export declare function reportForeignVectors(kind: string, space: string, foreign: number, fix: string): void;
56
+ /**
57
+ * Hand the event loop back between two synchronous batches.
58
+ *
59
+ * The mount-time corpus reconcile reads and hashes EVERY managed document (and then walks the tree
60
+ * again for orphans), all of it synchronous `node:fs`; at 10k documents that is a >0.6 s block of the
61
+ * host's event loop (measured — see the performance review §7.5 / P6). `setImmediate` (a macrotask),
62
+ * not a resolved-promise microtask: yielding to the microtask queue would let other JS continuations
63
+ * run while timers and I/O queued behind the batch still waited.
64
+ */
65
+ export declare function yieldToEventLoop(): Promise<void>;
66
+ /**
67
+ * How many items one synchronous batch may visit before {@link yieldToEventLoop} runs.
68
+ *
69
+ * 200 is a compromise: large enough that the yields themselves are noise next to the per-item
70
+ * `read`+`sha256` (measured ~60 µs each), small enough that the longest single block stays in the
71
+ * low tens of milliseconds at any corpus size.
72
+ */
73
+ export declare const YIELD_BATCH = 200;
74
+ /**
75
+ * Visit `items` in order, yielding the event loop every `every` items.
76
+ *
77
+ * Use this INSTEAD of a bare `for` loop whenever the per-item mission is synchronous filesystem or
78
+ * hashing work whose item count follows the corpus (not the request): the caller's whole loop used to
79
+ * be one uninterrupted block, so the host could not serve a timer or an I/O callback until it was
80
+ * over. An empty list does nothing (and never yields).
81
+ */
82
+ export declare function forEachYielding<T>(items: readonly T[], visit: (item: T, index: number) => void, every?: number): Promise<void>;