@hviana/sema 0.4.0 → 0.4.2

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 (154) hide show
  1. package/CONTRIBUTING.md +92 -10
  2. package/LICENSE.md +2 -2
  3. package/package.json +1 -1
  4. package/src/ingest-cache.ts +4 -0
  5. package/src/meter.ts +5 -0
  6. package/src/mind/attention.ts +18 -1
  7. package/src/mind/bridge.ts +292 -54
  8. package/src/mind/junction.ts +21 -7
  9. package/src/mind/match.ts +92 -1
  10. package/src/mind/mechanisms/cast.ts +12 -0
  11. package/src/mind/mechanisms/confluence.ts +30 -1
  12. package/src/mind/mechanisms/cover.ts +36 -4
  13. package/src/mind/mechanisms/recall.ts +21 -44
  14. package/src/mind/mind.ts +39 -2
  15. package/src/mind/pipeline-mechanism.ts +86 -4
  16. package/src/mind/pipeline.ts +1 -1
  17. package/src/mind/reasoning.ts +15 -8
  18. package/src/mind/recognition.ts +40 -0
  19. package/src/mind/resonance.ts +0 -0
  20. package/src/mind/traverse.ts +17 -15
  21. package/src/mind/types.ts +10 -0
  22. package/test/49-natural-units-synonym-bridge.test.mjs +56 -15
  23. package/dist/example/demo.d.ts +0 -1
  24. package/dist/example/demo.js +0 -39
  25. package/dist/example/train_base.d.ts +0 -87
  26. package/dist/example/train_base.js +0 -2252
  27. package/dist/src/alphabet.d.ts +0 -7
  28. package/dist/src/alphabet.js +0 -33
  29. package/dist/src/alu/src/alu.d.ts +0 -185
  30. package/dist/src/alu/src/alu.js +0 -440
  31. package/dist/src/alu/src/expr.d.ts +0 -61
  32. package/dist/src/alu/src/expr.js +0 -318
  33. package/dist/src/alu/src/index.d.ts +0 -11
  34. package/dist/src/alu/src/index.js +0 -19
  35. package/dist/src/alu/src/kernel-arith.d.ts +0 -16
  36. package/dist/src/alu/src/kernel-arith.js +0 -264
  37. package/dist/src/alu/src/kernel-bits.d.ts +0 -19
  38. package/dist/src/alu/src/kernel-bits.js +0 -152
  39. package/dist/src/alu/src/kernel-logic.d.ts +0 -4
  40. package/dist/src/alu/src/kernel-logic.js +0 -60
  41. package/dist/src/alu/src/kernel-nd.d.ts +0 -3
  42. package/dist/src/alu/src/kernel-nd.js +0 -208
  43. package/dist/src/alu/src/kernel-numeric.d.ts +0 -54
  44. package/dist/src/alu/src/kernel-numeric.js +0 -366
  45. package/dist/src/alu/src/operation.d.ts +0 -168
  46. package/dist/src/alu/src/operation.js +0 -189
  47. package/dist/src/alu/src/parser.d.ts +0 -221
  48. package/dist/src/alu/src/parser.js +0 -577
  49. package/dist/src/alu/src/resonance.d.ts +0 -55
  50. package/dist/src/alu/src/resonance.js +0 -126
  51. package/dist/src/alu/src/text.d.ts +0 -31
  52. package/dist/src/alu/src/text.js +0 -73
  53. package/dist/src/alu/src/value.d.ts +0 -109
  54. package/dist/src/alu/src/value.js +0 -300
  55. package/dist/src/alu/test/alu.test.d.ts +0 -1
  56. package/dist/src/alu/test/alu.test.js +0 -764
  57. package/dist/src/bytes.d.ts +0 -14
  58. package/dist/src/bytes.js +0 -59
  59. package/dist/src/canon.d.ts +0 -26
  60. package/dist/src/canon.js +0 -57
  61. package/dist/src/config.d.ts +0 -111
  62. package/dist/src/config.js +0 -91
  63. package/dist/src/derive/src/deduction.d.ts +0 -136
  64. package/dist/src/derive/src/deduction.js +0 -159
  65. package/dist/src/derive/src/index.d.ts +0 -8
  66. package/dist/src/derive/src/index.js +0 -11
  67. package/dist/src/derive/src/priority-queue.d.ts +0 -20
  68. package/dist/src/derive/src/priority-queue.js +0 -73
  69. package/dist/src/derive/src/rewrite.d.ts +0 -56
  70. package/dist/src/derive/src/rewrite.js +0 -100
  71. package/dist/src/derive/src/trie.d.ts +0 -90
  72. package/dist/src/derive/src/trie.js +0 -217
  73. package/dist/src/derive/test/derive.test.d.ts +0 -1
  74. package/dist/src/derive/test/derive.test.js +0 -122
  75. package/dist/src/extension.d.ts +0 -37
  76. package/dist/src/extension.js +0 -7
  77. package/dist/src/geometry.d.ts +0 -175
  78. package/dist/src/geometry.js +0 -823
  79. package/dist/src/index.d.ts +0 -17
  80. package/dist/src/index.js +0 -19
  81. package/dist/src/ingest-cache.d.ts +0 -41
  82. package/dist/src/ingest-cache.js +0 -161
  83. package/dist/src/meter.d.ts +0 -171
  84. package/dist/src/meter.js +0 -269
  85. package/dist/src/mind/articulation.d.ts +0 -6
  86. package/dist/src/mind/articulation.js +0 -99
  87. package/dist/src/mind/attention.d.ts +0 -414
  88. package/dist/src/mind/attention.js +0 -2065
  89. package/dist/src/mind/bridge.d.ts +0 -39
  90. package/dist/src/mind/bridge.js +0 -738
  91. package/dist/src/mind/canonical.d.ts +0 -34
  92. package/dist/src/mind/canonical.js +0 -93
  93. package/dist/src/mind/graph-search.d.ts +0 -294
  94. package/dist/src/mind/graph-search.js +0 -996
  95. package/dist/src/mind/index.d.ts +0 -9
  96. package/dist/src/mind/index.js +0 -5
  97. package/dist/src/mind/junction.d.ts +0 -134
  98. package/dist/src/mind/junction.js +0 -331
  99. package/dist/src/mind/learning.d.ts +0 -75
  100. package/dist/src/mind/learning.js +0 -270
  101. package/dist/src/mind/match.d.ts +0 -166
  102. package/dist/src/mind/match.js +0 -565
  103. package/dist/src/mind/mechanisms/alu.d.ts +0 -4
  104. package/dist/src/mind/mechanisms/alu.js +0 -36
  105. package/dist/src/mind/mechanisms/cast.d.ts +0 -89
  106. package/dist/src/mind/mechanisms/cast.js +0 -773
  107. package/dist/src/mind/mechanisms/confluence.d.ts +0 -24
  108. package/dist/src/mind/mechanisms/confluence.js +0 -225
  109. package/dist/src/mind/mechanisms/cover.d.ts +0 -6
  110. package/dist/src/mind/mechanisms/cover.js +0 -203
  111. package/dist/src/mind/mechanisms/extraction.d.ts +0 -33
  112. package/dist/src/mind/mechanisms/extraction.js +0 -300
  113. package/dist/src/mind/mechanisms/recall.d.ts +0 -16
  114. package/dist/src/mind/mechanisms/recall.js +0 -388
  115. package/dist/src/mind/mind.d.ts +0 -330
  116. package/dist/src/mind/mind.js +0 -594
  117. package/dist/src/mind/pipeline-mechanism.d.ts +0 -172
  118. package/dist/src/mind/pipeline-mechanism.js +0 -382
  119. package/dist/src/mind/pipeline.d.ts +0 -49
  120. package/dist/src/mind/pipeline.js +0 -275
  121. package/dist/src/mind/primitives.d.ts +0 -66
  122. package/dist/src/mind/primitives.js +0 -306
  123. package/dist/src/mind/rationale.d.ts +0 -139
  124. package/dist/src/mind/rationale.js +0 -163
  125. package/dist/src/mind/reasoning.d.ts +0 -40
  126. package/dist/src/mind/reasoning.js +0 -272
  127. package/dist/src/mind/recognition.d.ts +0 -20
  128. package/dist/src/mind/recognition.js +0 -463
  129. package/dist/src/mind/resonance.d.ts +0 -23
  130. package/dist/src/mind/resonance.js +0 -0
  131. package/dist/src/mind/trace.d.ts +0 -15
  132. package/dist/src/mind/trace.js +0 -73
  133. package/dist/src/mind/traverse.d.ts +0 -124
  134. package/dist/src/mind/traverse.js +0 -649
  135. package/dist/src/mind/types.d.ts +0 -323
  136. package/dist/src/mind/types.js +0 -130
  137. package/dist/src/rabitq-ivf/src/database.d.ts +0 -113
  138. package/dist/src/rabitq-ivf/src/database.js +0 -201
  139. package/dist/src/rabitq-ivf/src/index.d.ts +0 -7
  140. package/dist/src/rabitq-ivf/src/index.js +0 -4
  141. package/dist/src/rabitq-ivf/src/ivf.d.ts +0 -200
  142. package/dist/src/rabitq-ivf/src/ivf.js +0 -1165
  143. package/dist/src/rabitq-ivf/src/prng.d.ts +0 -19
  144. package/dist/src/rabitq-ivf/src/prng.js +0 -36
  145. package/dist/src/rabitq-ivf/src/rabitq.d.ts +0 -95
  146. package/dist/src/rabitq-ivf/src/rabitq.js +0 -283
  147. package/dist/src/sema.d.ts +0 -31
  148. package/dist/src/sema.js +0 -63
  149. package/dist/src/store-sqlite.d.ts +0 -184
  150. package/dist/src/store-sqlite.js +0 -942
  151. package/dist/src/store.d.ts +0 -678
  152. package/dist/src/store.js +0 -1703
  153. package/dist/src/vec.d.ts +0 -31
  154. package/dist/src/vec.js +0 -109
@@ -1,275 +0,0 @@
1
- // pipeline.ts — the think pipeline (Section 5 of the mind).
2
- //
3
- // think() is the whole file's job: one lightest-derivation choice among
4
- // UNIFORM mechanisms. The pipeline sees mechanisms through the
5
- // PipelineMechanism interface only — it never imports a mechanism-specific
6
- // type and never has a special-case branch for any mechanism. Adding a
7
- // mechanism means registering one object; removing one means dropping it
8
- // from the list. The mechanisms themselves live in mechanisms/ (one file
9
- // each); the shared pre-computation they exchange lives in Precomputed
10
- // (pipeline-mechanism.ts).
11
- import { PASS, STEP } from "./graph-search.js";
12
- import { gistOf, resolve } from "./primitives.js";
13
- import { recognise } from "./recognition.js";
14
- import { fuseAttention, reason } from "./reasoning.js";
15
- import { unexplainedSpans } from "./rationale.js";
16
- import { rItem } from "./trace.js";
17
- import { Precomputed } from "./pipeline-mechanism.js";
18
- import { coverMechanism } from "./mechanisms/cover.js";
19
- import { castMechanism } from "./mechanisms/cast.js";
20
- import { confluenceMechanism } from "./mechanisms/confluence.js";
21
- import { extractionMechanism } from "./mechanisms/extraction.js";
22
- import { recallMechanism } from "./mechanisms/recall.js";
23
- // Re-exports: cover's pre-resolution helpers and the ALU adapter kept
24
- // importable from the pipeline module (their historical home).
25
- export { resolveConcepts, resolveConnectors } from "./mechanisms/cover.js";
26
- export { aluToMechanism } from "./mechanisms/alu.js";
27
- // ── Extension dispatch (pre-loop parse) ─────────────────────────────────────
28
- async function collectComputed(ctx, mechanisms, query) {
29
- const out = [];
30
- const meter = ctx.meter;
31
- for (const m of mechanisms) {
32
- if (!m.parse)
33
- continue;
34
- const spans = meter
35
- ? await meter.time(`${m.name}.parse`, () => m.parse(query))
36
- : await m.parse(query);
37
- out.push(...spans);
38
- }
39
- return out;
40
- }
41
- // ── Built-in mechanisms ─────────────────────────────────────────────────────
42
- // ORDER MATTERS, but only through the uniform floor/worthRunning pruning —
43
- // no mechanism is special-cased. Cover runs FIRST: when a computed
44
- // extension result (e.g. ALU) exists, cover masks it in at near-zero cost
45
- // (see mechanisms/cover.ts), which becomes `best` before any other mechanism
46
- // invests in its own precomputation. CAST's and confluence's floors (2*STEP,
47
- // 3*STEP) then fail `worthRunning` and are skipped by the SAME admissible-
48
- // floor pruning every mechanism is already subject to — not by asking
49
- // "is this an extension?". Grade TIES keep the earlier candidate, so this
50
- // order is also the tie-break priority: cover, cast, confluence, extraction,
51
- // recall.
52
- export const defaultMechanisms = [
53
- coverMechanism,
54
- castMechanism,
55
- confluenceMechanism,
56
- extractionMechanism,
57
- recallMechanism,
58
- ];
59
- /** Think: a single lightest-derivation exploration of the Sema graph.
60
- *
61
- * Every answer travels the same path:
62
- * 1. Pre-computation — recognise, extension parse, guide; everything
63
- * expensive stays lazy on Precomputed until a mechanism asks.
64
- * 2. Grounding — every mechanism yields candidates weighed in the one
65
- * cost ladder; the lightest grounding derivation wins.
66
- * 3. Post-grounding — diagnostics (narrowDecision, thinGrounding),
67
- * reasoning (multi-hop), fusion (multi-topic). */
68
- export async function think(ctx, query, mechs) {
69
- if (query.length === 0)
70
- return null;
71
- ctx._edgeGuide = gistOf(ctx, query);
72
- ctx._edgeChoice.clear();
73
- const t = ctx.trace?.enter("think", [rItem(query, "query")]);
74
- const done = (answer, note) => {
75
- t?.done(answer
76
- ? [rItem(answer, "answer", resolve(ctx, answer) ?? undefined)]
77
- : [], note);
78
- return answer;
79
- };
80
- // ── Pre-computation ──────────────────────────────────────────────────
81
- const mechanisms = mechs ?? defaultMechanisms;
82
- const rec = recognise(ctx, query);
83
- // Phase 1: collect computed spans from mechanisms that implement parse()
84
- const computed = await collectComputed(ctx, mechanisms, query);
85
- if (computed.length > 0) {
86
- ctx.trace?.step("computeExtensions", [rItem(query, "query")], computed.map((u) => rItem(query.subarray(u.i, u.j), "operand", undefined, [u.i, u.j])), `extensions recognised and evaluated ${computed.length} computation(s)`);
87
- for (const u of computed) {
88
- ctx.trace?.step("evalComputation", [rItem(query.subarray(u.i, u.j), "expression", undefined, [u.i, u.j])], [rItem(u.bytes, "result", resolve(ctx, u.bytes) ?? undefined)], "evaluate the recognised operation to its authoritative result");
89
- }
90
- }
91
- // Phase 2: the shared pre-computation container. Eager fields only
92
- // (recognition, computed spans, guide) — every expensive analysis
93
- // (consensus climb, weave, span-shape classification) is a lazily-cached
94
- // method on Precomputed, first-touched by whichever mechanism's floor
95
- // survives its cheap gates and the worthRunning check. A query no
96
- // mechanism climbs for (e.g. one an extension decided) never climbs.
97
- const pre = new Precomputed(ctx, query, rec, computed, ctx._edgeGuide);
98
- const grade = (w) => Math.floor(w / STEP);
99
- const unaccounted = (spans) => unexplainedSpans(query.length, spans)
100
- .reduce((sum, [s, e]) => sum + (e - s), 0);
101
- const weigh = (accounted, moves) => moves + PASS * unaccounted(accounted);
102
- const candidates = [];
103
- let best = null;
104
- const consider = (c) => {
105
- if (c.bytes.length === 0)
106
- return;
107
- if (ctx.meter)
108
- ctx.meter.candidates++;
109
- candidates.push(c);
110
- if (best === null || grade(c.weight) < grade(best.weight))
111
- best = c;
112
- };
113
- const worthRunning = (floor) => best === null || grade(floor) < grade(best.weight);
114
- // Phase 3: grounding loop
115
- // Per-mechanism accounting (src/meter.ts). The market's whole premise is
116
- // that mechanisms compete on one cost scale — so the profiling read-out is
117
- // also per-mechanism, uniformly: the loop never asks which one it holds.
118
- const meter = ctx.meter;
119
- for (const mech of mechanisms) {
120
- const floor = meter
121
- ? await meter.time(`${mech.name}.floor`, () => mech.floor(ctx, query, pre, worthRunning))
122
- : await mech.floor(ctx, query, pre, worthRunning);
123
- if (meter) {
124
- if (floor === null)
125
- meter.mechanismSkips++;
126
- else
127
- meter.mechanismFloors++;
128
- }
129
- if (floor === null) {
130
- ctx.trace?.step("skipMechanism", [], [], `${mech.name} skipped — structural precondition failed`);
131
- continue;
132
- }
133
- if (!worthRunning(floor)) {
134
- ctx.trace?.step("skipMechanism", [], [], `${mech.name} skipped — floor ${floor} cannot beat incumbent (grade ${grade(best.weight)})`);
135
- continue;
136
- }
137
- if (meter)
138
- meter.mechanismRuns++;
139
- const results = meter
140
- ? await meter.time(`${mech.name}.run`, () => mech.run(ctx, query, pre))
141
- : await mech.run(ctx, query, pre);
142
- for (const r of results) {
143
- const weight = r.weight ?? weigh(r.accounted, r.moves);
144
- consider({
145
- bytes: r.bytes,
146
- provenance: r.provenance ?? mech.provenance,
147
- weight,
148
- used: r.used,
149
- accounted: r.accounted,
150
- unexplained: r.unexplained,
151
- complete: r.complete,
152
- });
153
- }
154
- }
155
- // (TS cannot see the closure assignments into `best` and narrows it to its
156
- // initial null, so the read-back needs the assertion.)
157
- const decided = best;
158
- if (candidates.length > 1) {
159
- // The runner-up is computed BEFORE the decideGrounding step so its grade
160
- // margin can ride along in the step's structured data payload; the
161
- // computation itself is pure and was always unconditional — only its
162
- // position moved.
163
- let runnerUp = null;
164
- if (decided !== null) {
165
- for (const c of candidates) {
166
- if (c === decided)
167
- continue;
168
- if (runnerUp === null || grade(c.weight) < grade(runnerUp.weight)) {
169
- runnerUp = c;
170
- }
171
- }
172
- }
173
- const margin = decided !== null && runnerUp !== null
174
- ? grade(runnerUp.weight) - grade(decided.weight)
175
- : null;
176
- ctx.trace?.step("decideGrounding", candidates.map((c) => rItem(c.bytes, `${c.provenance} (weight ${c.weight.toFixed(3)}${c.unexplained ? `, unexplained: "${c.unexplained}"` : ""})`)), decided ? [rItem(decided.bytes, decided.provenance)] : [], "the lightest grounding derivation wins — every mechanism weighed in the one cost ladder", undefined, {
177
- version: 1,
178
- candidates: candidates.map((c) => ({
179
- provenance: c.provenance,
180
- weight: c.weight,
181
- grade: grade(c.weight),
182
- unexplainedBytes: unaccounted(c.accounted),
183
- decided: c === decided,
184
- })),
185
- ...(margin !== null ? { runnerUpMargin: margin } : {}),
186
- });
187
- if (decided !== null && runnerUp !== null && margin !== null) {
188
- if (margin <= 1) {
189
- ctx.trace?.step("narrowDecision", [
190
- rItem(decided.bytes, `${decided.provenance} (weight ${decided.weight.toFixed(3)})`),
191
- ], [
192
- rItem(runnerUp.bytes, `${runnerUp.provenance} (weight ${runnerUp.weight.toFixed(3)})`),
193
- ], `margin ${margin} grade-unit(s) — the decision could change with one more training fact`, undefined, { version: 1, margin });
194
- }
195
- }
196
- }
197
- if (decided === null) {
198
- done(null, "no mechanism grounded an answer");
199
- return null;
200
- }
201
- // Honesty density
202
- {
203
- const covered = query.length - unaccounted(decided.accounted);
204
- const density = query.length > 0 ? covered / query.length : 1;
205
- const thinBar = 1 / ctx.space.maxGroup;
206
- if (density < thinBar) {
207
- ctx.trace?.step("thinGrounding", [rItem(decided.bytes, decided.provenance)], [], `grounded but thin — density ${density.toFixed(3)} is below 1/W (${thinBar.toFixed(3)})`);
208
- }
209
- }
210
- const answer = decided.bytes;
211
- const provenance = decided.provenance;
212
- const castUsed = decided.used ?? new Set();
213
- // ── Post-grounding, gated by provenance ──────────────────────────────
214
- const preConsumed = provenance === "cast" || provenance === "join"
215
- ? castUsed
216
- : provenance === "recall" || provenance === "recall-echo"
217
- ? new Set()
218
- : new Set(recognise(ctx, answer).sites.map((s) => s.payload));
219
- // A grounding that DECLARED itself complete is not extended: the answer is
220
- // already a trained form's own continuation, reached through an identity
221
- // claim about the query, so a multi-hop pivot could only chain past the
222
- // fact that produced it (see MechanismResult.complete).
223
- const reasoned = decided.complete ? answer : meter
224
- ? await meter.time("reason", () => reason(ctx, query, answer, preConsumed, pre))
225
- : await reason(ctx, query, answer, preConsumed, pre);
226
- // Fuse only when the query has a genuine REMAINDER no mechanism's
227
- // structural evidence touched at all. `decided.accounted` alone
228
- // undercounts this: it is a COST-LADDER quantity (cover.ts prices its
229
- // masked/computed spans at near-zero and deliberately leaves them out of
230
- // `accounted` so PASS-bridged bytes are still charged), not a coverage
231
- // one — a query fully explained by one computed span plus bridged
232
- // connectors can report `accounted: []` while nothing is actually left
233
- // unexplained. The genuine remainder is what NEITHER the winning
234
- // candidate's accounted spans NOR any recognised extension's computed
235
- // span (`pre.computed` — every mechanism's parse() output, ALU included)
236
- // ever touched. A remainder under one river-fold quantum (W, the same
237
- // floor cover.ts's restatedSpan and the honesty-density bar above both
238
- // use) is bridging punctuation/whitespace, never a second topic —
239
- // observed: a single space between two fully-computed arithmetic spans
240
- // ("2+2 3+3") registered as "unaccounted" and pulled in an unrelated
241
- // corpus fact, corrupting "4 6" into "4 63".
242
- const explained = [
243
- ...decided.accounted,
244
- ...pre.computed.map((u) => [u.i, u.j]),
245
- ];
246
- const remainder = unaccounted(explained);
247
- // Whether the winning candidate's entire recognised substance is
248
- // COMPUTED — every accounted span exactly a pre.computed span, nothing
249
- // from a genuinely recognised/climbed site. fuseAttention's lone-root
250
- // shortcut assumes a single point of attention already IS primary's own
251
- // source; that assumption is exactly backwards for a pure computation
252
- // (an ALU result has no anchor of its own) — see fuseAttention's
253
- // `unclimbed` parameter, gated there by Attention.breadth so a
254
- // coincidental echo (which this flag alone cannot distinguish) is still
255
- // rejected.
256
- const unclimbed = decided.accounted.length > 0 &&
257
- decided.accounted.every(([i, j]) => pre.computed.some((u) => u.i === i && u.j === j));
258
- // Where the winning grounding stands in the query — fusion places primary
259
- // by it (see fuseAttention's `primarySpans`). `accounted` is the
260
- // cost-ladder read and is authoritative when non-empty; when it is empty
261
- // the grounding is a pure COMPUTATION, whose evidence is its computed span.
262
- // Exactly the cost-ladder-vs-coverage distinction `explained` above draws,
263
- // read here for POSITION instead of for coverage — and resolved here, where
264
- // both readings are in hand, rather than inside fuseAttention.
265
- const primarySpans = decided.accounted.length > 0
266
- ? decided.accounted
267
- : pre.computed.map((u) => [u.i, u.j]);
268
- const fused = remainder < ctx.space.maxGroup
269
- ? reasoned
270
- : meter
271
- ? await meter.time("fuse", () => fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans))
272
- : await fuseAttention(ctx, query, reasoned, pre, unclimbed, decided.accounted);
273
- done(fused, "grounded, reasoned forward, fused across points of attention");
274
- return { bytes: fused, provenance };
275
- }
@@ -1,66 +0,0 @@
1
- import { Vec } from "../vec.js";
2
- import { Sema } from "../sema.js";
3
- import type { Input, MindContext } from "./types.js";
4
- /** The content key of a byte span — one latin1 char per byte, an exact,
5
- * collision-free encoding. Spans on the perception path are query-scale
6
- * (windows, regions, candidate spans), so key construction is far cheaper
7
- * than the river fold it deduplicates. */
8
- export declare function latin1Key(bytes: Uint8Array): string;
9
- /** Perceive input into a content-defined tree (the river fold).
10
- * Deterministic — identical bytes always produce an identical tree.
11
- *
12
- * `boundaries` is an optional sorted list of proper byte offsets where the
13
- * fold must split so that each prefix segment folds identically to how it
14
- * folded when it was learned (§10.3 stable-prefix contract). Only the
15
- * CALLER — who assembled the multi-turn context — knows where those
16
- * boundaries are; the geometry never guesses them from the bytes. */
17
- export declare function perceive(ctx: MindContext, input: Input, leafAt?: (i: number) => number | null, lookup?: (ids: number[]) => number | null, boundaries?: readonly number[]): Sema;
18
- /** The DEPOSIT-shaped perceive. Folds over the stream's own content cuts —
19
- * bit-identical to what inference computes for the same bytes, and that
20
- * train/inference agreement is load-bearing for exact recall. An input that
21
- * EXTENDS a previously deposited one is a conversation context grown by one
22
- * turn; the cached prefix length IS the turn boundary (derived from the deposit
23
- * sequence itself, never from a content convention) and joins the cut set, so
24
- * the trained context node and the query's context subtree are the SAME node.
25
- * Segment folds reuse across deposits ({@link stablePrefixFoldIncremental}) —
26
- * O(turn) instead of O(context) per turn. All of it is purely a cache: an
27
- * evicted chain loses only the turn boundaries, and since the content cuts do
28
- * not depend on the cache, the segments themselves are unchanged. */
29
- export declare function perceiveDeposit(ctx: MindContext, bytes: Uint8Array, conversational?: boolean): Sema;
30
- /** The raw bytes of an input — modality-neutral conversion. */
31
- export declare function inputBytes(ctx: MindContext, input: Input): Uint8Array;
32
- /** Convenience: the gist vector of a byte span. */
33
- export declare function gistOf(ctx: MindContext, bytes: Uint8Array): Vec;
34
- /** Fold a perceived tree bottom-up against the store's content-addressed maps:
35
- * every leaf is named by findLeaf, every branch by findBranch over its kids'
36
- * ids (null the moment any child is unknown). `visit`, when given, sees each
37
- * node with its byte span and resolved id. Returns the node's byte end and
38
- * resolved id. */
39
- export declare function foldTree(ctx: MindContext, n: Sema, start: number, visit?: (n: Sema, start: number, end: number, node: number | null) => void): {
40
- end: number;
41
- node: number | null;
42
- };
43
- /** The canonical node id of a byte span: perceive it in isolation — the way
44
- * training did — and recover its root bottom-up. Returns null if any part is
45
- * unknown. */
46
- export declare function resolve(ctx: MindContext, bytes: Uint8Array): number | null;
47
- /** Equivalence-class resolution: when the exact content-addressed lookup
48
- * misses, find a stored node whose CANONICAL key equals the span's — the
49
- * store's canon index proposes candidates by key hash, and each is verified
50
- * by re-canonicalizing its bytes (hash-then-verify, like every content
51
- * lookup). Among verified candidates, one that leads somewhere (has a
52
- * continuation edge) is preferred; ties break to the lowest id — a corpus
53
- * property, not a seed property. Null when the response carries no
54
- * canonicalizer, the store has no canon index, or nothing verifies. */
55
- export declare function canonResolve(ctx: MindContext, bytes: Uint8Array): number | null;
56
- /** Walk a perceived tree in POST-ORDER with byte offsets — children before
57
- * their parent, `visit(node, start, end)` for every node including leaves.
58
- * Returns the byte end. The one shared traversal the offset-carrying tree
59
- * readers (recognition via foldTree's richer variant, attention's region
60
- * collection, resonance's branch counting) build on, so each does not
61
- * re-derive the offset bookkeeping. (recognition.segment keeps its own
62
- * walk: its flush semantics need PRE-order decisions at leaf-parents, which
63
- * a post-order visitor cannot express.) */
64
- export declare function walkTree(n: Sema, start: number, visit: (node: Sema, start: number, end: number) => void): number;
65
- /** Reconstruct a node's byte content from the DAG, up to `maxLen` bytes. */
66
- export declare function read(ctx: MindContext, id: number, maxLen?: number): Uint8Array;
@@ -1,306 +0,0 @@
1
- // primitives.ts — Address + Read primitives (Section 1 of the mind).
2
- //
3
- // Address — bytes → node (perceive, foldTree, resolve)
4
- // Read — node → bytes (read)
5
- import { bytesToTree, gridToTree, hilbertBytes, stablePrefixFoldIncremental, stackGrids, } from "../geometry.js";
6
- import { canonHash } from "../canon.js";
7
- import { bytesEqual } from "../bytes.js";
8
- import { ALL } from "./types.js";
9
- // ── Address: bytes → node ──────────────────────────────────────────────
10
- /** The content key of a byte span — one latin1 char per byte, an exact,
11
- * collision-free encoding. Spans on the perception path are query-scale
12
- * (windows, regions, candidate spans), so key construction is far cheaper
13
- * than the river fold it deduplicates. */
14
- export function latin1Key(bytes) {
15
- // Batched String.fromCharCode — avoids the O(n²) cost of repeated += on
16
- // potentially-large query spans, and stays well under the ~65536 arg limit.
17
- const n = bytes.length;
18
- let s = "";
19
- for (let i = 0; i < n; i += 4096) {
20
- s += String.fromCharCode(...bytes.subarray(i, Math.min(i + 4096, n)));
21
- }
22
- return s;
23
- }
24
- /** Perceive input into a content-defined tree (the river fold).
25
- * Deterministic — identical bytes always produce an identical tree.
26
- *
27
- * `boundaries` is an optional sorted list of proper byte offsets where the
28
- * fold must split so that each prefix segment folds identically to how it
29
- * folded when it was learned (§10.3 stable-prefix contract). Only the
30
- * CALLER — who assembled the multi-turn context — knows where those
31
- * boundaries are; the geometry never guesses them from the bytes. */
32
- export function perceive(ctx, input, leafAt, lookup, boundaries) {
33
- if (typeof input === "string" || input instanceof Uint8Array) {
34
- const bytes = typeof input === "string"
35
- ? new TextEncoder().encode(input)
36
- : input;
37
- if (leafAt === undefined && lookup === undefined) {
38
- // Per-response memo (see MindContext.perceiveMemo): only the plain
39
- // inference shape — raw bytes, no store capabilities — is memoised,
40
- // keyed by CONTENT so byte-identical spans in fresh arrays still hit.
41
- // The tree is shared by reference; Sema nodes are never mutated.
42
- const memo = ctx.perceiveMemo;
43
- if (memo) {
44
- const key = latin1Key(bytes);
45
- const hit = memo.get(key);
46
- if (hit !== undefined) {
47
- if (ctx.meter)
48
- ctx.meter.perceiveHits++;
49
- return hit;
50
- }
51
- if (ctx.meter) {
52
- ctx.meter.perceptions++;
53
- ctx.meter.perceivedBytes += bytes.length;
54
- }
55
- const tree = bytesToTree(ctx.space, ctx.alphabet, bytes, undefined, undefined, boundaries);
56
- memo.set(key, tree);
57
- return tree;
58
- }
59
- if (ctx.meter) {
60
- ctx.meter.perceptions++;
61
- ctx.meter.perceivedBytes += bytes.length;
62
- }
63
- return bytesToTree(ctx.space, ctx.alphabet, bytes, undefined, undefined, boundaries);
64
- }
65
- return bytesToTree(ctx.space, ctx.alphabet, bytes, leafAt, lookup);
66
- }
67
- if (Array.isArray(input)) {
68
- return gridToTree(ctx.space, ctx.alphabet, stackGrids(input));
69
- }
70
- return gridToTree(ctx.space, ctx.alphabet, input);
71
- }
72
- /** The DEPOSIT-shaped perceive. Folds over the stream's own content cuts —
73
- * bit-identical to what inference computes for the same bytes, and that
74
- * train/inference agreement is load-bearing for exact recall. An input that
75
- * EXTENDS a previously deposited one is a conversation context grown by one
76
- * turn; the cached prefix length IS the turn boundary (derived from the deposit
77
- * sequence itself, never from a content convention) and joins the cut set, so
78
- * the trained context node and the query's context subtree are the SAME node.
79
- * Segment folds reuse across deposits ({@link stablePrefixFoldIncremental}) —
80
- * O(turn) instead of O(context) per turn. All of it is purely a cache: an
81
- * evicted chain loses only the turn boundaries, and since the content cuts do
82
- * not depend on the cache, the segments themselves are unchanged. */
83
- export function perceiveDeposit(ctx, bytes, conversational = false) {
84
- let prev;
85
- let prefixLen = 0;
86
- // Cache consult (both boundary lookup and stable-prefix reuse) is scoped
87
- // to conversational deposits only — a bare, unrelated fact whose bytes
88
- // happen to extend an earlier deposit is NOT a conversation turn, and
89
- // must keep the plain fold so it shares structure with ITS OWN prior
90
- // deposits, not fragment against a coincidental byte-prefix.
91
- if (conversational) {
92
- // Longest cached PROPER prefix first.
93
- const lens = [...ctx._depositLens]
94
- .filter((L) => L >= 2 && L < bytes.length)
95
- .sort((a, b) => b - a);
96
- for (const L of lens) {
97
- const hit = ctx._depositTrees.get(latin1Key(bytes.subarray(0, L)));
98
- // The suffix must bytes-equal the hit's OWN recorded continuation —
99
- // proof this deposit is that turn's actual next turn, not a fact
100
- // that coincidentally shares its byte prefix.
101
- if (hit !== undefined && hit.nextBytes !== undefined &&
102
- bytesEqual(hit.nextBytes, bytes.subarray(L))) {
103
- prev = hit;
104
- prefixLen = L;
105
- break;
106
- }
107
- }
108
- }
109
- // ONLY turn boundaries belong here. The stream's own content cuts are NOT
110
- // passed in: `bytesToTree` and `stablePrefixFoldIncremental` both derive them
111
- // per span, at every level, and handing the level-0 cuts in as stable-prefix
112
- // boundaries instead produces a LEFT-NESTED join of flat segments — a
113
- // different tree from the one inference builds for the same bytes. When that
114
- // happened, a deposit's context root and `resolve(question)` were different
115
- // nodes, so the trained edge hung off a node inference never reached and
116
- // recall went silent (test/44 caught it as a site that could not be emitted
117
- // because the resolved node led nowhere). Train and infer must fold
118
- // identically; the way to guarantee that is to give this function nothing
119
- // extra to say.
120
- const cuts = new Set();
121
- if (prev !== undefined) {
122
- for (const b of prev.boundaries)
123
- cuts.add(b);
124
- cuts.add(prefixLen);
125
- }
126
- const boundaries = [...cuts].sort((a, b) => a - b);
127
- const folded = stablePrefixFoldIncremental(ctx.space, ctx.alphabet, bytes, boundaries, prev?.stable);
128
- const tree = folded.tree;
129
- const entry = { boundaries, stable: folded.fold };
130
- // Only a conversational deposit writes the cache too — otherwise a bare
131
- // fact's plain fold could later be misread as a conversation's turn-zero
132
- // boundary by an unrelated conversational deposit that happens to extend
133
- // its bytes.
134
- if (conversational && bytes.length >= 2) {
135
- // The lengths set drifts as the map evicts; past the probe budget the
136
- // drift itself becomes the cost (each stale length is an O(len) key
137
- // build), so both reset together — losing only warm-up on live chains.
138
- if (ctx._depositLens.size > 64) {
139
- ctx._depositLens.clear();
140
- ctx._depositTrees.clear();
141
- }
142
- ctx._depositTrees.set(latin1Key(bytes), entry);
143
- ctx._depositLens.add(bytes.length);
144
- }
145
- return tree;
146
- }
147
- /** The raw bytes of an input — modality-neutral conversion. */
148
- export function inputBytes(ctx, input) {
149
- if (typeof input === "string")
150
- return new TextEncoder().encode(input);
151
- if (input instanceof Uint8Array)
152
- return input;
153
- if (Array.isArray(input))
154
- return hilbertBytes(stackGrids(input));
155
- return hilbertBytes(input);
156
- }
157
- /** Convenience: the gist vector of a byte span. */
158
- export function gistOf(ctx, bytes) {
159
- return perceive(ctx, bytes).v;
160
- }
161
- /** Fold a perceived tree bottom-up against the store's content-addressed maps:
162
- * every leaf is named by findLeaf, every branch by findBranch over its kids'
163
- * ids (null the moment any child is unknown). `visit`, when given, sees each
164
- * node with its byte span and resolved id. Returns the node's byte end and
165
- * resolved id. */
166
- export function foldTree(ctx, n, start, visit) {
167
- // Fast path: subtree already resolved (from a previous conversation turn
168
- // or an earlier recognition pass). The pyramid reuses prefix subtrees as
169
- // identical Sema objects, so this cache turns foldTree into O(suffix)
170
- // instead of O(context) for multi-turn recognition.
171
- const cached = ctx._resolvedSubtrees?.get(n);
172
- if (cached !== undefined) {
173
- const end = start + cached.len;
174
- visit?.(n, start, end, cached.id);
175
- return { end, node: cached.id };
176
- }
177
- if (n.kids === null) {
178
- const b = n.leaf ?? new Uint8Array(0);
179
- const end = start + b.length;
180
- const node = ctx.store.findLeaf(b);
181
- visit?.(n, start, end, node);
182
- if (node !== null && ctx._resolvedSubtrees) {
183
- ctx._resolvedSubtrees.set(n, { id: node, len: b.length });
184
- }
185
- return { end, node };
186
- }
187
- let pos = start;
188
- let known = true;
189
- const kids = [];
190
- for (const k of n.kids) {
191
- const r = foldTree(ctx, k, pos, visit);
192
- if (r.node === null)
193
- known = false;
194
- else if (known)
195
- kids.push(r.node);
196
- pos = r.end;
197
- }
198
- const node = known ? ctx.store.findBranch(kids) : null;
199
- visit?.(n, start, pos, node);
200
- if (node !== null && ctx._resolvedSubtrees) {
201
- ctx._resolvedSubtrees.set(n, { id: node, len: pos - start });
202
- }
203
- return { end: pos, node };
204
- }
205
- /** The canonical node id of a byte span: perceive it in isolation — the way
206
- * training did — and recover its root bottom-up. Returns null if any part is
207
- * unknown. */
208
- export function resolve(ctx, bytes) {
209
- if (bytes.length === 0)
210
- return null;
211
- if (ctx.meter)
212
- ctx.meter.resolves++;
213
- const exact = foldTree(ctx, perceive(ctx, bytes), 0).node;
214
- if (exact !== null)
215
- return exact;
216
- return canonResolve(ctx, bytes);
217
- }
218
- /** Equivalence-class resolution: when the exact content-addressed lookup
219
- * misses, find a stored node whose CANONICAL key equals the span's — the
220
- * store's canon index proposes candidates by key hash, and each is verified
221
- * by re-canonicalizing its bytes (hash-then-verify, like every content
222
- * lookup). Among verified candidates, one that leads somewhere (has a
223
- * continuation edge) is preferred; ties break to the lowest id — a corpus
224
- * property, not a seed property. Null when the response carries no
225
- * canonicalizer, the store has no canon index, or nothing verifies. */
226
- export function canonResolve(ctx, bytes) {
227
- const canon = ctx.canon;
228
- const store = ctx.store;
229
- if (canon === null || !store.canonFind)
230
- return null;
231
- if (bytes.length < 2)
232
- return null;
233
- const memo = ctx.canonMemo;
234
- const memoKey = memo ? latin1Key(bytes) : "";
235
- if (memo) {
236
- const hit = memo.get(memoKey);
237
- if (hit !== undefined)
238
- return hit;
239
- }
240
- const set = (v) => {
241
- memo?.set(memoKey, v);
242
- return v;
243
- };
244
- const key = canon(bytes);
245
- if (key.length === 0)
246
- return set(null);
247
- // A stored form that IS canonical is not in the index (buildCanonIndex
248
- // skips identity rows) — the exact content-addressed lookup of the
249
- // canonical bytes finds it directly.
250
- if (key.length !== bytes.length || !bytesEqual(key, bytes)) {
251
- const direct = foldTree(ctx, perceive(ctx, key), 0).node;
252
- if (direct !== null)
253
- return set(direct);
254
- }
255
- if (ctx.meter)
256
- ctx.meter.canonLookups++;
257
- const candidates = store.canonFind(canonHash(key));
258
- if (candidates.length === 0)
259
- return set(null);
260
- let best = null;
261
- let bestLeads = false;
262
- for (const id of candidates) {
263
- const bytesOf = read(ctx, id);
264
- const stored = canon(bytesOf);
265
- if (stored.length !== key.length || !bytesEqual(stored, key))
266
- continue;
267
- // The index stores FLAT content twins; the id the exact path would have
268
- // resolved for these bytes is their FOLD — the deposit-shaped node that
269
- // carries the edges and halos. Re-folding the candidate's bytes lands
270
- // on exactly the node the canonical-case query would have found.
271
- const folded = foldTree(ctx, perceive(ctx, bytesOf), 0).node;
272
- const use = folded ?? id;
273
- const leads = store.hasNext(use) || store.haloMass(use) > 0;
274
- if (best === null || (leads && !bestLeads) ||
275
- (leads === bestLeads && use < best)) {
276
- best = use;
277
- bestLeads = leads;
278
- }
279
- }
280
- return set(best);
281
- }
282
- /** Walk a perceived tree in POST-ORDER with byte offsets — children before
283
- * their parent, `visit(node, start, end)` for every node including leaves.
284
- * Returns the byte end. The one shared traversal the offset-carrying tree
285
- * readers (recognition via foldTree's richer variant, attention's region
286
- * collection, resonance's branch counting) build on, so each does not
287
- * re-derive the offset bookkeeping. (recognition.segment keeps its own
288
- * walk: its flush semantics need PRE-order decisions at leaf-parents, which
289
- * a post-order visitor cannot express.) */
290
- export function walkTree(n, start, visit) {
291
- if (n.kids === null) {
292
- const end = start + (n.leaf?.length ?? 0);
293
- visit(n, start, end);
294
- return end;
295
- }
296
- let pos = start;
297
- for (const k of n.kids)
298
- pos = walkTree(k, pos, visit);
299
- visit(n, start, pos);
300
- return pos;
301
- }
302
- // ── Read: node → bytes ──────────────────────────────────────────────────
303
- /** Reconstruct a node's byte content from the DAG, up to `maxLen` bytes. */
304
- export function read(ctx, id, maxLen = ALL) {
305
- return ctx.store.bytesPrefix(id, maxLen);
306
- }