@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,139 +0,0 @@
1
- import type { Vec } from "../vec.js";
2
- /** One element of a step's input or output vector.
3
- *
4
- * Modality-neutral and deliberately partial: an element might be a byte span of
5
- * the query, a resolved graph node, a resonance hit with its score, a spliced
6
- * connector — so every descriptive field is optional and a mechanism fills only
7
- * the ones that carry meaning for what it did. `text` is always present (the
8
- * human-readable rendering); the rest is provenance a debugger can lean on. */
9
- export interface RationaleItem {
10
- /** Human-readable rendering — decoded text for a byte span, else a label like
11
- * "‹none›" or an operator name. Always set, so a step always reads. */
12
- text: string;
13
- /** The graph node this element is, or resolved to, when known — the handle to
14
- * point back at the exact stored fact in the content-addressed DAG. */
15
- node?: number;
16
- /** The `[start, end)` span this element occupies in its step's frame of
17
- * reference (usually the query or the answer being composed). */
18
- span?: [number, number];
19
- /** The resonance / cosine score that selected this element, when it was chosen
20
- * by similarity rather than by exact structure. */
21
- score?: number;
22
- /** A short role tag — "query", "leaf", "form", "hit", "connector", "answer",
23
- * … — naming what KIND of element this is within the step. */
24
- role?: string;
25
- /** The gist vector, only when the element fundamentally IS a vector and a
26
- * caller asked to carry it (off by default — a D-float array per item would
27
- * bury the reasoning it is meant to explain). */
28
- v?: Vec;
29
- }
30
- /** A single completed act of inference — one mechanism, run once.
31
- *
32
- * Steps are emitted in COMPLETION order (a sub-mechanism finishes, and is
33
- * reported, before the mechanism that called it), while `index` is assigned in
34
- * ENTRY order (a parent reserves its index before its children run). So a
35
- * parent's `index` is always lower than its children's, and the two orderings
36
- * together give a valid topological reading of the dependency graph. */
37
- export interface RationaleStep {
38
- /** This step's index, assigned when the mechanism was ENTERED — a strict,
39
- * incremental ordering over the whole inference. */
40
- index: number;
41
- /** The mechanism and its enclosing mechanisms, outermost → innermost, e.g.
42
- * `["respond", "think", "recognise"]`. The last entry is this step; the
43
- * prefix is the nest of sub-mechanisms it ran inside. */
44
- mechanism: string[];
45
- /** The enclosing mechanism's step index, or -1 at the root — the NESTING edge
46
- * of the dependency graph (which step this one is a part of). */
47
- parent: number;
48
- /** The earlier steps whose OUTPUTS became this step's inputs — the DATA-FLOW
49
- * edges of the dependency graph. Defaults to the previous sibling (the step
50
- * run just before this one inside the same mechanism), or the parent when
51
- * this is the first sub-step; a mechanism that fuses several earlier results
52
- * names them all explicitly. */
53
- dependsOn: number[];
54
- /** The vector of elements handed to the mechanism (one or more). */
55
- inputs: RationaleItem[];
56
- /** The vector of elements the mechanism produced (one or more) — longer than
57
- * `inputs` when it decomposed, shorter when it combined. */
58
- outputs: RationaleItem[];
59
- /** A one-line, human account of what the mechanism did and why — the sentence
60
- * that turns the data into an explanation. */
61
- note?: string;
62
- /** Optional structured payload — a mechanism-specific, plain-serialisable
63
- * shape (no Map/Set/vectors/mutable internals) that carries more than the
64
- * human-readable `note` can, for a debugger or downstream tool to consume
65
- * programmatically. Never read by inference; purely additive. */
66
- data?: unknown;
67
- }
68
- /** The callback {@link Mind.respond} / {@link Mind.respondText} accept. It is
69
- * invoked once per completed step, AS the inference unfolds — never batched at
70
- * the end — so a caller can stream the reasoning live or accumulate it. */
71
- export type InspectRationale = (step: RationaleStep) => void;
72
- /** Decode bytes to text for display, dropping the NUL padding the encoder uses
73
- * (the same cleanup {@link Mind.respondText} does for its result). */
74
- export declare function decodeText(bytes: Uint8Array): string;
75
- /** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
76
- * the same union-of-spans reading think's grounding decider prices at PASS
77
- * per byte, exposed here so a mechanism can turn it into a human label. */
78
- export declare function unexplainedSpans(queryLen: number, accounted: ReadonlyArray<[number, number]>): Array<[number, number]>;
79
- /** A human-readable label for the query bytes a mechanism's `accounted`
80
- * spans leave unexplained — purely diagnostic (Task 2's negative evidence):
81
- * it never changes a candidate's weight, only what the rationale trace
82
- * says the mechanism left on the table. `""` when nothing is unexplained. */
83
- export declare function unexplainedLabel(query: Uint8Array, accounted: ReadonlyArray<[number, number]>): string;
84
- /** An open mechanism — the handle {@link Rationale.enter} returns. Hold it for
85
- * the duration of the mechanism and call {@link Scope.done} with the outputs
86
- * when it finishes; that emits the step and pops the nesting. */
87
- export interface Scope {
88
- /** The step index reserved for this mechanism at entry — pass it as an
89
- * explicit dependency of a later step that consumes this one's output. */
90
- readonly index: number;
91
- /** Close the mechanism: emit its step with these outputs and pop it off the
92
- * nesting stack. Idempotent — a second call is ignored, so a `finally` that
93
- * closes after an early return is safe. */
94
- done(outputs: RationaleItem[], note?: string, data?: unknown): void;
95
- }
96
- /** The live tracer: a stack of open mechanisms over one {@link Mind.respond}.
97
- *
98
- * Sema's inference is single-threaded and strictly sequential — every async
99
- * step is awaited before the next begins, and `respond` holds no two thoughts
100
- * at once — so a plain stack exactly tracks the current nesting: {@link enter}
101
- * pushes, {@link Scope.done} pops, and {@link step} (a mechanism with no
102
- * sub-steps) is the two fused. The tracer never branches the control flow; it
103
- * only records it. */
104
- export declare class Rationale {
105
- private readonly sink;
106
- private next;
107
- /** Open mechanisms, outermost first. Each frame remembers the last child it
108
- * has spawned so the next sibling can default its data-flow edge to it. */
109
- private readonly stack;
110
- /** The most recent step index emitted under each mechanism name — the handle
111
- * a later step uses to name an EARLIER mechanism as its data-flow producer
112
- * (e.g. cover depends on the latest recognise / computeExtensions). One tracer is
113
- * built per response and inference is sequential, so "most recent" is exactly
114
- * "the one that produced the inputs I am about to consume". */
115
- private readonly lastByName;
116
- constructor(sink: InspectRationale);
117
- /** The index of the most recent step with this mechanism name, or undefined if
118
- * none has run. Used to wire an explicit producer edge into {@link
119
- * Scope.done} / {@link step}'s `deps`. */
120
- lastIndex(name: string): number | undefined;
121
- /** The mechanism names currently open, outermost → innermost. */
122
- private path;
123
- /** The default data-flow edge for a step entering now: the previous sibling
124
- * inside the current mechanism, else the enclosing mechanism, else nothing
125
- * (the root). An explicit `deps` overrides this. */
126
- private defaultDeps;
127
- /** Reserve this step's index and register it as the current mechanism's most
128
- * recent child (so the NEXT sibling chains to it) and as the most recent step
129
- * of its own NAME (so a later mechanism can name it as a producer). */
130
- private reserve;
131
- private emit;
132
- /** Enter a mechanism that has sub-steps. Captures its inputs and the nesting
133
- * now; the matching {@link Scope.done} supplies the outputs when it finishes.
134
- * `deps` overrides the default data-flow edge (previous sibling / parent). */
135
- enter(name: string, inputs: RationaleItem[], deps?: number[]): Scope;
136
- /** Record a mechanism that has no sub-steps — its inputs and outputs are both
137
- * known at the call site. Returns its index, for a later step to depend on. */
138
- step(name: string, inputs: RationaleItem[], outputs: RationaleItem[], note?: string, deps?: number[], data?: unknown): number;
139
- }
@@ -1,163 +0,0 @@
1
- // rationale.ts — the inference, told as it happens.
2
- //
3
- // Sema's edge over a weight matrix is that every answer is a DERIVATION over
4
- // explicit facts, not a sample from an opaque distribution. This module turns
5
- // that derivation into a stream a human (or a debugger) can read: as {@link
6
- // Mind.respond} thinks, each inference MECHANISM it runs emits a {@link
7
- // RationaleStep} the moment it completes — what it was handed, what it produced,
8
- // where it sits in the nesting of mechanisms, and which earlier steps fed it.
9
- //
10
- // Nothing here drives the inference; it only WITNESSES it. When no
11
- // `inspectRationale` callback is supplied the tracer is never constructed and
12
- // the cost is exactly zero — every emit site in src/mind/mind.ts is guarded by `?.`, and
13
- // optional-chaining short-circuits its arguments, so the items are not even
14
- // built (see {@link Mind.respond}).
15
- //
16
- // The shape of a step mirrors how Sema reasons. A mechanism is rarely a 1→1
17
- // map: {@link Mind.recognise} DECOMPOSES one query into many recognised forms;
18
- // the cover COMBINES many forms back into one answer; resonance fans one gist
19
- // out into a ranked list of hits. So a step's `inputs` and `outputs` are each a
20
- // VECTOR — an ordered list of {@link RationaleItem}s, one per element — and the
21
- // fan-out / fan-in is visible in their lengths.
22
- /** Decode bytes to text for display, dropping the NUL padding the encoder uses
23
- * (the same cleanup {@link Mind.respondText} does for its result). */
24
- export function decodeText(bytes) {
25
- return new TextDecoder().decode(bytes.filter((b) => b !== 0x00));
26
- }
27
- /** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
28
- * the same union-of-spans reading think's grounding decider prices at PASS
29
- * per byte, exposed here so a mechanism can turn it into a human label. */
30
- export function unexplainedSpans(queryLen, accounted) {
31
- const sorted = accounted
32
- .map(([s, e]) => [Math.max(0, s), Math.min(queryLen, e)])
33
- .filter(([s, e]) => e > s)
34
- .sort((a, b) => a[0] - b[0]);
35
- const gaps = [];
36
- let reach = 0;
37
- for (const [s, e] of sorted) {
38
- if (s > reach)
39
- gaps.push([reach, s]);
40
- if (e > reach)
41
- reach = e;
42
- }
43
- if (reach < queryLen)
44
- gaps.push([reach, queryLen]);
45
- return gaps;
46
- }
47
- /** A human-readable label for the query bytes a mechanism's `accounted`
48
- * spans leave unexplained — purely diagnostic (Task 2's negative evidence):
49
- * it never changes a candidate's weight, only what the rationale trace
50
- * says the mechanism left on the table. `""` when nothing is unexplained. */
51
- export function unexplainedLabel(query, accounted) {
52
- const gaps = unexplainedSpans(query.length, accounted);
53
- if (gaps.length === 0)
54
- return "";
55
- return gaps.map(([s, e]) => decodeText(query.subarray(s, e))).join(" … ");
56
- }
57
- /** The live tracer: a stack of open mechanisms over one {@link Mind.respond}.
58
- *
59
- * Sema's inference is single-threaded and strictly sequential — every async
60
- * step is awaited before the next begins, and `respond` holds no two thoughts
61
- * at once — so a plain stack exactly tracks the current nesting: {@link enter}
62
- * pushes, {@link Scope.done} pops, and {@link step} (a mechanism with no
63
- * sub-steps) is the two fused. The tracer never branches the control flow; it
64
- * only records it. */
65
- export class Rationale {
66
- sink;
67
- next = 0;
68
- /** Open mechanisms, outermost first. Each frame remembers the last child it
69
- * has spawned so the next sibling can default its data-flow edge to it. */
70
- stack = [];
71
- /** The most recent step index emitted under each mechanism name — the handle
72
- * a later step uses to name an EARLIER mechanism as its data-flow producer
73
- * (e.g. cover depends on the latest recognise / computeExtensions). One tracer is
74
- * built per response and inference is sequential, so "most recent" is exactly
75
- * "the one that produced the inputs I am about to consume". */
76
- lastByName = new Map();
77
- constructor(sink) {
78
- this.sink = sink;
79
- }
80
- /** The index of the most recent step with this mechanism name, or undefined if
81
- * none has run. Used to wire an explicit producer edge into {@link
82
- * Scope.done} / {@link step}'s `deps`. */
83
- lastIndex(name) {
84
- return this.lastByName.get(name);
85
- }
86
- /** The mechanism names currently open, outermost → innermost. */
87
- path(leaf) {
88
- const p = this.stack.map((f) => f.name);
89
- p.push(leaf);
90
- return p;
91
- }
92
- /** The default data-flow edge for a step entering now: the previous sibling
93
- * inside the current mechanism, else the enclosing mechanism, else nothing
94
- * (the root). An explicit `deps` overrides this. */
95
- defaultDeps() {
96
- const top = this.stack[this.stack.length - 1];
97
- if (!top)
98
- return [];
99
- return [top.lastChild ?? top.index];
100
- }
101
- /** Reserve this step's index and register it as the current mechanism's most
102
- * recent child (so the NEXT sibling chains to it) and as the most recent step
103
- * of its own NAME (so a later mechanism can name it as a producer). */
104
- reserve(name) {
105
- const index = this.next++;
106
- const top = this.stack[this.stack.length - 1];
107
- if (top)
108
- top.lastChild = index;
109
- this.lastByName.set(name, index);
110
- return index;
111
- }
112
- emit(index, mechanism, inputs, outputs, deps, note, data) {
113
- this.sink({
114
- index,
115
- mechanism,
116
- parent: this.stack.length > 0
117
- ? this.stack[this.stack.length - 1].index
118
- : -1,
119
- dependsOn: deps ?? this.defaultDeps(),
120
- inputs,
121
- outputs,
122
- note,
123
- data,
124
- });
125
- }
126
- /** Enter a mechanism that has sub-steps. Captures its inputs and the nesting
127
- * now; the matching {@link Scope.done} supplies the outputs when it finishes.
128
- * `deps` overrides the default data-flow edge (previous sibling / parent). */
129
- enter(name, inputs, deps) {
130
- const mechanism = this.path(name);
131
- const resolvedDeps = deps ?? this.defaultDeps();
132
- const index = this.reserve(name);
133
- this.stack.push({ index, name, lastChild: null });
134
- let closed = false;
135
- const emit = this.emit.bind(this);
136
- const pop = () => {
137
- // Pop down to and including this frame — tolerant of a sub-mechanism that
138
- // forgot to close, so one missed `done` cannot desync the whole stack.
139
- const at = this.stack.findIndex((f) => f.index === index);
140
- if (at >= 0)
141
- this.stack.length = at;
142
- };
143
- return {
144
- index,
145
- done: (outputs, note, data) => {
146
- if (closed)
147
- return;
148
- closed = true;
149
- pop();
150
- emit(index, mechanism, inputs, outputs, resolvedDeps, note, data);
151
- },
152
- };
153
- }
154
- /** Record a mechanism that has no sub-steps — its inputs and outputs are both
155
- * known at the call site. Returns its index, for a later step to depend on. */
156
- step(name, inputs, outputs, note, deps, data) {
157
- const mechanism = this.path(name);
158
- const resolvedDeps = deps ?? this.defaultDeps();
159
- const index = this.reserve(name);
160
- this.emit(index, mechanism, inputs, outputs, resolvedDeps, note, data);
161
- return index;
162
- }
163
- }
@@ -1,40 +0,0 @@
1
- import type { MindContext } from "./types.js";
2
- import type { Precomputed } from "./pipeline-mechanism.js";
3
- /** Whether `bytes` is a proper byte-subspan of `query` — already present in
4
- * the question, so voicing it back only restates part of what was asked,
5
- * never answers it. The exact guard recallByResonance already applies to
6
- * its OWN grounding candidates (tier 1's `restates`, tier 2's subspan
7
- * check, tier 0b's argument-binding subspan check) — every mechanism that
8
- * walks a LEARNT CONTINUATION EDGE past an already-vetted grounding
9
- * (reason()'s own hops below, and CAST's `projectCounterfactual` seat
10
- * substitution — see cast.ts) needs the same guard applied to what the
11
- * walk turns up, since `follow()`/`chooseNext`/`pivotInto` know nothing of
12
- * the query at all — only of what structurally continues what. */
13
- export declare function restatesQuery(query: Uint8Array, bytes: Uint8Array): boolean;
14
- /** Extend a grounded answer forward across facts (multi-hop reasoning).
15
- * Pivots on the longest unconsumed learnt context each answer contains,
16
- * then follows the pivot's continuation to the next fact. Repeats up
17
- * to `cfg.recallQueryK` hops. `preConsumed` carries node ids already
18
- * spoken for by the grounding stage (cover/extract/CAST). `pre` is the
19
- * response's shared pre-computation — the post-grounding stages read the
20
- * same container the mechanisms did. */
21
- export declare function reason(ctx: MindContext, query: Uint8Array, answer: Uint8Array, preConsumed: ReadonlySet<number>, pre: Precomputed): Promise<Uint8Array>;
22
- /** Fuse independent points of attention into one answer (multi-topic).
23
- * When the consensus climb finds more than one dominant point, each
24
- * independent point grounds its own answer; they are bridged together
25
- * by any learnt connector the graph holds between them. */
26
- export declare function fuseAttention(ctx: MindContext, query: Uint8Array, primary: Uint8Array, pre: Precomputed,
27
- /** True when `primary` never touched the consensus climb at all — e.g. a
28
- * pure ALU computation, which has no anchor of its own. commitVotes
29
- * ALWAYS admits the dominant root regardless of its vote (attention.ts:
30
- * "roots.length === 0 || …") on the assumption a lone root already IS
31
- * primary's own source; that assumption is exactly backwards when
32
- * primary is unclimbed. Absent or false preserves the original
33
- * behaviour exactly. */
34
- unclimbed?: boolean,
35
- /** The query spans `primary`'s own grounding stands on — used ONLY to place
36
- * primary in the fused reading order (see below). Resolved by the caller,
37
- * which is the layer that knows how a given grounding records its evidence;
38
- * fuseAttention just reads a position from it. Empty or absent preserves
39
- * the original behaviour exactly. */
40
- primarySpans?: ReadonlyArray<readonly [number, number]>): Promise<Uint8Array>;
@@ -1,272 +0,0 @@
1
- // reasoning.ts — multi-hop reasoning + multi-topic fusion (Section 4 of the mind).
2
- //
3
- // reason — extend an answer forward across facts (multi-hop)
4
- // fuseAttention — fuse independent points of attention (multi-topic)
5
- import { rItem, rNode } from "./trace.js";
6
- import { bytesEqual, indexOf } from "../bytes.js";
7
- import { resolve } from "./primitives.js";
8
- import { hubBound } from "./traverse.js";
9
- import { follow, haloSiblings, project } from "./match.js";
10
- import { joinWithBridge, pivotInto } from "./resonance.js";
11
- /** Whether `bytes` is a proper byte-subspan of `query` — already present in
12
- * the question, so voicing it back only restates part of what was asked,
13
- * never answers it. The exact guard recallByResonance already applies to
14
- * its OWN grounding candidates (tier 1's `restates`, tier 2's subspan
15
- * check, tier 0b's argument-binding subspan check) — every mechanism that
16
- * walks a LEARNT CONTINUATION EDGE past an already-vetted grounding
17
- * (reason()'s own hops below, and CAST's `projectCounterfactual` seat
18
- * substitution — see cast.ts) needs the same guard applied to what the
19
- * walk turns up, since `follow()`/`chooseNext`/`pivotInto` know nothing of
20
- * the query at all — only of what structurally continues what. */
21
- export function restatesQuery(query, bytes) {
22
- return bytes.length < query.length && indexOf(query, bytes, 0) >= 0;
23
- }
24
- /** Extend a grounded answer forward across facts (multi-hop reasoning).
25
- * Pivots on the longest unconsumed learnt context each answer contains,
26
- * then follows the pivot's continuation to the next fact. Repeats up
27
- * to `cfg.recallQueryK` hops. `preConsumed` carries node ids already
28
- * spoken for by the grounding stage (cover/extract/CAST). `pre` is the
29
- * response's shared pre-computation — the post-grounding stages read the
30
- * same container the mechanisms did. */
31
- export async function reason(ctx, query, answer, preConsumed, pre) {
32
- // Echo guard: a query that is ITSELF a learnt continuation (some context's
33
- // answer) is being asked back at the system — hopping forward from it would
34
- // chain through the very fact that produced it and echo the conversation
35
- // back. The grounded answer alone is the honest read-out. Deliberately a
36
- // broad structural gate; pinned by test/31-audit.
37
- const qId = pre.queryResolved;
38
- if (qId !== null && ctx.store.prevCount(qId) > 0)
39
- return answer;
40
- const consumed = new Set();
41
- // Consume a node and its neighbours for pivot-cycle prevention — CAPPED at
42
- // the hub bound, via the store's LIMITed edge reads: a common continuation's
43
- // reverse fan-in (and a hub context's forward fan-out) is corpus-sized, and
44
- // no per-hop operation may grow with the corpus. The cap follows the one
45
- // convention every fan-out decision uses (first √N in the relation's own
46
- // read order); a pivot suppressed only by a beyond-cap neighbour may now
47
- // fire — the same visibility trade chooseNext documents.
48
- const bound = hubBound(ctx);
49
- const consumeNode = (id) => {
50
- if (id === null)
51
- return;
52
- consumed.add(id);
53
- for (const p of ctx.store.prevFirst(id, bound))
54
- consumed.add(p);
55
- };
56
- const consumeAll = (id) => {
57
- if (id === null)
58
- return;
59
- consumeNode(id);
60
- for (const n of ctx.store.nextFirst(id, bound))
61
- consumed.add(n);
62
- };
63
- // Pre-consume whatever the grounding stage already spoke for. The halo
64
- // sweep is one ANN query per node — cap it at haloQueryK sweeps (cover
65
- // grounding can pre-consume one node per recognised site, O(query length));
66
- // nodes past the cap are still consumed directly, they just skip the
67
- // synonym expansion.
68
- let haloSweeps = 0;
69
- for (const id of preConsumed) {
70
- consumeNode(id);
71
- if (haloSweeps >= ctx.cfg.haloQueryK)
72
- continue;
73
- const h = ctx.store.halo(id);
74
- if (!h)
75
- continue;
76
- haloSweeps++;
77
- for (const sib of await haloSiblings(ctx, id, h))
78
- consumeNode(sib.id);
79
- }
80
- let cur = answer;
81
- const qv = pre.guide; // the response-wide guide IS the query's gist
82
- let t;
83
- const startedFrom = answer;
84
- for (let hop = 0; hop < ctx.cfg.recallQueryK; hop++) {
85
- const curId = resolve(ctx, cur);
86
- consumeNode(curId);
87
- // Forward-absorb: follow only UNCONSUMED continuations. The gate below
88
- // checks an unconsumed edge EXISTS, but follow()'s chooseNext knows
89
- // nothing of `consumed` and may still walk to a consumed fixpoint —
90
- // absorbing it would repeat content the grounding stage already spoke
91
- // for, so a consumed fixpoint falls through to the pivot step instead.
92
- if (curId !== null &&
93
- ctx.store.nextFirst(curId, bound).some((n) => !consumed.has(n))) {
94
- const fwd = await follow(ctx, curId, qv);
95
- const fwdId = fwd !== null ? resolve(ctx, fwd) : null;
96
- if (fwd !== null && !bytesEqual(fwd, cur) &&
97
- (fwdId === null || !consumed.has(fwdId)) &&
98
- !restatesQuery(query, fwd)) {
99
- consumeAll(curId);
100
- t ??= ctx.trace?.enter("reason", [
101
- rItem(startedFrom, "grounded"),
102
- ]);
103
- ctx.trace?.step("absorbForward", [rItem(cur, "answer", curId)], [rItem(fwd, "answer", resolve(ctx, fwd) ?? undefined)], "the answer is itself a learnt fact — follow its continuation to the fixpoint");
104
- cur = fwd;
105
- continue;
106
- }
107
- }
108
- // Pivot: find the longest unconsumed learnt context the answer contains.
109
- consumeAll(curId);
110
- const pivot = await pivotInto(ctx, cur, consumed);
111
- if (pivot === null)
112
- break;
113
- const fc = await follow(ctx, pivot, qv);
114
- consumeAll(pivot);
115
- if (fc === null || bytesEqual(fc, cur) || restatesQuery(query, fc))
116
- break;
117
- t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
118
- ctx.trace?.step("pivotStep", [rItem(cur, "answer"), rNode(ctx, pivot, "pivot")], [rItem(fc, "answer", resolve(ctx, fc) ?? undefined)], "pivot on the shared span this answer contains, then step forward across that fact");
119
- cur = fc;
120
- }
121
- t?.done([rItem(cur, "answer", resolve(ctx, cur) ?? undefined)], "the multi-hop chain's fixpoint");
122
- return cur;
123
- }
124
- /** Fuse independent points of attention into one answer (multi-topic).
125
- * When the consensus climb finds more than one dominant point, each
126
- * independent point grounds its own answer; they are bridged together
127
- * by any learnt connector the graph holds between them. */
128
- export async function fuseAttention(ctx, query, primary, pre,
129
- /** True when `primary` never touched the consensus climb at all — e.g. a
130
- * pure ALU computation, which has no anchor of its own. commitVotes
131
- * ALWAYS admits the dominant root regardless of its vote (attention.ts:
132
- * "roots.length === 0 || …") on the assumption a lone root already IS
133
- * primary's own source; that assumption is exactly backwards when
134
- * primary is unclimbed. Absent or false preserves the original
135
- * behaviour exactly. */
136
- unclimbed = false,
137
- /** The query spans `primary`'s own grounding stands on — used ONLY to place
138
- * primary in the fused reading order (see below). Resolved by the caller,
139
- * which is the layer that knows how a given grounding records its evidence;
140
- * fuseAttention just reads a position from it. Empty or absent preserves
141
- * the original behaviour exactly. */
142
- primarySpans = []) {
143
- // When the answer is structurally drawn from the query itself
144
- // (extraction), it already spans all the query's pieces — fusion
145
- // would only add noise from unrelated stored contexts. The gate is
146
- // STRICT containment (resolved node in the query's tree, or a contiguous
147
- // byte run): the old sparse-subsequence test was trivially satisfied by
148
- // short answers over long queries, silently starving multi-topic queries
149
- // of fusion.
150
- if (containsSpan(ctx, query, primary))
151
- return primary;
152
- // The committed points of attention ARE the shared climb's roots (same
153
- // query, same k, same DF mode) — read them from Precomputed instead of
154
- // re-climbing, so even a traced response pays for the climb once.
155
- const forest = (await pre.attention()).roots;
156
- // A LONE root is ordinarily primary's own source — nothing to fuse. But
157
- // when primary is unclimbed, the lone root was never checked against
158
- // anything: it is admitted by commitVotes unconditionally, so it may be
159
- // genuine consensus (Attention.breadth dominates — most of the query's
160
- // OWN regions corroborate it) or a coincidental echo (breadth does not
161
- // dominate — see test/35-attention-confidence). breadth is the SCALE-
162
- // INVARIANT read of exactly this question: the raw IDF vote cannot serve
163
- // here, since it is an absolute ln(N)-scaled quantity (a genuine root on
164
- // a large store can score BELOW its own floor while a coincidental echo
165
- // on a small one scores comfortably above its own, smaller, floor).
166
- const lonePromotes = unclimbed && forest.length === 1 &&
167
- forest[0].breadth > 0.5;
168
- if (forest.length === 0 || (forest.length <= 1 && !lonePromotes)) {
169
- return primary;
170
- }
171
- // WHERE THE QUERY ASKED FOR IT. The sort below orders the fused pieces by
172
- // query position, which is the whole point of the `start` field: a
173
- // multi-topic answer should read in the order the question posed its
174
- // topics. Every ROOT carries its own start. `primary` did not — it was
175
- // given forest[0].start, the FIRST attention root's position, which is
176
- // primary's own source only when primary happens to come from that root.
177
- // When it does not, primary is sorted to a position it never occupied.
178
- //
179
- // Observed live: "What is the capital of France? And what is 2 + 2?"
180
- // answered "4The capital city of France is Paris." — the ALU result, whose
181
- // evidence is the "2 + 2" span near the END of the query, inherited the
182
- // France root's start of 0 and sorted ahead of the France answer. Both
183
- // pieces were right; only the order was.
184
- //
185
- // primary's own position is the earliest query byte its grounding stands
186
- // on. `accounted` is the cost-ladder read of that and is authoritative
187
- // when non-empty; when it is empty the grounding is a pure COMPUTATION,
188
- // whose evidence is its computed span — the same cost-ladder-vs-coverage
189
- // distinction think() already draws for the fusion remainder ("`accounted`
190
- // alone undercounts this ... cover prices its computed spans at near-zero
191
- // and deliberately leaves them out"), read here for position instead of
192
- // for coverage. With neither, nothing is known and the old behaviour
193
- // (forest[0].start) stands.
194
- const primaryStart = primarySpans.length > 0
195
- ? primarySpans.reduce((m, [s]) => Math.min(m, s), Infinity)
196
- : forest[0].start;
197
- const pieces = [
198
- { start: primaryStart, bytes: primary },
199
- ];
200
- const qv = pre.guide; // once, not per root
201
- const rest = lonePromotes ? forest : forest.slice(1);
202
- const t = ctx.trace?.enter("fuseAttention", [
203
- rItem(primary, "primary"),
204
- ...rest.map((r) => rNode(ctx, r.anchor, "point", r.vote)),
205
- ]);
206
- for (const root of rest) {
207
- // DISPERSION: this root's contributing regions are confined to a
208
- // single cluster (see Attention.clusters) — one local neighbourhood of
209
- // the query, not several separate places. Raw region count already
210
- // failed to discriminate a coincidental match from a genuine further
211
- // topic (test/24 gap 3.1 vs test/35's echo); dispersion is a different
212
- // question — not how MUCH evidence, but how many separate PLACES in the
213
- // query corroborate it — and a coincidental match is structurally
214
- // confined to one cluster no matter how strongly it resonates.
215
- //
216
- // EXCEPTION: crossRegionVotes' own joint conclusions (a query naming
217
- // two attributes that were only ever learnt TOGETHER — test/34's own
218
- // binding corpus) are inherently ONE fused context and are pooled from
219
- // a single synthetic region, so they always read as one cluster even
220
- // though they already, by construction, account for both original
221
- // mentions. `breadth` (dominates — the same > half-the-query bar used
222
- // everywhere else) still correctly recognises these: a genuine joint
223
- // binding explains the MAJORITY of the query's regions on its own,
224
- // which a coincidental echo never does (verified: test/35's echo tops
225
- // out at 0.40). So a root is trusted when EITHER measure alone
226
- // indicates real signal — excluded only when BOTH are weak. Cheap and
227
- // synchronous — checked before the async already-answered walk below.
228
- if (root.clusters < 2 && root.breadth <= 0.5) {
229
- ctx.trace?.step("singleCluster", [rNode(ctx, root.anchor, "point", root.vote)], [], "this point's evidence is confined to one local neighbourhood of the query — not trusted as an independent topic");
230
- continue;
231
- }
232
- // ALREADY ANSWERED: this root's own learnt continuation — the same
233
- // content-addressed walk reason()'s echo guard already trusts
234
- // (`ctx.store.prevCount(qId) > 0`), here applied per-candidate instead
235
- // of to the whole query — is VERBATIM present later in the query. A
236
- // query that embeds both an exchange's ask and its own already-given
237
- // reply (a conversation's turn plus its own prior answer, concatenated
238
- // raw by addTurn — or any caller pasting a transcript into one
239
- // respond() call; the check is Mind-bookkeeping-free, so it treats both
240
- // identically) has already spoken this root's answer — fusing it in
241
- // again would only restate it. Deliberately NOT a magnitude measure:
242
- // it fires on exact content-addressed recurrence, not on how strongly
243
- // the root resonates.
244
- const cont = await follow(ctx, root.anchor, qv);
245
- if (cont !== null && cont.length > 0 && indexOf(query, cont, root.end) >= 0) {
246
- ctx.trace?.step("alreadyAnswered", [rNode(ctx, root.anchor, "point", root.vote)], [rItem(cont, "continuation")], "this point's own learnt continuation already appears later in the query — already answered, not fused");
247
- continue;
248
- }
249
- const g = await project(ctx, root.anchor, qv);
250
- if (g === null || g.length === 0)
251
- continue;
252
- if (pieces.some((p) => indexOf(p.bytes, g, 0) >= 0))
253
- continue;
254
- pieces.push({ start: root.start, bytes: g });
255
- }
256
- if (pieces.length === 1) {
257
- t?.done([rItem(primary, "answer")], "no further independent point grounded");
258
- return primary;
259
- }
260
- pieces.sort((a, b) => a.start - b.start);
261
- let out = pieces[0].bytes;
262
- for (let i = 1; i < pieces.length; i++) {
263
- // An approximate-resonance miss (or a genuinely unlearnt junction) joins
264
- // the pieces bare — joinWithBridge surfaces it as a bridgeMiss step.
265
- out = await joinWithBridge(ctx, out, pieces[i].bytes);
266
- }
267
- t?.done([rItem(out, "answer", resolve(ctx, out) ?? undefined)], `fused ${pieces.length} independent points of attention into one answer`);
268
- return out;
269
- }
270
- // (resonance.js is already a static dependency above — `bridge` — so the old
271
- // dynamic import of pivotInto guarded against a cycle that does not exist.)
272
- import { containsSpan } from "./match.js";
@@ -1,20 +0,0 @@
1
- import type { MindContext, Recognition, Segment } from "./types.js";
2
- /** Decompose a byte stream into every stored form that leads somewhere
3
- * (has a continuation edge or a halo). Two complementary readings:
4
- *
5
- * • structural — walk the query's own perceived tree, naming each subtree
6
- * by findLeaf at the leaves and findBranch above. Catches every form
7
- * aligned to the query's segmentation.
8
- *
9
- * • canonical — re-derive the store's segmentation directly: at each byte,
10
- * the longest known leaf, chained into flat branches. Names forms the
11
- * query's own cut cannot, and records sub-leaf boundaries as `splits`.
12
- *
13
- * Both O(n · maxGroup) bounded O(1) probes — never a scan of the corpus. */
14
- export declare function recognise(ctx: MindContext, bytes: Uint8Array): Recognition;
15
- /** Segment bytes using the geometry's own groupings — leaf-parent
16
- * nodes from the perceived tree, with consecutive bare leaves merged
17
- * into one segment. Each segment's gist is perceived from its bytes
18
- * IN ISOLATION, so the same content has the same gist regardless of
19
- * where it appears. */
20
- export declare function segment(ctx: MindContext, bytes: Uint8Array): Segment[];