@hviana/sema 0.5.2 → 0.5.4
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/AGENTS.md +115 -53
- package/HOW_IT_WORKS.md +291 -184
- package/jsr.json +6 -0
- package/package.json +1 -1
- package/src/mind/bridge.ts +10 -104
- package/src/mind/match.ts +497 -7
- package/src/mind/{prefix-completion.ts → mechanisms/prefix-completion.ts} +66 -92
- package/src/mind/mechanisms/recall.ts +38 -129
- package/src/mind/mechanisms/reference.ts +343 -0
- package/src/mind/mind.ts +12 -8
- package/src/mind/pipeline-mechanism.ts +120 -3
- package/src/mind/pipeline.ts +16 -2
- package/src/mind/traverse.ts +92 -1
- package/src/store.ts +13 -4
- package/test/33-multi-candidate.test.mjs +21 -11
- package/test/70-prefix-completion.test.mjs +1 -1
- package/test/72-prefix-candidate-supply.test.mjs +7 -9
- package/test/74-prefix-trap-not-sprung-early.test.mjs +1 -1
- package/test/76-reference-binding.test.mjs +543 -0
- package/dist/example/demo.d.ts +0 -1
- package/dist/example/demo.js +0 -39
- package/dist/example/train_base.d.ts +0 -93
- package/dist/example/train_base.js +0 -2269
- package/dist/src/alphabet.d.ts +0 -7
- package/dist/src/alphabet.js +0 -33
- package/dist/src/alu/src/alu.d.ts +0 -185
- package/dist/src/alu/src/alu.js +0 -440
- package/dist/src/alu/src/expr.d.ts +0 -61
- package/dist/src/alu/src/expr.js +0 -318
- package/dist/src/alu/src/index.d.ts +0 -11
- package/dist/src/alu/src/index.js +0 -19
- package/dist/src/alu/src/kernel-arith.d.ts +0 -16
- package/dist/src/alu/src/kernel-arith.js +0 -264
- package/dist/src/alu/src/kernel-bits.d.ts +0 -19
- package/dist/src/alu/src/kernel-bits.js +0 -152
- package/dist/src/alu/src/kernel-logic.d.ts +0 -4
- package/dist/src/alu/src/kernel-logic.js +0 -60
- package/dist/src/alu/src/kernel-nd.d.ts +0 -3
- package/dist/src/alu/src/kernel-nd.js +0 -208
- package/dist/src/alu/src/kernel-numeric.d.ts +0 -54
- package/dist/src/alu/src/kernel-numeric.js +0 -366
- package/dist/src/alu/src/operation.d.ts +0 -168
- package/dist/src/alu/src/operation.js +0 -189
- package/dist/src/alu/src/parser.d.ts +0 -221
- package/dist/src/alu/src/parser.js +0 -577
- package/dist/src/alu/src/resonance.d.ts +0 -55
- package/dist/src/alu/src/resonance.js +0 -126
- package/dist/src/alu/src/text.d.ts +0 -31
- package/dist/src/alu/src/text.js +0 -73
- package/dist/src/alu/src/value.d.ts +0 -109
- package/dist/src/alu/src/value.js +0 -300
- package/dist/src/alu/test/alu.test.d.ts +0 -1
- package/dist/src/alu/test/alu.test.js +0 -764
- package/dist/src/bytes.d.ts +0 -14
- package/dist/src/bytes.js +0 -59
- package/dist/src/canon.d.ts +0 -45
- package/dist/src/canon.js +0 -85
- package/dist/src/config.d.ts +0 -111
- package/dist/src/config.js +0 -91
- package/dist/src/derive/src/deduction.d.ts +0 -136
- package/dist/src/derive/src/deduction.js +0 -159
- package/dist/src/derive/src/index.d.ts +0 -8
- package/dist/src/derive/src/index.js +0 -11
- package/dist/src/derive/src/priority-queue.d.ts +0 -20
- package/dist/src/derive/src/priority-queue.js +0 -73
- package/dist/src/derive/src/rewrite.d.ts +0 -56
- package/dist/src/derive/src/rewrite.js +0 -100
- package/dist/src/derive/src/trie.d.ts +0 -90
- package/dist/src/derive/src/trie.js +0 -217
- package/dist/src/derive/test/derive.test.d.ts +0 -1
- package/dist/src/derive/test/derive.test.js +0 -122
- package/dist/src/extension.d.ts +0 -37
- package/dist/src/extension.js +0 -7
- package/dist/src/geometry.d.ts +0 -233
- package/dist/src/geometry.js +0 -1089
- package/dist/src/index.d.ts +0 -17
- package/dist/src/index.js +0 -19
- package/dist/src/ingest-cache.d.ts +0 -41
- package/dist/src/ingest-cache.js +0 -165
- package/dist/src/meter.d.ts +0 -176
- package/dist/src/meter.js +0 -274
- package/dist/src/mind/articulation.d.ts +0 -6
- package/dist/src/mind/articulation.js +0 -99
- package/dist/src/mind/attention.d.ts +0 -430
- package/dist/src/mind/attention.js +0 -2418
- package/dist/src/mind/bridge.d.ts +0 -39
- package/dist/src/mind/bridge.js +0 -998
- package/dist/src/mind/canonical.d.ts +0 -34
- package/dist/src/mind/canonical.js +0 -93
- package/dist/src/mind/frame-filler.d.ts +0 -15
- package/dist/src/mind/frame-filler.js +0 -535
- package/dist/src/mind/graph-search.d.ts +0 -294
- package/dist/src/mind/graph-search.js +0 -996
- package/dist/src/mind/index.d.ts +0 -9
- package/dist/src/mind/index.js +0 -5
- package/dist/src/mind/junction.d.ts +0 -137
- package/dist/src/mind/junction.js +0 -358
- package/dist/src/mind/learning.d.ts +0 -75
- package/dist/src/mind/learning.js +0 -265
- package/dist/src/mind/match.d.ts +0 -192
- package/dist/src/mind/match.js +0 -666
- package/dist/src/mind/mechanisms/alu.d.ts +0 -4
- package/dist/src/mind/mechanisms/alu.js +0 -36
- package/dist/src/mind/mechanisms/cast.d.ts +0 -89
- package/dist/src/mind/mechanisms/cast.js +0 -1186
- package/dist/src/mind/mechanisms/confluence.d.ts +0 -24
- package/dist/src/mind/mechanisms/confluence.js +0 -271
- package/dist/src/mind/mechanisms/cover.d.ts +0 -6
- package/dist/src/mind/mechanisms/cover.js +0 -232
- package/dist/src/mind/mechanisms/extraction.d.ts +0 -33
- package/dist/src/mind/mechanisms/extraction.js +0 -327
- package/dist/src/mind/mechanisms/recall.d.ts +0 -16
- package/dist/src/mind/mechanisms/recall.js +0 -558
- package/dist/src/mind/mind.d.ts +0 -386
- package/dist/src/mind/mind.js +0 -754
- package/dist/src/mind/pipeline-mechanism.d.ts +0 -183
- package/dist/src/mind/pipeline-mechanism.js +0 -521
- package/dist/src/mind/pipeline.d.ts +0 -49
- package/dist/src/mind/pipeline.js +0 -331
- package/dist/src/mind/prefix-completion.d.ts +0 -59
- package/dist/src/mind/prefix-completion.js +0 -270
- package/dist/src/mind/primitives.d.ts +0 -85
- package/dist/src/mind/primitives.js +0 -333
- package/dist/src/mind/rationale.d.ts +0 -139
- package/dist/src/mind/rationale.js +0 -163
- package/dist/src/mind/reasoning.d.ts +0 -42
- package/dist/src/mind/reasoning.js +0 -305
- package/dist/src/mind/recognition.d.ts +0 -20
- package/dist/src/mind/recognition.js +0 -696
- package/dist/src/mind/resonance.d.ts +0 -35
- package/dist/src/mind/resonance.js +0 -365
- package/dist/src/mind/trace.d.ts +0 -15
- package/dist/src/mind/trace.js +0 -73
- package/dist/src/mind/traverse.d.ts +0 -158
- package/dist/src/mind/traverse.js +0 -716
- package/dist/src/mind/types.d.ts +0 -418
- package/dist/src/mind/types.js +0 -179
- package/dist/src/rabitq-ivf/src/database.d.ts +0 -113
- package/dist/src/rabitq-ivf/src/database.js +0 -201
- package/dist/src/rabitq-ivf/src/index.d.ts +0 -7
- package/dist/src/rabitq-ivf/src/index.js +0 -4
- package/dist/src/rabitq-ivf/src/ivf.d.ts +0 -200
- package/dist/src/rabitq-ivf/src/ivf.js +0 -1165
- package/dist/src/rabitq-ivf/src/prng.d.ts +0 -19
- package/dist/src/rabitq-ivf/src/prng.js +0 -36
- package/dist/src/rabitq-ivf/src/rabitq.d.ts +0 -95
- package/dist/src/rabitq-ivf/src/rabitq.js +0 -313
- package/dist/src/sema.d.ts +0 -41
- package/dist/src/sema.js +0 -77
- package/dist/src/store-sqlite.d.ts +0 -184
- package/dist/src/store-sqlite.js +0 -942
- package/dist/src/store.d.ts +0 -762
- package/dist/src/store.js +0 -1823
- package/dist/src/vec.d.ts +0 -31
- package/dist/src/vec.js +0 -109
- package/src/mind/frame-filler.ts +0 -604
- package/test/69-frame-filler.test.mjs +0 -115
|
@@ -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,42 +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). `voiced` carries
|
|
19
|
-
* the BYTES of the anchors a mechanism declared it voiced (its `used` set),
|
|
20
|
-
* when it declared one — see the pivot's own containment rule. `pre` is the
|
|
21
|
-
* response's shared pre-computation — the post-grounding stages read the
|
|
22
|
-
* same container the mechanisms did. */
|
|
23
|
-
export declare function reason(ctx: MindContext, query: Uint8Array, answer: Uint8Array, preConsumed: ReadonlySet<number>, pre: Precomputed, voiced?: readonly Uint8Array[]): Promise<Uint8Array>;
|
|
24
|
-
/** Fuse independent points of attention into one answer (multi-topic).
|
|
25
|
-
* When the consensus climb finds more than one dominant point, each
|
|
26
|
-
* independent point grounds its own answer; they are bridged together
|
|
27
|
-
* by any learnt connector the graph holds between them. */
|
|
28
|
-
export declare function fuseAttention(ctx: MindContext, query: Uint8Array, primary: Uint8Array, pre: Precomputed,
|
|
29
|
-
/** True when `primary` never touched the consensus climb at all — e.g. a
|
|
30
|
-
* pure ALU computation, which has no anchor of its own. commitVotes
|
|
31
|
-
* ALWAYS admits the dominant root regardless of its vote (attention.ts:
|
|
32
|
-
* "roots.length === 0 || …") on the assumption a lone root already IS
|
|
33
|
-
* primary's own source; that assumption is exactly backwards when
|
|
34
|
-
* primary is unclimbed. Absent or false preserves the original
|
|
35
|
-
* behaviour exactly. */
|
|
36
|
-
unclimbed?: boolean,
|
|
37
|
-
/** The query spans `primary`'s own grounding stands on — used ONLY to place
|
|
38
|
-
* primary in the fused reading order (see below). Resolved by the caller,
|
|
39
|
-
* which is the layer that knows how a given grounding records its evidence;
|
|
40
|
-
* fuseAttention just reads a position from it. Empty or absent preserves
|
|
41
|
-
* the original behaviour exactly. */
|
|
42
|
-
primarySpans?: ReadonlyArray<readonly [number, number]>): Promise<Uint8Array>;
|
|
@@ -1,305 +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). `voiced` carries
|
|
29
|
-
* the BYTES of the anchors a mechanism declared it voiced (its `used` set),
|
|
30
|
-
* when it declared one — see the pivot's own containment rule. `pre` is the
|
|
31
|
-
* response's shared pre-computation — the post-grounding stages read the
|
|
32
|
-
* same container the mechanisms did. */
|
|
33
|
-
export async function reason(ctx, query, answer, preConsumed, pre, voiced = []) {
|
|
34
|
-
// Echo guard: a query that is ITSELF a learnt continuation (some context's
|
|
35
|
-
// answer) is being asked back at the system — hopping forward from it would
|
|
36
|
-
// chain through the very fact that produced it and echo the conversation
|
|
37
|
-
// back. The grounded answer alone is the honest read-out. Deliberately a
|
|
38
|
-
// broad structural gate; pinned by test/31-audit.
|
|
39
|
-
const qId = pre.queryResolved;
|
|
40
|
-
if (qId !== null && ctx.store.prevCount(qId) > 0)
|
|
41
|
-
return answer;
|
|
42
|
-
const consumed = new Set();
|
|
43
|
-
// Consume a node and its neighbours for pivot-cycle prevention — CAPPED at
|
|
44
|
-
// the hub bound, via the store's LIMITed edge reads: a common continuation's
|
|
45
|
-
// reverse fan-in (and a hub context's forward fan-out) is corpus-sized, and
|
|
46
|
-
// no per-hop operation may grow with the corpus. The cap follows the one
|
|
47
|
-
// convention every fan-out decision uses (first √N in the relation's own
|
|
48
|
-
// read order); a pivot suppressed only by a beyond-cap neighbour may now
|
|
49
|
-
// fire — the same visibility trade chooseNext documents.
|
|
50
|
-
const bound = hubBound(ctx);
|
|
51
|
-
const consumeNode = (id) => {
|
|
52
|
-
if (id === null)
|
|
53
|
-
return;
|
|
54
|
-
consumed.add(id);
|
|
55
|
-
for (const p of ctx.store.prevFirst(id, bound))
|
|
56
|
-
consumed.add(p);
|
|
57
|
-
};
|
|
58
|
-
const consumeAll = (id) => {
|
|
59
|
-
if (id === null)
|
|
60
|
-
return;
|
|
61
|
-
consumeNode(id);
|
|
62
|
-
for (const n of ctx.store.nextFirst(id, bound))
|
|
63
|
-
consumed.add(n);
|
|
64
|
-
};
|
|
65
|
-
// Pre-consume whatever the grounding stage already spoke for. The halo
|
|
66
|
-
// sweep is one ANN query per node — cap it at haloQueryK sweeps (cover
|
|
67
|
-
// grounding can pre-consume one node per recognised site, O(query length));
|
|
68
|
-
// nodes past the cap are still consumed directly, they just skip the
|
|
69
|
-
// synonym expansion.
|
|
70
|
-
const preconsume = async () => {
|
|
71
|
-
let haloSweeps = 0;
|
|
72
|
-
for (const id of preConsumed) {
|
|
73
|
-
consumeNode(id);
|
|
74
|
-
if (haloSweeps >= ctx.cfg.haloQueryK)
|
|
75
|
-
continue;
|
|
76
|
-
const h = ctx.store.halo(id);
|
|
77
|
-
if (!h)
|
|
78
|
-
continue;
|
|
79
|
-
haloSweeps++;
|
|
80
|
-
for (const sib of await haloSiblings(ctx, id, h))
|
|
81
|
-
consumeNode(sib.id);
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
if (ctx.meter) {
|
|
85
|
-
await ctx.meter.time("reason.preconsumeHalos", preconsume);
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
await preconsume();
|
|
89
|
-
}
|
|
90
|
-
let cur = answer;
|
|
91
|
-
const qv = pre.guide; // the response-wide guide IS the query's gist
|
|
92
|
-
let t;
|
|
93
|
-
const startedFrom = answer;
|
|
94
|
-
for (let hop = 0; hop < ctx.cfg.recallQueryK; hop++) {
|
|
95
|
-
const curId = resolve(ctx, cur);
|
|
96
|
-
consumeNode(curId);
|
|
97
|
-
// Forward-absorb: follow only UNCONSUMED continuations. The gate below
|
|
98
|
-
// checks an unconsumed edge EXISTS, but follow()'s chooseNext knows
|
|
99
|
-
// nothing of `consumed` and may still walk to a consumed fixpoint —
|
|
100
|
-
// absorbing it would repeat content the grounding stage already spoke
|
|
101
|
-
// for, so a consumed fixpoint falls through to the pivot step instead.
|
|
102
|
-
if (curId !== null &&
|
|
103
|
-
ctx.store.nextFirst(curId, bound).some((n) => !consumed.has(n))) {
|
|
104
|
-
const fwd = await follow(ctx, curId, qv);
|
|
105
|
-
const fwdId = fwd !== null ? resolve(ctx, fwd) : null;
|
|
106
|
-
if (fwd !== null && !bytesEqual(fwd, cur) &&
|
|
107
|
-
(fwdId === null || !consumed.has(fwdId)) &&
|
|
108
|
-
!restatesQuery(query, fwd)) {
|
|
109
|
-
consumeAll(curId);
|
|
110
|
-
t ??= ctx.trace?.enter("reason", [
|
|
111
|
-
rItem(startedFrom, "grounded"),
|
|
112
|
-
]);
|
|
113
|
-
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");
|
|
114
|
-
cur = fwd;
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
// Pivot: find the longest unconsumed learnt context the answer contains.
|
|
119
|
-
consumeAll(curId);
|
|
120
|
-
const pivot = await pivotInto(ctx, cur, consumed, voiced);
|
|
121
|
-
if (pivot === null)
|
|
122
|
-
break;
|
|
123
|
-
const fc = await follow(ctx, pivot, qv);
|
|
124
|
-
consumeAll(pivot);
|
|
125
|
-
if (fc === null || bytesEqual(fc, cur) || restatesQuery(query, fc))
|
|
126
|
-
break;
|
|
127
|
-
t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
|
|
128
|
-
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");
|
|
129
|
-
cur = fc;
|
|
130
|
-
}
|
|
131
|
-
t?.done([rItem(cur, "answer", resolve(ctx, cur) ?? undefined)], "the multi-hop chain's fixpoint");
|
|
132
|
-
return cur;
|
|
133
|
-
}
|
|
134
|
-
/** Fuse independent points of attention into one answer (multi-topic).
|
|
135
|
-
* When the consensus climb finds more than one dominant point, each
|
|
136
|
-
* independent point grounds its own answer; they are bridged together
|
|
137
|
-
* by any learnt connector the graph holds between them. */
|
|
138
|
-
export async function fuseAttention(ctx, query, primary, pre,
|
|
139
|
-
/** True when `primary` never touched the consensus climb at all — e.g. a
|
|
140
|
-
* pure ALU computation, which has no anchor of its own. commitVotes
|
|
141
|
-
* ALWAYS admits the dominant root regardless of its vote (attention.ts:
|
|
142
|
-
* "roots.length === 0 || …") on the assumption a lone root already IS
|
|
143
|
-
* primary's own source; that assumption is exactly backwards when
|
|
144
|
-
* primary is unclimbed. Absent or false preserves the original
|
|
145
|
-
* behaviour exactly. */
|
|
146
|
-
unclimbed = false,
|
|
147
|
-
/** The query spans `primary`'s own grounding stands on — used ONLY to place
|
|
148
|
-
* primary in the fused reading order (see below). Resolved by the caller,
|
|
149
|
-
* which is the layer that knows how a given grounding records its evidence;
|
|
150
|
-
* fuseAttention just reads a position from it. Empty or absent preserves
|
|
151
|
-
* the original behaviour exactly. */
|
|
152
|
-
primarySpans = []) {
|
|
153
|
-
// When the answer is structurally drawn from the query itself
|
|
154
|
-
// (extraction), it already spans all the query's pieces — fusion
|
|
155
|
-
// would only add noise from unrelated stored contexts. The gate is
|
|
156
|
-
// STRICT containment (resolved node in the query's tree, or a contiguous
|
|
157
|
-
// byte run): the old sparse-subsequence test was trivially satisfied by
|
|
158
|
-
// short answers over long queries, silently starving multi-topic queries
|
|
159
|
-
// of fusion.
|
|
160
|
-
if (containsSpan(ctx, query, primary))
|
|
161
|
-
return primary;
|
|
162
|
-
// The committed points of attention ARE the shared climb's roots (same
|
|
163
|
-
// query, same k, same DF mode) — read them from Precomputed instead of
|
|
164
|
-
// re-climbing, so even a traced response pays for the climb once.
|
|
165
|
-
const forest = (await pre.attention()).roots;
|
|
166
|
-
// A LONE root is ordinarily primary's own source — nothing to fuse. But
|
|
167
|
-
// when primary is unclimbed, the lone root was never checked against
|
|
168
|
-
// anything: it is admitted by commitVotes unconditionally, so it may be
|
|
169
|
-
// genuine consensus (Attention.breadth dominates — most of the query's
|
|
170
|
-
// OWN regions corroborate it) or a coincidental echo (breadth does not
|
|
171
|
-
// dominate — see test/35-attention-confidence). breadth is the SCALE-
|
|
172
|
-
// INVARIANT read of exactly this question: the raw IDF vote cannot serve
|
|
173
|
-
// here, since it is an absolute ln(N)-scaled quantity (a genuine root on
|
|
174
|
-
// a large store can score BELOW its own floor while a coincidental echo
|
|
175
|
-
// on a small one scores comfortably above its own, smaller, floor).
|
|
176
|
-
//
|
|
177
|
-
// Breadth alone is not enough when primary is a pure COMPUTATION. The ALU
|
|
178
|
-
// answers "2+2 equals what?" with 4, and the store's own arithmetic table
|
|
179
|
-
// then supplies a lone root — an exemplar like "1+2" — whose breadth
|
|
180
|
-
// dominates because it is corroborated by the computation's OWN bytes.
|
|
181
|
-
// Fusing it projected that exemplar's continuation and the bridge voiced
|
|
182
|
-
// "4+3" (test/11 seed 99). A second point of attention must stand on
|
|
183
|
-
// evidence that is structurally SEPARATE from primary's: at least one
|
|
184
|
-
// perceptual quantum of query between them, the same separation
|
|
185
|
-
// countClusters uses to tell independent evidence neighbourhoods apart.
|
|
186
|
-
// Not a score, and not a tuned bar — the fold's own quantum.
|
|
187
|
-
//
|
|
188
|
-
// With no primarySpans (the caller did not resolve them) every span
|
|
189
|
-
// vacuously qualifies, preserving the original behaviour exactly.
|
|
190
|
-
const quantum = ctx.space.maxGroup;
|
|
191
|
-
const independentOfPrimary = (root) => primarySpans.every(([s, e]) => {
|
|
192
|
-
const gap = root.end <= s
|
|
193
|
-
? s - root.end
|
|
194
|
-
: e <= root.start
|
|
195
|
-
? root.start - e
|
|
196
|
-
: 0;
|
|
197
|
-
return gap >= quantum;
|
|
198
|
-
});
|
|
199
|
-
const lonePromotes = unclimbed && forest.length === 1 &&
|
|
200
|
-
forest[0].breadth > 0.5 && independentOfPrimary(forest[0]);
|
|
201
|
-
if (forest.length === 0 || (forest.length <= 1 && !lonePromotes)) {
|
|
202
|
-
return primary;
|
|
203
|
-
}
|
|
204
|
-
// WHERE THE QUERY ASKED FOR IT. The sort below orders the fused pieces by
|
|
205
|
-
// query position, which is the whole point of the `start` field: a
|
|
206
|
-
// multi-topic answer should read in the order the question posed its
|
|
207
|
-
// topics. Every ROOT carries its own start. `primary` did not — it was
|
|
208
|
-
// given forest[0].start, the FIRST attention root's position, which is
|
|
209
|
-
// primary's own source only when primary happens to come from that root.
|
|
210
|
-
// When it does not, primary is sorted to a position it never occupied.
|
|
211
|
-
//
|
|
212
|
-
// Observed live: "What is the capital of France? And what is 2 + 2?"
|
|
213
|
-
// answered "4The capital city of France is Paris." — the ALU result, whose
|
|
214
|
-
// evidence is the "2 + 2" span near the END of the query, inherited the
|
|
215
|
-
// France root's start of 0 and sorted ahead of the France answer. Both
|
|
216
|
-
// pieces were right; only the order was.
|
|
217
|
-
//
|
|
218
|
-
// primary's own position is the earliest query byte its grounding stands
|
|
219
|
-
// on. `accounted` is the cost-ladder read of that and is authoritative
|
|
220
|
-
// when non-empty; when it is empty the grounding is a pure COMPUTATION,
|
|
221
|
-
// whose evidence is its computed span — the same cost-ladder-vs-coverage
|
|
222
|
-
// distinction think() already draws for the fusion remainder ("`accounted`
|
|
223
|
-
// alone undercounts this ... cover prices its computed spans at near-zero
|
|
224
|
-
// and deliberately leaves them out"), read here for position instead of
|
|
225
|
-
// for coverage. With neither, nothing is known and the old behaviour
|
|
226
|
-
// (forest[0].start) stands.
|
|
227
|
-
const primaryStart = primarySpans.length > 0
|
|
228
|
-
? primarySpans.reduce((m, [s]) => Math.min(m, s), Infinity)
|
|
229
|
-
: forest[0].start;
|
|
230
|
-
const pieces = [
|
|
231
|
-
{ start: primaryStart, bytes: primary },
|
|
232
|
-
];
|
|
233
|
-
const qv = pre.guide; // once, not per root
|
|
234
|
-
const rest = lonePromotes ? forest : forest.slice(1);
|
|
235
|
-
const t = ctx.trace?.enter("fuseAttention", [
|
|
236
|
-
rItem(primary, "primary"),
|
|
237
|
-
...rest.map((r) => rNode(ctx, r.anchor, "point", r.vote)),
|
|
238
|
-
]);
|
|
239
|
-
for (const root of rest) {
|
|
240
|
-
// DISPERSION: this root's contributing regions are confined to a
|
|
241
|
-
// single cluster (see Attention.clusters) — one local neighbourhood of
|
|
242
|
-
// the query, not several separate places. Raw region count already
|
|
243
|
-
// failed to discriminate a coincidental match from a genuine further
|
|
244
|
-
// topic (test/24 gap 3.1 vs test/35's echo); dispersion is a different
|
|
245
|
-
// question — not how MUCH evidence, but how many separate PLACES in the
|
|
246
|
-
// query corroborate it — and a coincidental match is structurally
|
|
247
|
-
// confined to one cluster no matter how strongly it resonates.
|
|
248
|
-
//
|
|
249
|
-
// EXCEPTION: crossRegionVotes' own joint conclusions (a query naming
|
|
250
|
-
// two attributes that were only ever learnt TOGETHER — test/34's own
|
|
251
|
-
// binding corpus) are inherently ONE fused context and are pooled from
|
|
252
|
-
// a single synthetic region, so they always read as one cluster even
|
|
253
|
-
// though they already, by construction, account for both original
|
|
254
|
-
// mentions. `breadth` (dominates — the same > half-the-query bar used
|
|
255
|
-
// everywhere else) still correctly recognises these: a genuine joint
|
|
256
|
-
// binding explains the MAJORITY of the query's regions on its own,
|
|
257
|
-
// which a coincidental echo never does (verified: test/35's echo tops
|
|
258
|
-
// out at 0.40). So a root is trusted when EITHER measure alone
|
|
259
|
-
// indicates real signal — excluded only when BOTH are weak. Cheap and
|
|
260
|
-
// synchronous — checked before the async already-answered walk below.
|
|
261
|
-
if (root.clusters < 2 && root.breadth <= 0.5) {
|
|
262
|
-
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");
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
// ALREADY ANSWERED: this root's own learnt continuation — the same
|
|
266
|
-
// content-addressed walk reason()'s echo guard already trusts
|
|
267
|
-
// (`ctx.store.prevCount(qId) > 0`), here applied per-candidate instead
|
|
268
|
-
// of to the whole query — is VERBATIM present later in the query. A
|
|
269
|
-
// query that embeds both an exchange's ask and its own already-given
|
|
270
|
-
// reply (a conversation's turn plus its own prior answer, concatenated
|
|
271
|
-
// raw by addTurn — or any caller pasting a transcript into one
|
|
272
|
-
// respond() call; the check is Mind-bookkeeping-free, so it treats both
|
|
273
|
-
// identically) has already spoken this root's answer — fusing it in
|
|
274
|
-
// again would only restate it. Deliberately NOT a magnitude measure:
|
|
275
|
-
// it fires on exact content-addressed recurrence, not on how strongly
|
|
276
|
-
// the root resonates.
|
|
277
|
-
const cont = await follow(ctx, root.anchor, qv);
|
|
278
|
-
if (cont !== null && cont.length > 0 && indexOf(query, cont, root.end) >= 0) {
|
|
279
|
-
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");
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
const g = await project(ctx, root.anchor, qv);
|
|
283
|
-
if (g === null || g.length === 0)
|
|
284
|
-
continue;
|
|
285
|
-
if (pieces.some((p) => indexOf(p.bytes, g, 0) >= 0))
|
|
286
|
-
continue;
|
|
287
|
-
pieces.push({ start: root.start, bytes: g });
|
|
288
|
-
}
|
|
289
|
-
if (pieces.length === 1) {
|
|
290
|
-
t?.done([rItem(primary, "answer")], "no further independent point grounded");
|
|
291
|
-
return primary;
|
|
292
|
-
}
|
|
293
|
-
pieces.sort((a, b) => a.start - b.start);
|
|
294
|
-
let out = pieces[0].bytes;
|
|
295
|
-
for (let i = 1; i < pieces.length; i++) {
|
|
296
|
-
// An approximate-resonance miss (or a genuinely unlearnt junction) joins
|
|
297
|
-
// the pieces bare — joinWithBridge surfaces it as a bridgeMiss step.
|
|
298
|
-
out = await joinWithBridge(ctx, out, pieces[i].bytes);
|
|
299
|
-
}
|
|
300
|
-
t?.done([rItem(out, "answer", resolve(ctx, out) ?? undefined)], `fused ${pieces.length} independent points of attention into one answer`);
|
|
301
|
-
return out;
|
|
302
|
-
}
|
|
303
|
-
// (resonance.js is already a static dependency above — `bridge` — so the old
|
|
304
|
-
// dynamic import of pivotInto guarded against a cycle that does not exist.)
|
|
305
|
-
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[];
|