@hviana/sema 0.1.7 → 0.1.9

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/example/train_base.ts +29 -4
  2. package/package.json +1 -1
  3. package/src/alu/src/parser.ts +105 -4
  4. package/src/canon.ts +65 -0
  5. package/src/geometry.ts +26 -30
  6. package/src/index.ts +1 -0
  7. package/src/mind/learning.ts +39 -1
  8. package/src/mind/match.ts +29 -6
  9. package/src/mind/mechanisms/cast.ts +33 -13
  10. package/src/mind/mechanisms/confluence.ts +15 -1
  11. package/src/mind/mechanisms/recall.ts +116 -41
  12. package/src/mind/mind.ts +172 -29
  13. package/src/mind/primitives.ts +66 -1
  14. package/src/mind/recognition.ts +10 -0
  15. package/src/mind/types.ts +10 -0
  16. package/src/store-sqlite.ts +68 -0
  17. package/src/store.ts +27 -0
  18. package/test/13-conversation.test.mjs +77 -27
  19. package/test/35-prefix-edge.test.mjs +86 -0
  20. package/dist/example/demo.d.ts +0 -1
  21. package/dist/example/demo.js +0 -39
  22. package/dist/example/train_base.d.ts +0 -87
  23. package/dist/example/train_base.js +0 -2232
  24. package/dist/src/alphabet.d.ts +0 -7
  25. package/dist/src/alphabet.js +0 -33
  26. package/dist/src/alu/src/alu.d.ts +0 -185
  27. package/dist/src/alu/src/alu.js +0 -440
  28. package/dist/src/alu/src/expr.d.ts +0 -61
  29. package/dist/src/alu/src/expr.js +0 -318
  30. package/dist/src/alu/src/index.d.ts +0 -11
  31. package/dist/src/alu/src/index.js +0 -19
  32. package/dist/src/alu/src/kernel-arith.d.ts +0 -16
  33. package/dist/src/alu/src/kernel-arith.js +0 -264
  34. package/dist/src/alu/src/kernel-bits.d.ts +0 -19
  35. package/dist/src/alu/src/kernel-bits.js +0 -152
  36. package/dist/src/alu/src/kernel-logic.d.ts +0 -4
  37. package/dist/src/alu/src/kernel-logic.js +0 -60
  38. package/dist/src/alu/src/kernel-nd.d.ts +0 -3
  39. package/dist/src/alu/src/kernel-nd.js +0 -208
  40. package/dist/src/alu/src/kernel-numeric.d.ts +0 -54
  41. package/dist/src/alu/src/kernel-numeric.js +0 -366
  42. package/dist/src/alu/src/operation.d.ts +0 -168
  43. package/dist/src/alu/src/operation.js +0 -189
  44. package/dist/src/alu/src/parser.d.ts +0 -212
  45. package/dist/src/alu/src/parser.js +0 -469
  46. package/dist/src/alu/src/resonance.d.ts +0 -55
  47. package/dist/src/alu/src/resonance.js +0 -126
  48. package/dist/src/alu/src/text.d.ts +0 -31
  49. package/dist/src/alu/src/text.js +0 -73
  50. package/dist/src/alu/src/value.d.ts +0 -109
  51. package/dist/src/alu/src/value.js +0 -300
  52. package/dist/src/alu/test/alu.test.d.ts +0 -1
  53. package/dist/src/alu/test/alu.test.js +0 -764
  54. package/dist/src/bytes.d.ts +0 -14
  55. package/dist/src/bytes.js +0 -59
  56. package/dist/src/config.d.ts +0 -111
  57. package/dist/src/config.js +0 -91
  58. package/dist/src/derive/src/deduction.d.ts +0 -125
  59. package/dist/src/derive/src/deduction.js +0 -155
  60. package/dist/src/derive/src/index.d.ts +0 -7
  61. package/dist/src/derive/src/index.js +0 -11
  62. package/dist/src/derive/src/priority-queue.d.ts +0 -20
  63. package/dist/src/derive/src/priority-queue.js +0 -73
  64. package/dist/src/derive/src/rewrite.d.ts +0 -56
  65. package/dist/src/derive/src/rewrite.js +0 -100
  66. package/dist/src/derive/src/trie.d.ts +0 -90
  67. package/dist/src/derive/src/trie.js +0 -217
  68. package/dist/src/derive/test/derive.test.d.ts +0 -1
  69. package/dist/src/derive/test/derive.test.js +0 -122
  70. package/dist/src/extension.d.ts +0 -37
  71. package/dist/src/extension.js +0 -7
  72. package/dist/src/geometry.d.ts +0 -148
  73. package/dist/src/geometry.js +0 -517
  74. package/dist/src/index.d.ts +0 -15
  75. package/dist/src/index.js +0 -17
  76. package/dist/src/ingest-cache.d.ts +0 -41
  77. package/dist/src/ingest-cache.js +0 -161
  78. package/dist/src/mind/articulation.d.ts +0 -6
  79. package/dist/src/mind/articulation.js +0 -99
  80. package/dist/src/mind/attention.d.ts +0 -72
  81. package/dist/src/mind/attention.js +0 -898
  82. package/dist/src/mind/canonical.d.ts +0 -29
  83. package/dist/src/mind/canonical.js +0 -88
  84. package/dist/src/mind/graph-search.d.ts +0 -270
  85. package/dist/src/mind/graph-search.js +0 -847
  86. package/dist/src/mind/index.d.ts +0 -5
  87. package/dist/src/mind/index.js +0 -5
  88. package/dist/src/mind/junction.d.ts +0 -95
  89. package/dist/src/mind/junction.js +0 -262
  90. package/dist/src/mind/learning.d.ts +0 -47
  91. package/dist/src/mind/learning.js +0 -201
  92. package/dist/src/mind/match.d.ts +0 -111
  93. package/dist/src/mind/match.js +0 -422
  94. package/dist/src/mind/mechanisms/alu.d.ts +0 -4
  95. package/dist/src/mind/mechanisms/alu.js +0 -29
  96. package/dist/src/mind/mechanisms/cast.d.ts +0 -35
  97. package/dist/src/mind/mechanisms/cast.js +0 -447
  98. package/dist/src/mind/mechanisms/confluence.d.ts +0 -24
  99. package/dist/src/mind/mechanisms/confluence.js +0 -213
  100. package/dist/src/mind/mechanisms/cover.d.ts +0 -6
  101. package/dist/src/mind/mechanisms/cover.js +0 -179
  102. package/dist/src/mind/mechanisms/extraction.d.ts +0 -67
  103. package/dist/src/mind/mechanisms/extraction.js +0 -342
  104. package/dist/src/mind/mechanisms/recall.d.ts +0 -13
  105. package/dist/src/mind/mechanisms/recall.js +0 -166
  106. package/dist/src/mind/mind.d.ts +0 -211
  107. package/dist/src/mind/mind.js +0 -433
  108. package/dist/src/mind/pipeline-mechanism.d.ts +0 -142
  109. package/dist/src/mind/pipeline-mechanism.js +0 -213
  110. package/dist/src/mind/pipeline.d.ts +0 -20
  111. package/dist/src/mind/pipeline.js +0 -185
  112. package/dist/src/mind/primitives.d.ts +0 -54
  113. package/dist/src/mind/primitives.js +0 -184
  114. package/dist/src/mind/rationale.d.ts +0 -134
  115. package/dist/src/mind/rationale.js +0 -162
  116. package/dist/src/mind/reasoning.d.ts +0 -15
  117. package/dist/src/mind/reasoning.js +0 -162
  118. package/dist/src/mind/recognition.d.ts +0 -20
  119. package/dist/src/mind/recognition.js +0 -236
  120. package/dist/src/mind/resonance.d.ts +0 -23
  121. package/dist/src/mind/resonance.js +0 -0
  122. package/dist/src/mind/trace.d.ts +0 -15
  123. package/dist/src/mind/trace.js +0 -73
  124. package/dist/src/mind/traverse.d.ts +0 -113
  125. package/dist/src/mind/traverse.js +0 -488
  126. package/dist/src/mind/types.d.ts +0 -176
  127. package/dist/src/mind/types.js +0 -84
  128. package/dist/src/rabitq-ivf/src/database.d.ts +0 -113
  129. package/dist/src/rabitq-ivf/src/database.js +0 -201
  130. package/dist/src/rabitq-ivf/src/index.d.ts +0 -7
  131. package/dist/src/rabitq-ivf/src/index.js +0 -4
  132. package/dist/src/rabitq-ivf/src/ivf.d.ts +0 -200
  133. package/dist/src/rabitq-ivf/src/ivf.js +0 -1165
  134. package/dist/src/rabitq-ivf/src/prng.d.ts +0 -19
  135. package/dist/src/rabitq-ivf/src/prng.js +0 -36
  136. package/dist/src/rabitq-ivf/src/rabitq.d.ts +0 -95
  137. package/dist/src/rabitq-ivf/src/rabitq.js +0 -283
  138. package/dist/src/sema.d.ts +0 -31
  139. package/dist/src/sema.js +0 -63
  140. package/dist/src/store-sqlite.d.ts +0 -176
  141. package/dist/src/store-sqlite.js +0 -890
  142. package/dist/src/store.d.ts +0 -642
  143. package/dist/src/store.js +0 -1612
  144. package/dist/src/vec.d.ts +0 -31
  145. package/dist/src/vec.js +0 -109
@@ -1,342 +0,0 @@
1
- // extraction.ts — Extraction (Skill) — Section 4 of the mind.
2
- //
3
- // Given a query and its consensus-ranked anchors, find the first span-shaped
4
- // skill exemplar among the ranked anchors and read the analogous span of the
5
- // query. A skill exemplar is a learnt fact whose context and answer together
6
- // form a span-in-context pattern: the answer is a subsequence of the context
7
- // (or one of its pieces is), and the context is the smallest spanning frame
8
- // that contains it.
9
- import { foldTree, perceive, read, resolve } from "../primitives.js";
10
- import { follow, locate } from "../match.js";
11
- import { chooseAmong, hubBound } from "../traverse.js";
12
- import { concatBytes, indexOf } from "../../bytes.js";
13
- import { decodeText, unexplainedLabel } from "../rationale.js";
14
- import { CONCEPT, STEP } from "../graph-search.js";
15
- import { rItem, rNode, traceFail } from "../trace.js";
16
- // ── Extraction ────────────────────────────────────────────────────────────
17
- /** Find the first span-shaped skill exemplar among the ranked anchors from
18
- * climbAttentionAll and read the analogous span from the query. Returns
19
- * the extracted bytes PLUS the query spans the skill ACCOUNTED FOR — the
20
- * located frames AND any read span BOUNDED by located frames on both
21
- * sides, the elementary evidence think's grounding decider weighs. A
22
- * bounded read is explained: the skill located both its borders in the
23
- * query and emitted exactly what sits between them. An OPEN-ENDED read
24
- * (the exemplar's answer reaches the context's end, so the query is read
25
- * to its own end with no located right border) remains a guess about where
26
- * the span stops — it stays unaccounted, priced by exclusion like the
27
- * cover's bridged bytes. (Accounting frames only — the earlier convention
28
- * — let a CAST juxtaposition that merely echoed the query's exact site
29
- * outweigh a correct bounded extraction: the same span counted as
30
- * explained for one mechanism and not the other, and the asymmetry, not
31
- * the answers' merits, decided the grounding.) Null when no skill
32
- * applies. */
33
- export async function extractBySkill(ctx, query, pre) {
34
- const t = ctx.trace?.enter("extractBySkill", [
35
- rItem(query, "query"),
36
- ]);
37
- const fail = traceFail(t);
38
- // Use climbAttentionAll to get the FULL ranked list, not just the
39
- // roots that cleared commitVotes' significance floor. The floor
40
- // gates further points of attention for fusion, but extraction only
41
- // needs ONE anchor that IS a span-shaped skill exemplar — and on
42
- // some seeds the top-voted anchor is not one (e.g. a concept-merge
43
- // nickname outvotes the painting exemplars on shared substrings,
44
- // while the exemplars' votes fall below the floor). Iterating the
45
- // ranked list instead of just the roots lets extraction reach the
46
- // first painting-exemplar anchor regardless of its floor status.
47
- //
48
- const { ranked } = await pre.attention();
49
- if (ranked.length === 0) {
50
- return fail("no consensus anchor — no skill to apply");
51
- }
52
- let exemplar = null;
53
- let chosenAnchor = null;
54
- let skipped = 0;
55
- for (const cand of ranked) {
56
- const ex = await pre.spanShapedOf(cand.anchor);
57
- if (ex) {
58
- exemplar = ex;
59
- chosenAnchor = cand.anchor;
60
- break;
61
- }
62
- skipped++;
63
- }
64
- if (exemplar === null) {
65
- ctx.trace?.step("trySkillAnchors", [], [], `none of ${ranked.length} ranked anchor(s) is a span-shaped skill exemplar`);
66
- return fail("no consensus root is a span-shaped skill exemplar");
67
- }
68
- if (skipped > 0) {
69
- ctx.trace?.step("trySkillAnchors", [
70
- rItem(query.subarray(0, 0), `skipped ${skipped}`),
71
- rNode(ctx, chosenAnchor, "chosen"),
72
- ], [], `skipped ${skipped} anchor(s) that are not skill exemplars`);
73
- }
74
- const { contextBytes, answerBytes } = exemplar;
75
- const ansCtxRuns = answerRunsInContext(ctx, contextBytes, answerBytes);
76
- if (ansCtxRuns === null || ansCtxRuns.length === 0) {
77
- return fail("answer is not a subsequence of the context");
78
- }
79
- if (ansCtxRuns.length > 1) {
80
- ctx.trace?.step("decomposeAnswer", [rItem(answerBytes, "multi-piece-answer")], ansCtxRuns.map((r) => rItem(contextBytes.subarray(r.start, r.end), "piece", undefined, [
81
- r.start,
82
- r.end,
83
- ])), `answer splits into ${ansCtxRuns.length} piece(s) within the exemplar context`);
84
- }
85
- const pieces = [];
86
- const accounted = [];
87
- for (let ri = 0; ri < ansCtxRuns.length; ri++) {
88
- const run = ansCtxRuns[ri];
89
- const isLast = ri === ansCtxRuns.length - 1;
90
- const framePreLen = Math.min(run.start, ctx.space.maxGroup);
91
- const framePre = run.start > 0
92
- ? contextBytes.subarray(run.start - framePreLen, run.start)
93
- : null;
94
- const frames = [];
95
- let start = 0;
96
- if (framePre) {
97
- const prePos = locate(ctx, query, framePre, 0, pre.rec.sites);
98
- if (prePos < 0)
99
- continue;
100
- start = prePos + framePre.length;
101
- frames.push([prePos, start]); // the located frame IS matched evidence
102
- }
103
- let end;
104
- if (isLast) {
105
- if (run.end < contextBytes.length) {
106
- const framePostLen = Math.min(contextBytes.length - run.end, ctx.space.maxGroup);
107
- const framePost = contextBytes.subarray(run.end, run.end + framePostLen);
108
- const postPos = locate(ctx, query.subarray(start), framePost, 0, pre.rec.sites);
109
- if (postPos < 0)
110
- continue;
111
- end = start + postPos;
112
- frames.push([end, end + framePost.length]); // matched post-frame
113
- }
114
- else {
115
- end = query.length;
116
- }
117
- }
118
- else {
119
- const nextRun = ansCtxRuns[ri + 1];
120
- const nextPreLen = Math.min(nextRun.start, ctx.space.maxGroup);
121
- const nextPre = contextBytes.subarray(nextRun.start - nextPreLen, nextRun.start);
122
- const nextPos = locate(ctx, query.subarray(start), nextPre, 0, pre.rec.sites);
123
- if (nextPos < 0) {
124
- end = start + run.ansLen;
125
- }
126
- else {
127
- end = start + nextPos;
128
- frames.push([end, end + nextPre.length]); // matched next-frame
129
- }
130
- }
131
- if (start >= end)
132
- continue;
133
- pieces.push(query.subarray(start, end));
134
- accounted.push(...frames);
135
- // Bounded on both sides ⇒ the read span itself is explained (see doc).
136
- // frames carries the pre-border (when the answer is not at the context's
137
- // start) and the located right border (post-frame or next piece's
138
- // pre-frame); only when BOTH borders were located is the read bounded.
139
- const preBounded = run.start === 0 || frames.some(([, e]) => e === start);
140
- const postBounded = frames.some(([b]) => b === end);
141
- if (preBounded && postBounded)
142
- accounted.push([start, end]);
143
- }
144
- if (pieces.length === 0) {
145
- return fail("no answer piece's frame located in the query");
146
- }
147
- const out = pieces.length === 1 ? pieces[0] : concatBytes(pieces);
148
- t?.done([rItem(out, "extracted")], pieces.length === 1
149
- ? `apply a learnt extraction skill — read the analogous span of the query` +
150
- ` framed like "${decodeText(answerBytes)}" sits in its exemplar`
151
- : `apply a learnt MULTI-PIECE skill — read ${pieces.length} analogous` +
152
- ` pieces of the query and synthesize them like "${decodeText(answerBytes)}"`);
153
- return {
154
- bytes: out,
155
- accounted,
156
- unexplained: unexplainedLabel(query, accounted),
157
- };
158
- }
159
- // ── The two span-shape readings: OPEN acceptance vs. STRONG decomposition ──
160
- //
161
- // isSpanShaped and answerRunsInContext read the SAME relation ("the answer is
162
- // drawn from the context") at two deliberately different strengths, and they
163
- // are NOT interchangeable:
164
- //
165
- // • isSpanShaped — the OPEN reading: any in-order embedding (a sparse
166
- // subsequence, arbitrary gaps). O(|context|) byte scan. Used to ACCEPT
167
- // an exemplar candidate.
168
- // • answerRunsInContext — the STRONG reading: a greedy longest-run
169
- // DECOMPOSITION into contiguous pieces. Greedy-longest is strictly
170
- // stronger than subsequence (a long late match can consume context an
171
- // earlier shorter choice needed), so an ACCEPTED exemplar can still fail
172
- // to decompose — extractBySkill then fails with "answer is not a
173
- // subsequence of the context" and think falls through to recall. That
174
- // fall-through is BEHAVIOUR, pinned by the extraction suites: do not
175
- // "unify" the two into one machine — replacing the open reading with the
176
- // strong one silently rejects exemplars extraction today accepts, and
177
- // replacing the strong one with a backtracking embedding changes which
178
- // pieces are read out of the query.
179
- /** Decompose an answer into substrings of its surrounding context, in order —
180
- * the STRONG span-shape reading (see the section note above). Returns null
181
- * when no greedy longest-run decomposition exists. Adjacent runs that
182
- * connect contiguously are merged. */
183
- export function answerRunsInContext(_ctx, context, answer) {
184
- const pos = indexOf(context, answer, 0);
185
- if (pos >= 0) {
186
- return [{ start: pos, end: pos + answer.length, ansLen: answer.length }];
187
- }
188
- const runs = [];
189
- let ai = 0;
190
- let ci = 0;
191
- while (ai < answer.length) {
192
- // Longest match of the remaining answer at any position of the remaining
193
- // context: one direct extend per context position — O(|ctx|·match) per
194
- // run, replacing the previous longest-first indexOf countdown whose
195
- // repeated scans were cubic on long sparse-subsequence answers.
196
- let bestLen = 0;
197
- let bestPos = -1;
198
- for (let p = ci; p < context.length; p++) {
199
- let l = 0;
200
- const maxL = Math.min(context.length - p, answer.length - ai);
201
- if (maxL <= bestLen)
202
- break; // no later position can beat the best
203
- while (l < maxL && context[p + l] === answer[ai + l])
204
- l++;
205
- if (l > bestLen) {
206
- bestLen = l;
207
- bestPos = p;
208
- if (ai + l === answer.length)
209
- break; // the whole remainder matched
210
- }
211
- }
212
- if (bestLen === 0)
213
- return null;
214
- runs.push({ start: bestPos, end: bestPos + bestLen, ansLen: bestLen });
215
- ai += bestLen;
216
- ci = bestPos + bestLen;
217
- }
218
- const merged = [];
219
- for (const r of runs) {
220
- const last = merged[merged.length - 1];
221
- if (last && r.start === last.end) {
222
- last.end = r.end;
223
- last.ansLen += r.ansLen;
224
- }
225
- else {
226
- merged.push({ ...r });
227
- }
228
- }
229
- return merged.length > 0 ? merged : null;
230
- }
231
- /** Check whether an anchor is a span-shaped skill exemplar: it represents a
232
- * fact whose context and answer together form a span-in-context pattern.
233
- * If the anchor has a nextOf continuation, that is the answer and the anchor
234
- * itself is the context. Otherwise the anchor's prevOf parents provide
235
- * candidate contexts, and the longest one whose span is span-shaped wins. */
236
- export async function skillExemplar(ctx, anchor, guide) {
237
- if (ctx.store.hasNext(anchor)) {
238
- const contextBytes = read(ctx, anchor);
239
- const answerBytes = await follow(ctx, anchor, guide);
240
- if (answerBytes !== null && isSpanShaped(ctx, contextBytes, answerBytes)) {
241
- return { contextBytes, answerBytes };
242
- }
243
- return null;
244
- }
245
- const answerBytes = read(ctx, anchor);
246
- // Candidate contexts, capped at the hub bound (a common answer's reverse
247
- // fan-in is corpus-sized).
248
- const capped = ctx.store.prevFirst(anchor, hubBound(ctx));
249
- const spanShaped = [];
250
- for (const p of capped) {
251
- const ctxB = read(ctx, p);
252
- if (ctxB.length > 0 && isSpanShaped(ctx, ctxB, answerBytes)) {
253
- spanShaped.push({ id: p, bytes: ctxB });
254
- }
255
- }
256
- if (spanShaped.length === 0)
257
- return null;
258
- // Among span-shaped contexts, the longest wins (the smallest spanning frame
259
- // heuristic's dual: more frame to locate in the query); the query gist,
260
- // when given, breaks LENGTH TIES via chooseAmong — the same reverse-regime
261
- // disambiguator every context pick uses, whose gist cache spares the
262
- // re-fold this block once paid per tied candidate. Same strict first-seen
263
- // tie-break as the hand loop it replaces.
264
- const maxLen = Math.max(...spanShaped.map((s) => s.bytes.length));
265
- const longest = spanShaped.filter((s) => s.bytes.length === maxLen);
266
- let contextBytes = longest[0].bytes;
267
- if (guide && longest.length > 1) {
268
- const pick = chooseAmong(ctx, longest.map((s) => s.id), guide).id;
269
- contextBytes = longest.find((s) => s.id === pick).bytes;
270
- }
271
- return { contextBytes, answerBytes };
272
- }
273
- /** Whether the answer is a SPARSE subsequence of the context (bytes in
274
- * order, arbitrary gaps) — the OPEN span-shape reading (see the section
275
- * note above). This is what lets extraction validate a MULTI-PIECE
276
- * exemplar whose answer is stitched from several context runs — but it is
277
- * deliberately permissive, so it must never be used as evidence that one
278
- * span was "drawn from" another (see {@link containsSpan} for that).
279
- *
280
- * There is deliberately NO containsSpan pre-check here: strict containment
281
- * IMPLIES the subsequence embedding (a contiguous run, or a resolved node —
282
- * whose content-addressed identity means its bytes occur contiguously — is
283
- * an in-order embedding with zero gaps), so the scan below decides alone,
284
- * with the same truth value. The old pre-check re-perceived the context
285
- * (a full river fold) per CANDIDATE in skillExemplar's √N-capped loop —
286
- * pure cost, no discrimination. */
287
- export function isSpanShaped(_ctx, context, answer) {
288
- let ai = 0;
289
- for (let ci = 0; ci < context.length && ai < answer.length; ci++) {
290
- if (context[ci] === answer[ai])
291
- ai++;
292
- }
293
- return ai === answer.length;
294
- }
295
- /** STRICT containment: the answer's resolved node appears in the context's
296
- * folded tree, or the answer occurs as one CONTIGUOUS byte run of the
297
- * context. This is real evidence the answer was drawn from the context.
298
- * Fusion gates on this — the sparse-subsequence reading of
299
- * {@link isSpanShaped} is trivially satisfied by short answers over long
300
- * queries ("cold" is a gap-tolerant subsequence of most sentences holding
301
- * c…o…l…d in order), and gating fusion on it silently starved multi-topic
302
- * queries of their further points of attention. */
303
- export function containsSpan(ctx, context, answer) {
304
- const ansId = resolve(ctx, answer);
305
- if (ansId !== null) {
306
- let found = false;
307
- foldTree(ctx, perceive(ctx, context), 0, (_n, _s, _e, node) => {
308
- if (node === ansId)
309
- found = true;
310
- });
311
- if (found)
312
- return true;
313
- }
314
- return indexOf(context, answer, 0) >= 0;
315
- }
316
- // ── Pipeline mechanism ──────────────────────────────────────────────────────
317
- export const extractionMechanism = {
318
- name: "extraction",
319
- provenance: "extract",
320
- async floor(_ctx, _query, pre, worthRunning) {
321
- // Extraction's floor is always exactly CONCEPT+STEP when it exists —
322
- // same investment discipline as CAST's (see cast.ts): when the bound
323
- // already cannot beat the incumbent, return it UNINVESTED (never
324
- // first-touch the climb just to be pruned).
325
- if (!worthRunning(CONCEPT + STEP))
326
- return CONCEPT + STEP;
327
- if ((await pre.attention()).ranked.length === 0)
328
- return null;
329
- return CONCEPT + STEP;
330
- },
331
- async run(ctx, query, pre) {
332
- const ex = await extractBySkill(ctx, query, pre);
333
- if (!ex)
334
- return [];
335
- return [{
336
- bytes: ex.bytes,
337
- accounted: ex.accounted,
338
- moves: CONCEPT + STEP * ex.accounted.length,
339
- unexplained: ex.unexplained,
340
- }];
341
- },
342
- };
@@ -1,13 +0,0 @@
1
- import type { MindContext } from "../types.js";
2
- import type { PipelineMechanism, Precomputed } from "../pipeline-mechanism.js";
3
- /** A recall result. */
4
- export interface RecallResult {
5
- bytes: Uint8Array;
6
- echoed: boolean;
7
- accounted: Array<[number, number]>;
8
- moves: number;
9
- unexplained: string;
10
- }
11
- /** Recall the answer by resonating the whole query against the content index. */
12
- export declare function recallByResonance(ctx: MindContext, query: Uint8Array, pre: Precomputed): Promise<RecallResult | null>;
13
- export declare const recallMechanism: PipelineMechanism;
@@ -1,166 +0,0 @@
1
- // mechanisms/recall.ts — Recall by resonance (Grounding IV).
2
- //
3
- // The recall mechanism resonates the whole query's gist against the content
4
- // index and grounds the nearest learned form. Four tiers, orderly degrading
5
- // from exact self-match to honest echo.
6
- import { cosine } from "../../vec.js";
7
- import { consensusFloor, identityBar, reachThreshold, significanceBar, } from "../../geometry.js";
8
- import { gistOf, read, resolve } from "../primitives.js";
9
- import { corpusN, hubBound } from "../traverse.js";
10
- import { project, reverseContext } from "../match.js";
11
- import { CONCEPT, STEP } from "../graph-search.js";
12
- import { unexplainedLabel } from "../rationale.js";
13
- import { rItem, rNode } from "../trace.js";
14
- /** Recall the answer by resonating the whole query against the content index. */
15
- export async function recallByResonance(ctx, query, pre) {
16
- const t = ctx.trace?.enter("recallByResonance", [
17
- rItem(query, "query"),
18
- ]);
19
- const whole_ = [[0, query.length]];
20
- const nothing = [];
21
- const ground = (bytes, note, accounted, moves, echoed = false) => {
22
- t?.done(bytes === null
23
- ? []
24
- : [rItem(bytes, "answer", resolve(ctx, bytes) ?? undefined)], note);
25
- return bytes === null ? null : {
26
- bytes,
27
- echoed,
28
- accounted,
29
- moves,
30
- unexplained: unexplainedLabel(query, accounted),
31
- };
32
- };
33
- const k = pre.k;
34
- const queryGist = pre.guide;
35
- // 0. Exact self-match — content-addressed, deterministic.
36
- const qId = pre.queryResolved;
37
- if (qId !== null) {
38
- const rev = ctx.store.prevFirst(qId, hubBound(ctx));
39
- const g = reverseContext(ctx, qId, queryGist, rev);
40
- if (g !== null) {
41
- return ground(g, rev.length === 1
42
- ? "exact self-match — reverse recall to the sole predecessor"
43
- : "exact self-match — reverse recall to the best-resonating predecessor", nothing, STEP);
44
- }
45
- }
46
- const whole = await ctx.store.resonate(queryGist, k);
47
- if (whole.length === 0) {
48
- return ground(null, "empty store — nothing to resonate with", [], 0);
49
- }
50
- const top = whole[0];
51
- ctx.trace?.step("resonate", [rItem(query, "query-gist")], whole.map((h) => rNode(ctx, h.id, "hit", h.score)), `resonate the whole-query gist → ${whole.length} nearest learnt form(s)`);
52
- // 1. Clean resonance — the scale-aware identity claim. The ANGLE
53
- // (top.score) carries the shared fraction; the query's MAGNITUDE (√len,
54
- // the linear fold's own norm) converts the tolerated foreign fraction
55
- // into bytes — at most one river window (see {@link identityBar}). A
56
- // fixed cosine bar let long queries claim "near-identical" while whole
57
- // windows — an answer word — differed.
58
- if (top.score >= identityBar(ctx.store.D, ctx.space.maxGroup, query.length)) {
59
- for (const h of whole) {
60
- if (h.id === qId || h.score >= 1.0) {
61
- const rev = ctx.store.prevFirst(h.id, hubBound(ctx));
62
- const g = reverseContext(ctx, h.id, queryGist, rev);
63
- if (g !== null) {
64
- return ground(g, rev.length === 1
65
- ? "perfect self-match — reverse recall to the sole predecessor"
66
- : "perfect self-match — reverse recall to the best-resonating predecessor", nothing, STEP);
67
- }
68
- const own = read(ctx, h.id);
69
- if (own.length > 0) {
70
- return ground(own, "perfect self-match — the query IS this node", nothing, STEP);
71
- }
72
- }
73
- const g = await project(ctx, h.id, queryGist);
74
- if (g) {
75
- return ground(g, "clean whole-query resonance — ground the nearest hit", whole_, STEP);
76
- }
77
- }
78
- }
79
- // 2. Scaffolding-dominated.
80
- if (top.score >= significanceBar(ctx.store.D)) {
81
- const N = corpusN(ctx);
82
- const minVote = consensusFloor(N);
83
- // The committed points of attention ARE the shared climb's roots (same
84
- // query, same k, same DF mode) — read them from Precomputed instead of
85
- // re-climbing, so even a traced response pays for the climb once.
86
- const forest = (await pre.attention()).roots;
87
- if (forest.length > 0 && forest[0].vote >= minVote) {
88
- const g = await project(ctx, forest[0].anchor, queryGist);
89
- if (g) {
90
- return ground(g, "scaffolding-dominated query — ground the consensus-climb anchor", [[forest[0].start, forest[0].end]], CONCEPT);
91
- }
92
- }
93
- }
94
- // 3. Last resort — gated on the FRACTION OF THE QUERY the grounding
95
- // explains, not the raw cosine. Root gists are unit vectors, but their
96
- // magnitudes are recoverable from the byte lengths (‖·‖ = √len under the
97
- // linear fold): cos = shared/√(lenQ·lenG), so shared/lenQ = cos·√(lenG/lenQ).
98
- // The raw cosine punished honest containment — a query fully inside a
99
- // longer grounded answer scored √(lenQ/lenG) and was refused — and let a
100
- // long answer sharing only scaffolding pass; the query-relative fraction
101
- // measures exactly what the reach bar means: how much of THE QUERY the
102
- // store accounts for.
103
- // Chance similarity survives the length conversion AMPLIFIED: the same
104
- // √(lenG/lenQ) factor that converts an honest shared fraction into a
105
- // query-relative one multiplies the estimator/chance floor too, so a long
106
- // stored form (√(lenG/lenQ) ≈ 10 at 100×) lifted a noise-level cosine past
107
- // the reach bar and grounded pure gibberish (observed). Only the
108
- // ABOVE-CHANCE part of the similarity is evidence of shared content —
109
- // subtract the significance bar (3/√D, §8.3) before converting. Derived
110
- // from the existing bars; never tuned.
111
- const sig = significanceBar(ctx.store.D);
112
- const fracOfQuery = (cos, otherLen) => Math.min(1, Math.max(0, cos - sig) *
113
- Math.sqrt(otherLen / Math.max(1, query.length)));
114
- for (const h of whole) {
115
- const g = await project(ctx, h.id, queryGist);
116
- if (g) {
117
- if (fracOfQuery(cosine(queryGist, gistOf(ctx, g)), g.length) >=
118
- reachThreshold(ctx.space.maxGroup)) {
119
- return ground(g, "last resort: the nearest grounded whole-query hit", [], STEP);
120
- }
121
- }
122
- }
123
- // The refusal/echo decision. The echo returns a stored form's bytes AS
124
- // the answer — a near-identity claim about the query — and identity-grade
125
- // decisions are never made on an estimated score ("approximate scores may
126
- // rank and propose; they may never decide", §6.2): the RaBitQ estimate
127
- // overshooting the reach bar echoed a WRONG-entity neighbour ("capital of
128
- // Zamunda?" echoed the Armenia fact, observed). The bytes are read
129
- // anyway to be echoed, so the decision uses their EXACT fold: one river
130
- // fold of the top hit, measured in the same query-relative,
131
- // chance-corrected units as the tier above.
132
- const reach = reachThreshold(ctx.space.maxGroup);
133
- const topBytes = read(ctx, top.id);
134
- const exact = topBytes.length > 0
135
- ? cosine(queryGist, gistOf(ctx, topBytes))
136
- : 0;
137
- if (fracOfQuery(exact, topBytes.length) < reach) {
138
- return ground(null, "below reach threshold — nothing in the store relates to this query", [], 0);
139
- }
140
- // Honest echo.
141
- return ground(topBytes, "last resort: the nearest resonant form's own bytes (echo, not grounded)", [], 0, true);
142
- }
143
- // ── Pipeline mechanism ──────────────────────────────────────────────────────
144
- export const recallMechanism = {
145
- name: "recall",
146
- provenance: "recall",
147
- // Recall's floor is free to state (one STEP-grade projection) and its run
148
- // gates its own tiers — no expensive investment happens inside floor, so
149
- // there is nothing to guard with worthRunning here: the pipeline's own
150
- // check prunes run() against the incumbent.
151
- async floor(_ctx, _query, _pre, _worthRunning) {
152
- return STEP;
153
- },
154
- async run(ctx, query, pre) {
155
- const r = await recallByResonance(ctx, query, pre);
156
- if (!r)
157
- return [];
158
- return [{
159
- bytes: r.bytes,
160
- accounted: r.accounted,
161
- moves: r.moves,
162
- unexplained: r.unexplained,
163
- provenance: r.echoed ? "recall-echo" : "recall",
164
- }];
165
- },
166
- };