@hviana/sema 0.1.3 → 0.1.5
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.
- package/dist/example/demo.js +25 -25
- package/dist/example/train_base.d.ts +21 -19
- package/dist/example/train_base.js +1952 -1684
- package/dist/src/alphabet.d.ts +3 -3
- package/dist/src/alphabet.js +27 -24
- package/dist/src/alu/src/alu.d.ts +194 -169
- package/dist/src/alu/src/alu.js +414 -374
- package/dist/src/alu/src/expr.d.ts +43 -36
- package/dist/src/alu/src/expr.js +278 -252
- package/dist/src/alu/src/index.d.ts +98 -9
- package/dist/src/alu/src/index.js +63 -9
- package/dist/src/alu/src/kernel-arith.d.ts +5 -1
- package/dist/src/alu/src/kernel-arith.js +285 -195
- package/dist/src/alu/src/kernel-bits.d.ts +5 -1
- package/dist/src/alu/src/kernel-bits.js +115 -81
- package/dist/src/alu/src/kernel-logic.js +63 -33
- package/dist/src/alu/src/kernel-nd.js +195 -155
- package/dist/src/alu/src/kernel-numeric.d.ts +80 -16
- package/dist/src/alu/src/kernel-numeric.js +331 -259
- package/dist/src/alu/src/operation.d.ts +132 -112
- package/dist/src/alu/src/operation.js +163 -149
- package/dist/src/alu/src/parser.d.ts +175 -168
- package/dist/src/alu/src/parser.js +421 -382
- package/dist/src/alu/src/resonance.d.ts +25 -16
- package/dist/src/alu/src/resonance.js +56 -49
- package/dist/src/alu/src/text.d.ts +7 -3
- package/dist/src/alu/src/text.js +37 -31
- package/dist/src/alu/src/value.d.ts +14 -14
- package/dist/src/alu/src/value.js +160 -150
- package/dist/src/alu/test/alu.test.js +656 -543
- package/dist/src/bytes.d.ts +5 -1
- package/dist/src/bytes.js +40 -33
- package/dist/src/config.d.ts +102 -102
- package/dist/src/config.js +83 -83
- package/dist/src/derive/src/deduction.d.ts +62 -60
- package/dist/src/derive/src/deduction.js +109 -103
- package/dist/src/derive/src/index.d.ts +7 -1
- package/dist/src/derive/src/priority-queue.d.ts +8 -8
- package/dist/src/derive/src/priority-queue.js +61 -57
- package/dist/src/derive/src/rewrite.d.ts +18 -15
- package/dist/src/derive/src/rewrite.js +67 -59
- package/dist/src/derive/src/trie.d.ts +56 -56
- package/dist/src/derive/src/trie.js +179 -175
- package/dist/src/derive/test/derive.test.js +100 -96
- package/dist/src/extension.d.ts +16 -13
- package/dist/src/geometry.d.ts +42 -17
- package/dist/src/geometry.js +266 -235
- package/dist/src/index.d.ts +17 -2
- package/dist/src/index.js +7 -1
- package/dist/src/ingest-cache.d.ts +30 -25
- package/dist/src/ingest-cache.js +127 -108
- package/dist/src/mind/articulation.d.ts +5 -1
- package/dist/src/mind/articulation.js +112 -77
- package/dist/src/mind/attention.d.ts +108 -40
- package/dist/src/mind/attention.js +871 -749
- package/dist/src/mind/canonical.d.ts +19 -4
- package/dist/src/mind/canonical.js +31 -27
- package/dist/src/mind/graph-search.d.ts +225 -201
- package/dist/src/mind/graph-search.js +821 -742
- package/dist/src/mind/index.d.ts +10 -2
- package/dist/src/mind/junction.d.ts +58 -31
- package/dist/src/mind/junction.js +237 -172
- package/dist/src/mind/learning.d.ts +52 -16
- package/dist/src/mind/learning.js +165 -143
- package/dist/src/mind/match.d.ts +69 -20
- package/dist/src/mind/match.js +318 -259
- package/dist/src/mind/mechanisms/alu.js +16 -16
- package/dist/src/mind/mechanisms/cast.d.ts +13 -9
- package/dist/src/mind/mechanisms/cast.js +456 -363
- package/dist/src/mind/mechanisms/confluence.d.ts +12 -8
- package/dist/src/mind/mechanisms/confluence.js +183 -152
- package/dist/src/mind/mechanisms/cover.d.ts +8 -2
- package/dist/src/mind/mechanisms/cover.js +210 -148
- package/dist/src/mind/mechanisms/extraction.d.ts +34 -8
- package/dist/src/mind/mechanisms/extraction.js +288 -234
- package/dist/src/mind/mechanisms/recall.d.ts +10 -6
- package/dist/src/mind/mechanisms/recall.js +185 -126
- package/dist/src/mind/mind.d.ts +154 -129
- package/dist/src/mind/mind.js +288 -258
- package/dist/src/mind/pipeline-mechanism.d.ts +124 -112
- package/dist/src/mind/pipeline-mechanism.js +172 -161
- package/dist/src/mind/pipeline.d.ts +14 -4
- package/dist/src/mind/pipeline.js +189 -125
- package/dist/src/mind/primitives.d.ts +32 -8
- package/dist/src/mind/primitives.js +117 -99
- package/dist/src/mind/rationale.d.ts +98 -86
- package/dist/src/mind/rationale.js +121 -113
- package/dist/src/mind/reasoning.d.ts +13 -2
- package/dist/src/mind/reasoning.js +166 -129
- package/dist/src/mind/recognition.d.ts +4 -1
- package/dist/src/mind/recognition.js +208 -183
- package/dist/src/mind/resonance.d.ts +22 -5
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/trace.d.ts +25 -6
- package/dist/src/mind/trace.js +58 -50
- package/dist/src/mind/traverse.d.ts +58 -16
- package/dist/src/mind/traverse.js +357 -304
- package/dist/src/mind/types.d.ts +127 -120
- package/dist/src/mind/types.js +69 -60
- package/dist/src/rabitq-hnsw/src/database.d.ts +179 -177
- package/dist/src/rabitq-hnsw/src/database.js +359 -342
- package/dist/src/rabitq-hnsw/src/heap.d.ts +12 -12
- package/dist/src/rabitq-hnsw/src/heap.js +80 -75
- package/dist/src/rabitq-hnsw/src/hnsw.d.ts +103 -103
- package/dist/src/rabitq-hnsw/src/hnsw.js +454 -428
- package/dist/src/rabitq-hnsw/src/index.d.ts +6 -1
- package/dist/src/rabitq-hnsw/src/prng.d.ts +10 -10
- package/dist/src/rabitq-hnsw/src/prng.js +27 -25
- package/dist/src/rabitq-hnsw/src/rabitq.d.ts +69 -69
- package/dist/src/rabitq-hnsw/src/rabitq.js +262 -246
- package/dist/src/rabitq-hnsw/src/store.d.ts +132 -132
- package/dist/src/rabitq-hnsw/src/store.js +843 -752
- package/dist/src/rabitq-hnsw/test/hnsw.test.js +1054 -805
- package/dist/src/sema.d.ts +35 -0
- package/dist/src/sema.js +77 -0
- package/dist/src/store-sqlite.d.ts +166 -144
- package/dist/src/store-sqlite.js +660 -578
- package/dist/src/store.d.ts +630 -593
- package/dist/src/store.js +1581 -1440
- package/dist/src/vec.d.ts +9 -5
- package/dist/src/vec.js +73 -61
- package/package.json +2 -2
- package/src/sema.ts +102 -0
|
@@ -22,37 +22,41 @@
|
|
|
22
22
|
/** Decode bytes to text for display, dropping the NUL padding the encoder uses
|
|
23
23
|
* (the same cleanup {@link Mind.respondText} does for its result). */
|
|
24
24
|
export function decodeText(bytes) {
|
|
25
|
-
|
|
25
|
+
return new TextDecoder().decode(bytes.filter((b) => b !== 0x00));
|
|
26
26
|
}
|
|
27
27
|
/** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
|
|
28
28
|
* the same union-of-spans reading think's grounding decider prices at PASS
|
|
29
29
|
* per byte, exposed here so a mechanism can turn it into a human label. */
|
|
30
30
|
export function unexplainedSpans(queryLen, accounted) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (e > reach)
|
|
41
|
-
reach = e;
|
|
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]);
|
|
42
40
|
}
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
if (e > reach) {
|
|
42
|
+
reach = e;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (reach < queryLen) {
|
|
46
|
+
gaps.push([reach, queryLen]);
|
|
47
|
+
}
|
|
48
|
+
return gaps;
|
|
46
49
|
}
|
|
47
50
|
/** A human-readable label for the query bytes a mechanism's `accounted`
|
|
48
51
|
* spans leave unexplained — purely diagnostic (Task 2's negative evidence):
|
|
49
52
|
* it never changes a candidate's weight, only what the rationale trace
|
|
50
53
|
* says the mechanism left on the table. `""` when nothing is unexplained. */
|
|
51
54
|
export function unexplainedLabel(query, accounted) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
const gaps = unexplainedSpans(query.length, accounted);
|
|
56
|
+
if (gaps.length === 0) {
|
|
57
|
+
return "";
|
|
58
|
+
}
|
|
59
|
+
return gaps.map(([s, e]) => decodeText(query.subarray(s, e))).join(" … ");
|
|
56
60
|
}
|
|
57
61
|
/** The live tracer: a stack of open mechanisms over one {@link Mind.respond}.
|
|
58
62
|
*
|
|
@@ -63,100 +67,104 @@ export function unexplainedLabel(query, accounted) {
|
|
|
63
67
|
* sub-steps) is the two fused. The tracer never branches the control flow; it
|
|
64
68
|
* only records it. */
|
|
65
69
|
export class Rationale {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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) {
|
|
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
|
-
});
|
|
124
|
-
}
|
|
125
|
-
/** Enter a mechanism that has sub-steps. Captures its inputs and the nesting
|
|
126
|
-
* now; the matching {@link Scope.done} supplies the outputs when it finishes.
|
|
127
|
-
* `deps` overrides the default data-flow edge (previous sibling / parent). */
|
|
128
|
-
enter(name, inputs, deps) {
|
|
129
|
-
const mechanism = this.path(name);
|
|
130
|
-
const resolvedDeps = deps ?? this.defaultDeps();
|
|
131
|
-
const index = this.reserve(name);
|
|
132
|
-
this.stack.push({ index, name, lastChild: null });
|
|
133
|
-
let closed = false;
|
|
134
|
-
const emit = this.emit.bind(this);
|
|
135
|
-
const pop = () => {
|
|
136
|
-
// Pop down to and including this frame — tolerant of a sub-mechanism that
|
|
137
|
-
// forgot to close, so one missed `done` cannot desync the whole stack.
|
|
138
|
-
const at = this.stack.findIndex((f) => f.index === index);
|
|
139
|
-
if (at >= 0)
|
|
140
|
-
this.stack.length = at;
|
|
141
|
-
};
|
|
142
|
-
return {
|
|
143
|
-
index,
|
|
144
|
-
done: (outputs, note) => {
|
|
145
|
-
if (closed)
|
|
146
|
-
return;
|
|
147
|
-
closed = true;
|
|
148
|
-
pop();
|
|
149
|
-
emit(index, mechanism, inputs, outputs, resolvedDeps, note);
|
|
150
|
-
},
|
|
151
|
-
};
|
|
70
|
+
sink;
|
|
71
|
+
next = 0;
|
|
72
|
+
/** Open mechanisms, outermost first. Each frame remembers the last child it
|
|
73
|
+
* has spawned so the next sibling can default its data-flow edge to it. */
|
|
74
|
+
stack = [];
|
|
75
|
+
/** The most recent step index emitted under each mechanism name — the handle
|
|
76
|
+
* a later step uses to name an EARLIER mechanism as its data-flow producer
|
|
77
|
+
* (e.g. cover depends on the latest recognise / computeExtensions). One tracer is
|
|
78
|
+
* built per response and inference is sequential, so "most recent" is exactly
|
|
79
|
+
* "the one that produced the inputs I am about to consume". */
|
|
80
|
+
lastByName = new Map();
|
|
81
|
+
constructor(sink) {
|
|
82
|
+
this.sink = sink;
|
|
83
|
+
}
|
|
84
|
+
/** The index of the most recent step with this mechanism name, or undefined if
|
|
85
|
+
* none has run. Used to wire an explicit producer edge into {@link
|
|
86
|
+
* Scope.done} / {@link step}'s `deps`. */
|
|
87
|
+
lastIndex(name) {
|
|
88
|
+
return this.lastByName.get(name);
|
|
89
|
+
}
|
|
90
|
+
/** The mechanism names currently open, outermost → innermost. */
|
|
91
|
+
path(leaf) {
|
|
92
|
+
const p = this.stack.map((f) => f.name);
|
|
93
|
+
p.push(leaf);
|
|
94
|
+
return p;
|
|
95
|
+
}
|
|
96
|
+
/** The default data-flow edge for a step entering now: the previous sibling
|
|
97
|
+
* inside the current mechanism, else the enclosing mechanism, else nothing
|
|
98
|
+
* (the root). An explicit `deps` overrides this. */
|
|
99
|
+
defaultDeps() {
|
|
100
|
+
const top = this.stack[this.stack.length - 1];
|
|
101
|
+
if (!top) {
|
|
102
|
+
return [];
|
|
152
103
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
104
|
+
return [top.lastChild ?? top.index];
|
|
105
|
+
}
|
|
106
|
+
/** Reserve this step's index and register it as the current mechanism's most
|
|
107
|
+
* recent child (so the NEXT sibling chains to it) and as the most recent step
|
|
108
|
+
* of its own NAME (so a later mechanism can name it as a producer). */
|
|
109
|
+
reserve(name) {
|
|
110
|
+
const index = this.next++;
|
|
111
|
+
const top = this.stack[this.stack.length - 1];
|
|
112
|
+
if (top) {
|
|
113
|
+
top.lastChild = index;
|
|
161
114
|
}
|
|
115
|
+
this.lastByName.set(name, index);
|
|
116
|
+
return index;
|
|
117
|
+
}
|
|
118
|
+
emit(index, mechanism, inputs, outputs, deps, note) {
|
|
119
|
+
this.sink({
|
|
120
|
+
index,
|
|
121
|
+
mechanism,
|
|
122
|
+
parent: this.stack.length > 0
|
|
123
|
+
? this.stack[this.stack.length - 1].index
|
|
124
|
+
: -1,
|
|
125
|
+
dependsOn: deps ?? this.defaultDeps(),
|
|
126
|
+
inputs,
|
|
127
|
+
outputs,
|
|
128
|
+
note,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/** Enter a mechanism that has sub-steps. Captures its inputs and the nesting
|
|
132
|
+
* now; the matching {@link Scope.done} supplies the outputs when it finishes.
|
|
133
|
+
* `deps` overrides the default data-flow edge (previous sibling / parent). */
|
|
134
|
+
enter(name, inputs, deps) {
|
|
135
|
+
const mechanism = this.path(name);
|
|
136
|
+
const resolvedDeps = deps ?? this.defaultDeps();
|
|
137
|
+
const index = this.reserve(name);
|
|
138
|
+
this.stack.push({ index, name, lastChild: null });
|
|
139
|
+
let closed = false;
|
|
140
|
+
const emit = this.emit.bind(this);
|
|
141
|
+
const pop = () => {
|
|
142
|
+
// Pop down to and including this frame — tolerant of a sub-mechanism that
|
|
143
|
+
// forgot to close, so one missed `done` cannot desync the whole stack.
|
|
144
|
+
const at = this.stack.findIndex((f) => f.index === index);
|
|
145
|
+
if (at >= 0) {
|
|
146
|
+
this.stack.length = at;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
return {
|
|
150
|
+
index,
|
|
151
|
+
done: (outputs, note) => {
|
|
152
|
+
if (closed) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
closed = true;
|
|
156
|
+
pop();
|
|
157
|
+
emit(index, mechanism, inputs, outputs, resolvedDeps, note);
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** Record a mechanism that has no sub-steps — its inputs and outputs are both
|
|
162
|
+
* known at the call site. Returns its index, for a later step to depend on. */
|
|
163
|
+
step(name, inputs, outputs, note, deps) {
|
|
164
|
+
const mechanism = this.path(name);
|
|
165
|
+
const resolvedDeps = deps ?? this.defaultDeps();
|
|
166
|
+
const index = this.reserve(name);
|
|
167
|
+
this.emit(index, mechanism, inputs, outputs, resolvedDeps, note);
|
|
168
|
+
return index;
|
|
169
|
+
}
|
|
162
170
|
}
|
|
@@ -7,9 +7,20 @@ import type { Precomputed } from "./pipeline-mechanism.js";
|
|
|
7
7
|
* spoken for by the grounding stage (cover/extract/CAST). `pre` is the
|
|
8
8
|
* response's shared pre-computation — the post-grounding stages read the
|
|
9
9
|
* same container the mechanisms did. */
|
|
10
|
-
export declare function reason(
|
|
10
|
+
export declare function reason(
|
|
11
|
+
ctx: MindContext,
|
|
12
|
+
query: Uint8Array,
|
|
13
|
+
answer: Uint8Array,
|
|
14
|
+
preConsumed: ReadonlySet<number>,
|
|
15
|
+
pre: Precomputed,
|
|
16
|
+
): Promise<Uint8Array>;
|
|
11
17
|
/** Fuse independent points of attention into one answer (multi-topic).
|
|
12
18
|
* When the consensus climb finds more than one dominant point, each
|
|
13
19
|
* independent point grounds its own answer; they are bridged together
|
|
14
20
|
* by any learnt connector the graph holds between them. */
|
|
15
|
-
export declare function fuseAttention(
|
|
21
|
+
export declare function fuseAttention(
|
|
22
|
+
ctx: MindContext,
|
|
23
|
+
query: Uint8Array,
|
|
24
|
+
primary: Uint8Array,
|
|
25
|
+
pre: Precomputed,
|
|
26
|
+
): Promise<Uint8Array>;
|
|
@@ -16,146 +16,183 @@ import { joinWithBridge, pivotInto } from "./resonance.js";
|
|
|
16
16
|
* response's shared pre-computation — the post-grounding stages read the
|
|
17
17
|
* same container the mechanisms did. */
|
|
18
18
|
export async function reason(ctx, query, answer, preConsumed, pre) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
for (const p of ctx.store.prevFirst(id, hubBound))
|
|
41
|
-
consumed.add(p);
|
|
42
|
-
};
|
|
43
|
-
const consumeAll = (id) => {
|
|
44
|
-
if (id === null)
|
|
45
|
-
return;
|
|
46
|
-
consumeNode(id);
|
|
47
|
-
for (const n of ctx.store.nextFirst(id, hubBound))
|
|
48
|
-
consumed.add(n);
|
|
49
|
-
};
|
|
50
|
-
// Pre-consume whatever the grounding stage already spoke for. The halo
|
|
51
|
-
// sweep is one ANN query per node — cap it at haloQueryK sweeps (cover
|
|
52
|
-
// grounding can pre-consume one node per recognised site, O(query length));
|
|
53
|
-
// nodes past the cap are still consumed directly, they just skip the
|
|
54
|
-
// synonym expansion.
|
|
55
|
-
let haloSweeps = 0;
|
|
56
|
-
for (const id of preConsumed) {
|
|
57
|
-
consumeNode(id);
|
|
58
|
-
if (haloSweeps >= ctx.cfg.haloQueryK)
|
|
59
|
-
continue;
|
|
60
|
-
const h = ctx.store.halo(id);
|
|
61
|
-
if (!h)
|
|
62
|
-
continue;
|
|
63
|
-
haloSweeps++;
|
|
64
|
-
for (const sib of await haloSiblings(ctx, id, h))
|
|
65
|
-
consumeNode(sib.id);
|
|
19
|
+
// Echo guard: a query that is ITSELF a learnt continuation (some context's
|
|
20
|
+
// answer) is being asked back at the system — hopping forward from it would
|
|
21
|
+
// chain through the very fact that produced it and echo the conversation
|
|
22
|
+
// back. The grounded answer alone is the honest read-out. Deliberately a
|
|
23
|
+
// broad structural gate; pinned by test/31-audit.
|
|
24
|
+
const qId = pre.queryResolved;
|
|
25
|
+
if (qId !== null && ctx.store.prevCount(qId) > 0) {
|
|
26
|
+
return answer;
|
|
27
|
+
}
|
|
28
|
+
const consumed = new Set();
|
|
29
|
+
// Consume a node and its neighbours for pivot-cycle prevention — CAPPED at
|
|
30
|
+
// the hub bound, via the store's LIMITed edge reads: a common continuation's
|
|
31
|
+
// reverse fan-in (and a hub context's forward fan-out) is corpus-sized, and
|
|
32
|
+
// no per-hop operation may grow with the corpus. The cap follows the one
|
|
33
|
+
// convention every fan-out decision uses (first √N in the relation's own
|
|
34
|
+
// read order); a pivot suppressed only by a beyond-cap neighbour may now
|
|
35
|
+
// fire — the same visibility trade chooseNext documents.
|
|
36
|
+
const hubBound = Math.ceil(Math.sqrt(corpusN(ctx)));
|
|
37
|
+
const consumeNode = (id) => {
|
|
38
|
+
if (id === null) {
|
|
39
|
+
return;
|
|
66
40
|
}
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
41
|
+
consumed.add(id);
|
|
42
|
+
for (const p of ctx.store.prevFirst(id, hubBound)) {
|
|
43
|
+
consumed.add(p);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const consumeAll = (id) => {
|
|
47
|
+
if (id === null) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
consumeNode(id);
|
|
51
|
+
for (const n of ctx.store.nextFirst(id, hubBound)) {
|
|
52
|
+
consumed.add(n);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
// Pre-consume whatever the grounding stage already spoke for. The halo
|
|
56
|
+
// sweep is one ANN query per node — cap it at haloQueryK sweeps (cover
|
|
57
|
+
// grounding can pre-consume one node per recognised site, O(query length));
|
|
58
|
+
// nodes past the cap are still consumed directly, they just skip the
|
|
59
|
+
// synonym expansion.
|
|
60
|
+
let haloSweeps = 0;
|
|
61
|
+
for (const id of preConsumed) {
|
|
62
|
+
consumeNode(id);
|
|
63
|
+
if (haloSweeps >= ctx.cfg.haloQueryK) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const h = ctx.store.halo(id);
|
|
67
|
+
if (!h) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
haloSweeps++;
|
|
71
|
+
for (const sib of await haloSiblings(ctx, id, h)) {
|
|
72
|
+
consumeNode(sib.id);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
let cur = answer;
|
|
76
|
+
const qv = pre.guide; // the response-wide guide IS the query's gist
|
|
77
|
+
let t;
|
|
78
|
+
const startedFrom = answer;
|
|
79
|
+
for (let hop = 0; hop < ctx.cfg.recallQueryK; hop++) {
|
|
80
|
+
const curId = resolve(ctx, cur);
|
|
81
|
+
consumeNode(curId);
|
|
82
|
+
// Forward-absorb: follow only UNCONSUMED continuations. The gate below
|
|
83
|
+
// checks an unconsumed edge EXISTS, but follow()'s chooseNext knows
|
|
84
|
+
// nothing of `consumed` and may still walk to a consumed fixpoint —
|
|
85
|
+
// absorbing it would repeat content the grounding stage already spoke
|
|
86
|
+
// for, so a consumed fixpoint falls through to the pivot step instead.
|
|
87
|
+
if (
|
|
88
|
+
curId !== null &&
|
|
89
|
+
ctx.store.nextFirst(curId, hubBound).some((n) => !consumed.has(n))
|
|
90
|
+
) {
|
|
91
|
+
const fwd = await follow(ctx, curId, qv);
|
|
92
|
+
const fwdId = fwd !== null ? resolve(ctx, fwd) : null;
|
|
93
|
+
if (
|
|
94
|
+
fwd !== null && !bytesEqual(fwd, cur) &&
|
|
95
|
+
(fwdId === null || !consumed.has(fwdId))
|
|
96
|
+
) {
|
|
95
97
|
consumeAll(curId);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
cur =
|
|
98
|
+
t ??= ctx.trace?.enter("reason", [
|
|
99
|
+
rItem(startedFrom, "grounded"),
|
|
100
|
+
]);
|
|
101
|
+
ctx.trace?.step(
|
|
102
|
+
"absorbForward",
|
|
103
|
+
[rItem(cur, "answer", curId)],
|
|
104
|
+
[rItem(fwd, "answer", resolve(ctx, fwd) ?? undefined)],
|
|
105
|
+
"the answer is itself a learnt fact — follow its continuation to the fixpoint",
|
|
106
|
+
);
|
|
107
|
+
cur = fwd;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Pivot: find the longest unconsumed learnt context the answer contains.
|
|
112
|
+
consumeAll(curId);
|
|
113
|
+
const pivot = await pivotInto(ctx, cur, consumed);
|
|
114
|
+
if (pivot === null) {
|
|
115
|
+
break;
|
|
106
116
|
}
|
|
107
|
-
|
|
108
|
-
|
|
117
|
+
const fc = await follow(ctx, pivot, qv);
|
|
118
|
+
consumeAll(pivot);
|
|
119
|
+
if (fc === null || bytesEqual(fc, cur)) {
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
|
|
123
|
+
ctx.trace?.step(
|
|
124
|
+
"pivotStep",
|
|
125
|
+
[rItem(cur, "answer"), rNode(ctx, pivot, "pivot")],
|
|
126
|
+
[rItem(fc, "answer", resolve(ctx, fc) ?? undefined)],
|
|
127
|
+
"pivot on the shared span this answer contains, then step forward across that fact",
|
|
128
|
+
);
|
|
129
|
+
cur = fc;
|
|
130
|
+
}
|
|
131
|
+
t?.done(
|
|
132
|
+
[rItem(cur, "answer", resolve(ctx, cur) ?? undefined)],
|
|
133
|
+
"the multi-hop chain's fixpoint",
|
|
134
|
+
);
|
|
135
|
+
return cur;
|
|
109
136
|
}
|
|
110
137
|
/** Fuse independent points of attention into one answer (multi-topic).
|
|
111
138
|
* When the consensus climb finds more than one dominant point, each
|
|
112
139
|
* independent point grounds its own answer; they are bridged together
|
|
113
140
|
* by any learnt connector the graph holds between them. */
|
|
114
141
|
export async function fuseAttention(ctx, query, primary, pre) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
pieces.push({ start: root.start, bytes: g });
|
|
145
|
-
}
|
|
146
|
-
if (pieces.length === 1) {
|
|
147
|
-
t?.done([rItem(primary, "answer")], "no further independent point grounded");
|
|
148
|
-
return primary;
|
|
142
|
+
// When the answer is structurally drawn from the query itself
|
|
143
|
+
// (extraction), it already spans all the query's pieces — fusion
|
|
144
|
+
// would only add noise from unrelated stored contexts. The gate is
|
|
145
|
+
// STRICT containment (resolved node in the query's tree, or a contiguous
|
|
146
|
+
// byte run): the old sparse-subsequence test was trivially satisfied by
|
|
147
|
+
// short answers over long queries, silently starving multi-topic queries
|
|
148
|
+
// of fusion.
|
|
149
|
+
if (containsSpan(ctx, query, primary)) {
|
|
150
|
+
return primary;
|
|
151
|
+
}
|
|
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
|
+
if (forest.length <= 1) {
|
|
157
|
+
return primary;
|
|
158
|
+
}
|
|
159
|
+
const pieces = [
|
|
160
|
+
{ start: forest[0].start, bytes: primary },
|
|
161
|
+
];
|
|
162
|
+
const qv = pre.guide; // once, not per root
|
|
163
|
+
const t = ctx.trace?.enter("fuseAttention", [
|
|
164
|
+
rItem(primary, "primary"),
|
|
165
|
+
...forest.slice(1).map((r) => rNode(ctx, r.anchor, "point", r.vote)),
|
|
166
|
+
]);
|
|
167
|
+
for (const root of forest.slice(1)) {
|
|
168
|
+
const g = await project(ctx, root.anchor, qv);
|
|
169
|
+
if (g === null || g.length === 0) {
|
|
170
|
+
continue;
|
|
149
171
|
}
|
|
150
|
-
pieces.
|
|
151
|
-
|
|
152
|
-
for (let i = 1; i < pieces.length; i++) {
|
|
153
|
-
// An approximate-resonance miss (or a genuinely unlearnt junction) joins
|
|
154
|
-
// the pieces bare — joinWithBridge surfaces it as a bridgeMiss step.
|
|
155
|
-
out = await joinWithBridge(ctx, out, pieces[i].bytes);
|
|
172
|
+
if (pieces.some((p) => indexOf(p.bytes, g, 0) >= 0)) {
|
|
173
|
+
continue;
|
|
156
174
|
}
|
|
157
|
-
|
|
158
|
-
|
|
175
|
+
pieces.push({ start: root.start, bytes: g });
|
|
176
|
+
}
|
|
177
|
+
if (pieces.length === 1) {
|
|
178
|
+
t?.done(
|
|
179
|
+
[rItem(primary, "answer")],
|
|
180
|
+
"no further independent point grounded",
|
|
181
|
+
);
|
|
182
|
+
return primary;
|
|
183
|
+
}
|
|
184
|
+
pieces.sort((a, b) => a.start - b.start);
|
|
185
|
+
let out = pieces[0].bytes;
|
|
186
|
+
for (let i = 1; i < pieces.length; i++) {
|
|
187
|
+
// An approximate-resonance miss (or a genuinely unlearnt junction) joins
|
|
188
|
+
// the pieces bare — joinWithBridge surfaces it as a bridgeMiss step.
|
|
189
|
+
out = await joinWithBridge(ctx, out, pieces[i].bytes);
|
|
190
|
+
}
|
|
191
|
+
t?.done(
|
|
192
|
+
[rItem(out, "answer", resolve(ctx, out) ?? undefined)],
|
|
193
|
+
`fused ${pieces.length} independent points of attention into one answer`,
|
|
194
|
+
);
|
|
195
|
+
return out;
|
|
159
196
|
}
|
|
160
197
|
// (resonance.js is already a static dependency above — `bridge` — so the old
|
|
161
198
|
// dynamic import of pivotInto guarded against a cycle that does not exist.)
|
|
@@ -11,7 +11,10 @@ import type { MindContext, Recognition, Segment } from "./types.js";
|
|
|
11
11
|
* query's own cut cannot, and records sub-leaf boundaries as `splits`.
|
|
12
12
|
*
|
|
13
13
|
* Both O(n · maxGroup) bounded O(1) probes — never a scan of the corpus. */
|
|
14
|
-
export declare function recognise(
|
|
14
|
+
export declare function recognise(
|
|
15
|
+
ctx: MindContext,
|
|
16
|
+
bytes: Uint8Array,
|
|
17
|
+
): Recognition;
|
|
15
18
|
/** Segment bytes using the geometry's own groupings — leaf-parent
|
|
16
19
|
* nodes from the perceived tree, with consecutive bare leaves merged
|
|
17
20
|
* into one segment. Each segment's gist is perceived from its bytes
|