@hviana/sema 0.2.1 → 0.2.3
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/src/geometry.d.ts +28 -0
- package/dist/src/geometry.js +35 -0
- package/dist/src/mind/articulation.js +1 -1
- package/dist/src/mind/attention.d.ts +4 -0
- package/dist/src/mind/attention.js +65 -12
- package/dist/src/mind/graph-search.d.ts +16 -1
- package/dist/src/mind/graph-search.js +34 -5
- package/dist/src/mind/learning.d.ts +1 -1
- package/dist/src/mind/learning.js +20 -5
- package/dist/src/mind/mechanisms/alu.js +8 -1
- package/dist/src/mind/mechanisms/cast.js +35 -11
- package/dist/src/mind/mechanisms/cover.js +40 -1
- package/dist/src/mind/mechanisms/extraction.js +75 -30
- package/dist/src/mind/mechanisms/recall.js +26 -2
- package/dist/src/mind/mind.d.ts +3 -2
- package/dist/src/mind/mind.js +28 -54
- package/dist/src/mind/pipeline.js +34 -2
- package/dist/src/mind/primitives.d.ts +16 -9
- package/dist/src/mind/primitives.js +58 -22
- package/dist/src/mind/reasoning.d.ts +9 -1
- package/dist/src/mind/reasoning.js +26 -4
- package/dist/src/mind/recognition.js +30 -6
- package/dist/src/mind/traverse.js +15 -0
- package/dist/src/mind/types.d.ts +58 -10
- package/dist/src/mind/types.js +15 -0
- package/package.json +1 -1
- package/src/geometry.ts +61 -1
- package/src/mind/articulation.ts +1 -0
- package/src/mind/attention.ts +80 -12
- package/src/mind/graph-search.ts +59 -2
- package/src/mind/learning.ts +20 -3
- package/src/mind/mechanisms/alu.ts +8 -1
- package/src/mind/mechanisms/cast.ts +46 -8
- package/src/mind/mechanisms/cover.ts +46 -0
- package/src/mind/mechanisms/extraction.ts +101 -40
- package/src/mind/mechanisms/recall.ts +30 -2
- package/src/mind/mind.ts +38 -61
- package/src/mind/pipeline.ts +37 -2
- package/src/mind/primitives.ts +71 -29
- package/src/mind/reasoning.ts +26 -3
- package/src/mind/recognition.ts +28 -5
- package/src/mind/traverse.ts +18 -0
- package/src/mind/types.ts +73 -10
- package/test/35-attention-confidence.test.mjs +139 -0
|
@@ -49,32 +49,86 @@ export async function extractBySkill(ctx, query, pre) {
|
|
|
49
49
|
if (ranked.length === 0) {
|
|
50
50
|
return fail("no consensus anchor — no skill to apply");
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
// Try ranked anchors IN ORDER until one yields a USABLE extraction — not
|
|
53
|
+
// merely a span-shaped exemplar, but one whose extracted span clears the
|
|
54
|
+
// same one-river-fold quantum (W) cover.ts's restatedSpan gate already
|
|
55
|
+
// treats as the floor below which byte overlap is chance, not evidence.
|
|
56
|
+
// isSpanShaped (spanShapedOf) is a deliberately permissive sparse-
|
|
57
|
+
// subsequence check — see the section note below — so it accepts exemplars
|
|
58
|
+
// whose relation to the query is coincidental gap-matching, not genuine
|
|
59
|
+
// structure. Stopping at the FIRST such exemplar let a coincidental match
|
|
60
|
+
// early in the ranked list win outright and read out a sub-quantum
|
|
61
|
+
// fragment (observed: a 3-byte "Hel" pulled from an unrelated exemplar,
|
|
62
|
+
// while a later ranked anchor would have read the query's own "Hello…"
|
|
63
|
+
// correctly). Trying further anchors when one produces nothing usable is
|
|
64
|
+
// the same idiom this loop already uses for non-exemplars — extended to
|
|
65
|
+
// cover a bad extraction, not just a structural non-match.
|
|
66
|
+
//
|
|
67
|
+
// The retry is bounded at pre.k — the SAME evidence-breadth constant every
|
|
68
|
+
// other consumer of a ranked list already self-limits to (resonance, the
|
|
69
|
+
// weave, the climb itself; see Precomputed.k's own doc comment) — not the
|
|
70
|
+
// full ranked list. locate()'s frame match has an EXACT-byte tier with no
|
|
71
|
+
// significance correction of its own (short W-byte frames are cheap to
|
|
72
|
+
// match by pure chance), so trying every ranked anchor turns that per-
|
|
73
|
+
// anchor chance into a near-certainty over enough attempts: on a pure-
|
|
74
|
+
// gibberish query, 170 anchors deep found an unrelated Zulu exemplar whose
|
|
75
|
+
// short frame happened to byte-match, producing "xyzzy pl" — a coincidence
|
|
76
|
+
// no different in kind from the "RaBitQ estimate overshot the reach bar
|
|
77
|
+
// and grounded pure gibberish" failure recall.ts's own significance
|
|
78
|
+
// correction exists to prevent (see recallByResonance's reach-threshold
|
|
79
|
+
// comment). Bounding the search to the ranked list's own top-k restores
|
|
80
|
+
// the "genuinely relevant but not root-significant" exemplars this loop
|
|
81
|
+
// was built for, without the unbounded tail's chance collisions.
|
|
82
|
+
const W = ctx.space.maxGroup;
|
|
83
|
+
const searched = ranked.slice(0, pre.k);
|
|
84
|
+
let shapeMisses = 0;
|
|
85
|
+
let subQuantum = 0;
|
|
86
|
+
for (const cand of searched) {
|
|
87
|
+
const exemplar = await pre.spanShapedOf(cand.anchor);
|
|
88
|
+
if (!exemplar) {
|
|
89
|
+
shapeMisses++;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const built = buildFromExemplar(ctx, query, pre, exemplar);
|
|
93
|
+
if (built === null || built.bytes.length < W) {
|
|
94
|
+
subQuantum++;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (shapeMisses > 0 || subQuantum > 0) {
|
|
98
|
+
ctx.trace?.step("trySkillAnchors", [
|
|
99
|
+
rItem(query.subarray(0, 0), `skipped ${shapeMisses + subQuantum}`),
|
|
100
|
+
rNode(ctx, cand.anchor, "chosen"),
|
|
101
|
+
], [], `skipped ${shapeMisses} non-exemplar and ${subQuantum} sub-quantum ` +
|
|
102
|
+
`anchor(s) before one yielded a usable extraction`);
|
|
61
103
|
}
|
|
62
|
-
|
|
104
|
+
t?.done([rItem(built.bytes, "extracted")], built.pieces === 1
|
|
105
|
+
? `apply a learnt extraction skill — read the analogous span of the query` +
|
|
106
|
+
` framed like "${decodeText(exemplar.answerBytes)}" sits in its exemplar`
|
|
107
|
+
: `apply a learnt MULTI-PIECE skill — read ${built.pieces} analogous` +
|
|
108
|
+
` pieces of the query and synthesize them like "${decodeText(exemplar.answerBytes)}"`);
|
|
109
|
+
return {
|
|
110
|
+
bytes: built.bytes,
|
|
111
|
+
accounted: built.accounted,
|
|
112
|
+
unexplained: unexplainedLabel(query, built.accounted),
|
|
113
|
+
};
|
|
63
114
|
}
|
|
64
|
-
if (
|
|
65
|
-
ctx.trace?.step("trySkillAnchors", [], [], `none of ${
|
|
115
|
+
if (shapeMisses === searched.length) {
|
|
116
|
+
ctx.trace?.step("trySkillAnchors", [], [], `none of the top ${searched.length} ranked anchor(s) (of ${ranked.length} total) ` +
|
|
117
|
+
`is a span-shaped skill exemplar`);
|
|
66
118
|
return fail("no consensus root is a span-shaped skill exemplar");
|
|
67
119
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
120
|
+
return fail("no ranked anchor yielded an extraction at or above the quantum floor");
|
|
121
|
+
}
|
|
122
|
+
/** Build the extracted bytes for ONE already-accepted span-shaped exemplar —
|
|
123
|
+
* factored out of {@link extractBySkill} so its anchor loop can try
|
|
124
|
+
* successive ranked candidates instead of committing to the first
|
|
125
|
+
* structural match. Null when the exemplar's answer does not decompose
|
|
126
|
+
* against its context, or no piece's frame locates in the query. */
|
|
127
|
+
function buildFromExemplar(ctx, query, pre, exemplar) {
|
|
74
128
|
const { contextBytes, answerBytes } = exemplar;
|
|
75
129
|
const ansCtxRuns = answerRunsInContext(ctx, contextBytes, answerBytes);
|
|
76
130
|
if (ansCtxRuns === null || ansCtxRuns.length === 0) {
|
|
77
|
-
return
|
|
131
|
+
return null;
|
|
78
132
|
}
|
|
79
133
|
if (ansCtxRuns.length > 1) {
|
|
80
134
|
ctx.trace?.step("decomposeAnswer", [rItem(answerBytes, "multi-piece-answer")], ansCtxRuns.map((r) => rItem(contextBytes.subarray(r.start, r.end), "piece", undefined, [
|
|
@@ -142,19 +196,10 @@ export async function extractBySkill(ctx, query, pre) {
|
|
|
142
196
|
accounted.push([start, end]);
|
|
143
197
|
}
|
|
144
198
|
if (pieces.length === 0) {
|
|
145
|
-
return
|
|
199
|
+
return null;
|
|
146
200
|
}
|
|
147
201
|
const out = pieces.length === 1 ? pieces[0] : concatBytes(pieces);
|
|
148
|
-
|
|
149
|
-
? `apply a learnt extraction skill — read the analogous span of the query` +
|
|
150
|
-
` framed like "${decodeText(answerBytes)}" sits in its exemplar`
|
|
151
|
-
: `apply a learnt MULTI-PIECE skill — read ${pieces.length} analogous` +
|
|
152
|
-
` pieces of the query and synthesize them like "${decodeText(answerBytes)}"`);
|
|
153
|
-
return {
|
|
154
|
-
bytes: out,
|
|
155
|
-
accounted,
|
|
156
|
-
unexplained: unexplainedLabel(query, accounted),
|
|
157
|
-
};
|
|
202
|
+
return { bytes: out, accounted, pieces: pieces.length };
|
|
158
203
|
}
|
|
159
204
|
// ── The two span-shape readings: OPEN acceptance vs. STRONG decomposition ──
|
|
160
205
|
//
|
|
@@ -74,10 +74,34 @@ export async function recallByResonance(ctx, query, pre) {
|
|
|
74
74
|
maximal.push(s);
|
|
75
75
|
maxEnd = s.end;
|
|
76
76
|
}
|
|
77
|
-
|
|
77
|
+
// The wrapper must actually BE scaffolding: RC8's own premise is "the
|
|
78
|
+
// wrapper is scaffolding; the argument is the span that leads
|
|
79
|
+
// somewhere" ("How do you say 'thank you' in French?" — everything
|
|
80
|
+
// outside the argument is a small fixed template). When the query
|
|
81
|
+
// instead has ANOTHER substantial recognised form (≥ W2, the same
|
|
82
|
+
// constituent bar the argument itself must clear) sitting OUTSIDE the
|
|
83
|
+
// chosen argument, the query is not one argument in a wrapper — it is
|
|
84
|
+
// several complete, independently-meaningful pieces (a multi-turn
|
|
85
|
+
// conversation's own accumulated turns are exactly this shape), and
|
|
86
|
+
// binding to the argument's continuation would answer past content
|
|
87
|
+
// the query itself already carries forward. Derived from the same W2
|
|
88
|
+
// bar the argument itself is held to, never a separate tuned number.
|
|
89
|
+
const hasSubstantialOutside = maximal.length === 1 &&
|
|
90
|
+
pre.rec.sites.some((s) => s.end - s.start >= W2 &&
|
|
91
|
+
(s.end <= maximal[0].start || s.start >= maximal[0].end));
|
|
92
|
+
if (maximal.length === 1 && !hasSubstantialOutside) {
|
|
78
93
|
const arg = maximal[0];
|
|
79
94
|
const g = await follow(ctx, arg.payload, queryGist);
|
|
80
|
-
|
|
95
|
+
// The same "no restated fragment" guard tier 2 applies below (§ "the
|
|
96
|
+
// anchor cleared the consensus floor..."): a followed continuation
|
|
97
|
+
// that is itself a proper byte-subspan of the QUERY restates part of
|
|
98
|
+
// the question — never an answer. A multi-turn query's own later
|
|
99
|
+
// turns are exact, content-addressed matches for exactly this reason
|
|
100
|
+
// (each turn is its own previously-learnt form), so without this
|
|
101
|
+
// guard the argument's OWN later restatement in the same
|
|
102
|
+
// conversation reads as if it were the next thing to say.
|
|
103
|
+
if (g !== null && g.length > 0 &&
|
|
104
|
+
!(g.length < query.length && indexOf(query, g, 0) >= 0)) {
|
|
81
105
|
return ground(g, "argument binding — the query's sole edge-source constituent, continuation followed", [[arg.start, arg.end]], STEP);
|
|
82
106
|
}
|
|
83
107
|
}
|
package/dist/src/mind/mind.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Vec } from "../vec.js";
|
|
2
2
|
import { Sema, Space } from "../sema.js";
|
|
3
3
|
import { Alphabet } from "../alphabet.js";
|
|
4
|
-
import {
|
|
4
|
+
import { Grid } from "../geometry.js";
|
|
5
5
|
import { BoundedMap, type Store } from "../store.js";
|
|
6
6
|
import { type MindConfig } from "../config.js";
|
|
7
7
|
import { type Canon } from "../canon.js";
|
|
@@ -111,7 +111,7 @@ export declare class Mind implements MindContext {
|
|
|
111
111
|
* {@link MindContext._gistCache}. 32 MB ≈ 8K gists at D=1024; hub
|
|
112
112
|
* candidate sets (√N at most) fit comfortably and recur across queries. */
|
|
113
113
|
_gistCache: BoundedMap<number, Vec>;
|
|
114
|
-
_depositTrees: BoundedMap<string,
|
|
114
|
+
_depositTrees: BoundedMap<string, import("./types.js").DepositCacheEntry>;
|
|
115
115
|
_depositLens: Set<number>;
|
|
116
116
|
_internIds: WeakMap<Sema, number>;
|
|
117
117
|
private _nextConvId;
|
|
@@ -122,6 +122,7 @@ export declare class Mind implements MindContext {
|
|
|
122
122
|
sites: ReadonlyArray<Site>;
|
|
123
123
|
leaves: ReadonlyArray<Leaf>;
|
|
124
124
|
splits: ReadonlySet<number>;
|
|
125
|
+
starts: ReadonlySet<number>;
|
|
125
126
|
};
|
|
126
127
|
/** Disambiguate among multiple learnt continuations of the same context node.
|
|
127
128
|
* Required by {@link GraphSearchHost} — the graph search calls this through the
|
package/dist/src/mind/mind.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// Implementation split across src/mind/*.ts — this file assembles the Mind class.
|
|
11
11
|
import { makeKeyring, rng, setVecConfig } from "../vec.js";
|
|
12
12
|
import { Alphabet } from "../alphabet.js";
|
|
13
|
-
import {
|
|
13
|
+
import { bytesToTree, reachThreshold, } from "../geometry.js";
|
|
14
14
|
import { BoundedMap } from "../store.js";
|
|
15
15
|
import { SQliteStore } from "../store-sqlite.js";
|
|
16
16
|
import { resolveConfig } from "../config.js";
|
|
@@ -95,7 +95,12 @@ export class Mind {
|
|
|
95
95
|
// recogniseSpan wraps recognise
|
|
96
96
|
recogniseSpan(bytes) {
|
|
97
97
|
const r = recognise(this, bytes);
|
|
98
|
-
return {
|
|
98
|
+
return {
|
|
99
|
+
sites: r.sites,
|
|
100
|
+
leaves: r.leaves,
|
|
101
|
+
splits: r.splits,
|
|
102
|
+
starts: r.starts,
|
|
103
|
+
};
|
|
99
104
|
}
|
|
100
105
|
/** Disambiguate among multiple learnt continuations of the same context node.
|
|
101
106
|
* Required by {@link GraphSearchHost} — the graph search calls this through the
|
|
@@ -263,14 +268,13 @@ export class Mind {
|
|
|
263
268
|
* where one turn ends and the next begins. */
|
|
264
269
|
beginConversation(state) {
|
|
265
270
|
const id = this._nextConvId++;
|
|
266
|
-
// Build the initial pyramid: from scratch for a new conversation,
|
|
267
|
-
// or from the saved context bytes when restoring.
|
|
268
271
|
const initBytes = state?.context ?? new Uint8Array(0);
|
|
269
|
-
const
|
|
272
|
+
const initBoundaries = state?.boundaries ? [...state.boundaries] : [];
|
|
273
|
+
const tree = bytesToTree(this.space, this.alphabet, initBytes, undefined, undefined, initBoundaries.length > 0 ? initBoundaries : undefined);
|
|
270
274
|
this._conversations.set(id, {
|
|
271
|
-
|
|
275
|
+
tree,
|
|
272
276
|
bytes: initBytes,
|
|
273
|
-
boundaries:
|
|
277
|
+
boundaries: initBoundaries,
|
|
274
278
|
perceiveMemo: new Map(),
|
|
275
279
|
recogniseMemo: new Map(),
|
|
276
280
|
climbMemo: new Map(),
|
|
@@ -316,21 +320,18 @@ export class Mind {
|
|
|
316
320
|
* place a context grows ({@link addTurn} and {@link respondTurn} both
|
|
317
321
|
* come through here), so the append semantics cannot drift. */
|
|
318
322
|
_growContext(data, turnBytes) {
|
|
319
|
-
const prevLen = data.
|
|
323
|
+
const prevLen = data.bytes.length;
|
|
320
324
|
// An empty turn neither grows the context nor marks a boundary —
|
|
321
325
|
// boundaries are documented strictly increasing, and a zero-length
|
|
322
|
-
// "turn" is no turn.
|
|
323
|
-
// refold below O(1), so the existing tree is returned unchanged.
|
|
326
|
+
// "turn" is no turn. Nothing changed, so the existing tree stands.
|
|
324
327
|
const grow = turnBytes.length > 0;
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
: turnBytes;
|
|
330
|
-
if (grow && prevLen > 0)
|
|
328
|
+
if (!grow)
|
|
329
|
+
return data.tree;
|
|
330
|
+
const grown = prevLen > 0 ? concat2(data.bytes, turnBytes) : turnBytes;
|
|
331
|
+
if (prevLen > 0)
|
|
331
332
|
data.boundaries.push(prevLen);
|
|
332
|
-
const
|
|
333
|
-
data.
|
|
333
|
+
const tree = bytesToTree(this.space, this.alphabet, grown, undefined, undefined, data.boundaries.length > 0 ? data.boundaries : undefined);
|
|
334
|
+
data.tree = tree;
|
|
334
335
|
data.bytes = grown;
|
|
335
336
|
data.perceiveMemo.set(latin1Key(grown), tree);
|
|
336
337
|
return tree;
|
|
@@ -377,42 +378,15 @@ export class Mind {
|
|
|
377
378
|
this.canon = this._canonFor(typeof turn === "string" ? textCanon : null);
|
|
378
379
|
this.canonMemo = this.canon ? new Map() : null;
|
|
379
380
|
try {
|
|
380
|
-
//
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
const suffixStart = data.boundaries[data.boundaries.length - 1];
|
|
390
|
-
if (suffixStart < newContext.length) {
|
|
391
|
-
const suffixBytes = newContext.subarray(suffixStart);
|
|
392
|
-
const suffixRec = recognise(this, suffixBytes);
|
|
393
|
-
const fullRec = recognise(this, newContext);
|
|
394
|
-
const seen = new Set(fullRec.sites.map((s) => `${s.start},${s.end}`));
|
|
395
|
-
const mergedSites = [...fullRec.sites];
|
|
396
|
-
for (const site of suffixRec.sites) {
|
|
397
|
-
const absStart = suffixStart + site.start;
|
|
398
|
-
const absEnd = suffixStart + site.end;
|
|
399
|
-
const key = `${absStart},${absEnd}`;
|
|
400
|
-
if (!seen.has(key)) {
|
|
401
|
-
seen.add(key);
|
|
402
|
-
mergedSites.push({
|
|
403
|
-
start: absStart,
|
|
404
|
-
end: absEnd,
|
|
405
|
-
payload: site.payload,
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
data.recogniseMemo.set(latin1Key(newContext), {
|
|
410
|
-
sites: mergedSites,
|
|
411
|
-
leaves: fullRec.leaves,
|
|
412
|
-
splits: fullRec.splits,
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
}
|
|
381
|
+
// No recognise-memo pre-seeding here: that used to be necessary
|
|
382
|
+
// because the flat/positional fold lost visibility into an earlier
|
|
383
|
+
// turn's own structure once later bytes shifted its position
|
|
384
|
+
// (foldTree no longer visited the turn's root node). The
|
|
385
|
+
// STABLE-PREFIX fold (see {@link ConversationData}) makes every
|
|
386
|
+
// turn's subtree independent of what follows it by construction, so
|
|
387
|
+
// recognise() finds it correctly on its own, first-touch, exactly
|
|
388
|
+
// once per turn — no workaround needed, and none pre-empting
|
|
389
|
+
// recognise()'s own memo with a partial result.
|
|
416
390
|
const top = this.trace?.enter("respondTurn", [
|
|
417
391
|
rItem(newContext, "query"),
|
|
418
392
|
]);
|
|
@@ -177,8 +177,40 @@ export async function think(ctx, query, mechs) {
|
|
|
177
177
|
? new Set()
|
|
178
178
|
: new Set(recognise(ctx, answer).sites.map((s) => s.payload));
|
|
179
179
|
const reasoned = await reason(ctx, query, answer, preConsumed, pre);
|
|
180
|
-
|
|
181
|
-
|
|
180
|
+
// Fuse only when the query has a genuine REMAINDER no mechanism's
|
|
181
|
+
// structural evidence touched at all. `decided.accounted` alone
|
|
182
|
+
// undercounts this: it is a COST-LADDER quantity (cover.ts prices its
|
|
183
|
+
// masked/computed spans at near-zero and deliberately leaves them out of
|
|
184
|
+
// `accounted` so PASS-bridged bytes are still charged), not a coverage
|
|
185
|
+
// one — a query fully explained by one computed span plus bridged
|
|
186
|
+
// connectors can report `accounted: []` while nothing is actually left
|
|
187
|
+
// unexplained. The genuine remainder is what NEITHER the winning
|
|
188
|
+
// candidate's accounted spans NOR any recognised extension's computed
|
|
189
|
+
// span (`pre.computed` — every mechanism's parse() output, ALU included)
|
|
190
|
+
// ever touched. A remainder under one river-fold quantum (W, the same
|
|
191
|
+
// floor cover.ts's restatedSpan and the honesty-density bar above both
|
|
192
|
+
// use) is bridging punctuation/whitespace, never a second topic —
|
|
193
|
+
// observed: a single space between two fully-computed arithmetic spans
|
|
194
|
+
// ("2+2 3+3") registered as "unaccounted" and pulled in an unrelated
|
|
195
|
+
// corpus fact, corrupting "4 6" into "4 63".
|
|
196
|
+
const explained = [
|
|
197
|
+
...decided.accounted,
|
|
198
|
+
...pre.computed.map((u) => [u.i, u.j]),
|
|
199
|
+
];
|
|
200
|
+
const remainder = unaccounted(explained);
|
|
201
|
+
// Whether the winning candidate's entire recognised substance is
|
|
202
|
+
// COMPUTED — every accounted span exactly a pre.computed span, nothing
|
|
203
|
+
// from a genuinely recognised/climbed site. fuseAttention's lone-root
|
|
204
|
+
// shortcut assumes a single point of attention already IS primary's own
|
|
205
|
+
// source; that assumption is exactly backwards for a pure computation
|
|
206
|
+
// (an ALU result has no anchor of its own) — see fuseAttention's
|
|
207
|
+
// `unclimbed` parameter, gated there by Attention.breadth so a
|
|
208
|
+
// coincidental echo (which this flag alone cannot distinguish) is still
|
|
209
|
+
// rejected.
|
|
210
|
+
const unclimbed = decided.accounted.length > 0 &&
|
|
211
|
+
decided.accounted.every(([i, j]) => pre.computed.some((u) => u.i === i && u.j === j));
|
|
212
|
+
const fused = remainder >= ctx.space.maxGroup
|
|
213
|
+
? await fuseAttention(ctx, query, reasoned, pre, unclimbed)
|
|
182
214
|
: reasoned;
|
|
183
215
|
done(fused, "grounded, reasoned forward, fused across points of attention");
|
|
184
216
|
return { bytes: fused, provenance };
|
|
@@ -15,15 +15,22 @@ export declare function latin1Key(bytes: Uint8Array): string;
|
|
|
15
15
|
* CALLER — who assembled the multi-turn context — knows where those
|
|
16
16
|
* boundaries are; the geometry never guesses them from the bytes. */
|
|
17
17
|
export declare function perceive(ctx: MindContext, input: Input, leafAt?: (i: number) => number | null, lookup?: (ids: number[]) => number | null, boundaries?: readonly number[]): Sema;
|
|
18
|
-
/** The DEPOSIT-shaped perceive
|
|
19
|
-
* perception
|
|
20
|
-
* for exact recall),
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
|
|
18
|
+
/** The DEPOSIT-shaped perceive. A FIRST-SEEN input takes the PLAIN fold
|
|
19
|
+
* (bit-identical to inference perception of a standalone query — that
|
|
20
|
+
* structural train/inference agreement is load-bearing for exact recall),
|
|
21
|
+
* computed incrementally via the fold's level pyramid
|
|
22
|
+
* ({@link bytesToTreePyramid}). An input that EXTENDS a previously
|
|
23
|
+
* deposited one is a conversation context grown by one turn — the cached
|
|
24
|
+
* prefix length IS the turn boundary (derived from the deposit sequence
|
|
25
|
+
* itself, never from content conventions) — and takes the STABLE-PREFIX
|
|
26
|
+
* fold over the accumulated boundaries, bit-identical to the boundary
|
|
27
|
+
* fold query-time conversation perception uses, so the trained context
|
|
28
|
+
* node and the query's context subtree are the SAME node. Segment folds
|
|
29
|
+
* reuse across deposits ({@link stablePrefixFoldIncremental}) — O(turn)
|
|
30
|
+
* instead of O(context) per turn. The fold state is purely a cache; the
|
|
31
|
+
* boundary accumulation is what an evicted chain loses (falling back to
|
|
32
|
+
* the plain fold, the pre-boundary shape — a warm replay restores it). */
|
|
33
|
+
export declare function perceiveDeposit(ctx: MindContext, bytes: Uint8Array, conversational?: boolean): Sema;
|
|
27
34
|
/** The raw bytes of an input — modality-neutral conversion. */
|
|
28
35
|
export declare function inputBytes(ctx: MindContext, input: Input): Uint8Array;
|
|
29
36
|
/** Convenience: the gist vector of a byte span. */
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Address — bytes → node (perceive, foldTree, resolve)
|
|
4
4
|
// Read — node → bytes (read)
|
|
5
|
-
import { bytesToTree, bytesToTreePyramid, gridToTree, hilbertBytes, stackGrids, } from "../geometry.js";
|
|
5
|
+
import { bytesToTree, bytesToTreePyramid, gridToTree, hilbertBytes, stablePrefixFoldIncremental, stackGrids, } from "../geometry.js";
|
|
6
6
|
import { canonHash } from "../canon.js";
|
|
7
7
|
import { bytesEqual } from "../bytes.js";
|
|
8
8
|
import { ALL } from "./types.js";
|
|
@@ -58,29 +58,65 @@ export function perceive(ctx, input, leafAt, lookup, boundaries) {
|
|
|
58
58
|
}
|
|
59
59
|
return gridToTree(ctx.space, ctx.alphabet, input);
|
|
60
60
|
}
|
|
61
|
-
/** The DEPOSIT-shaped perceive
|
|
62
|
-
* perception
|
|
63
|
-
* for exact recall),
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
|
|
61
|
+
/** The DEPOSIT-shaped perceive. A FIRST-SEEN input takes the PLAIN fold
|
|
62
|
+
* (bit-identical to inference perception of a standalone query — that
|
|
63
|
+
* structural train/inference agreement is load-bearing for exact recall),
|
|
64
|
+
* computed incrementally via the fold's level pyramid
|
|
65
|
+
* ({@link bytesToTreePyramid}). An input that EXTENDS a previously
|
|
66
|
+
* deposited one is a conversation context grown by one turn — the cached
|
|
67
|
+
* prefix length IS the turn boundary (derived from the deposit sequence
|
|
68
|
+
* itself, never from content conventions) — and takes the STABLE-PREFIX
|
|
69
|
+
* fold over the accumulated boundaries, bit-identical to the boundary
|
|
70
|
+
* fold query-time conversation perception uses, so the trained context
|
|
71
|
+
* node and the query's context subtree are the SAME node. Segment folds
|
|
72
|
+
* reuse across deposits ({@link stablePrefixFoldIncremental}) — O(turn)
|
|
73
|
+
* instead of O(context) per turn. The fold state is purely a cache; the
|
|
74
|
+
* boundary accumulation is what an evicted chain loses (falling back to
|
|
75
|
+
* the plain fold, the pre-boundary shape — a warm replay restores it). */
|
|
76
|
+
export function perceiveDeposit(ctx, bytes, conversational = false) {
|
|
70
77
|
let prev;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
let prefixLen = 0;
|
|
79
|
+
// Cache consult (both boundary lookup and stable-prefix reuse) is scoped
|
|
80
|
+
// to conversational deposits only — a bare, unrelated fact whose bytes
|
|
81
|
+
// happen to extend an earlier deposit is NOT a conversation turn, and
|
|
82
|
+
// must keep the plain fold so it shares structure with ITS OWN prior
|
|
83
|
+
// deposits, not fragment against a coincidental byte-prefix.
|
|
84
|
+
if (conversational) {
|
|
85
|
+
// Longest cached PROPER prefix first.
|
|
86
|
+
const lens = [...ctx._depositLens]
|
|
87
|
+
.filter((L) => L >= 2 && L < bytes.length)
|
|
88
|
+
.sort((a, b) => b - a);
|
|
89
|
+
for (const L of lens) {
|
|
90
|
+
const hit = ctx._depositTrees.get(latin1Key(bytes.subarray(0, L)));
|
|
91
|
+
// The suffix must bytes-equal the hit's OWN recorded continuation —
|
|
92
|
+
// proof this deposit is that turn's actual next turn, not a fact
|
|
93
|
+
// that coincidentally shares its byte prefix.
|
|
94
|
+
if (hit !== undefined && hit.nextBytes !== undefined &&
|
|
95
|
+
bytesEqual(hit.nextBytes, bytes.subarray(L))) {
|
|
96
|
+
prev = hit;
|
|
97
|
+
prefixLen = L;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
80
100
|
}
|
|
81
101
|
}
|
|
82
|
-
|
|
83
|
-
|
|
102
|
+
let tree;
|
|
103
|
+
let entry;
|
|
104
|
+
if (prev !== undefined) {
|
|
105
|
+
const boundaries = [...prev.boundaries, prefixLen];
|
|
106
|
+
const folded = stablePrefixFoldIncremental(ctx.space, ctx.alphabet, bytes, boundaries, prev.stable);
|
|
107
|
+
tree = folded.tree;
|
|
108
|
+
entry = { boundaries, stable: folded.fold };
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const plain = bytesToTreePyramid(ctx.space, ctx.alphabet, bytes);
|
|
112
|
+
tree = plain.tree;
|
|
113
|
+
entry = { boundaries: [], pyramid: plain.pyramid };
|
|
114
|
+
}
|
|
115
|
+
// Only a conversational deposit writes the cache too — otherwise a bare
|
|
116
|
+
// fact's plain fold could later be misread as a conversation's turn-zero
|
|
117
|
+
// boundary by an unrelated conversational deposit that happens to extend
|
|
118
|
+
// its bytes.
|
|
119
|
+
if (conversational && bytes.length >= 2) {
|
|
84
120
|
// The lengths set drifts as the map evicts; past the probe budget the
|
|
85
121
|
// drift itself becomes the cost (each stale length is an O(len) key
|
|
86
122
|
// build), so both reset together — losing only warm-up on live chains.
|
|
@@ -88,7 +124,7 @@ export function perceiveDeposit(ctx, bytes) {
|
|
|
88
124
|
ctx._depositLens.clear();
|
|
89
125
|
ctx._depositTrees.clear();
|
|
90
126
|
}
|
|
91
|
-
ctx._depositTrees.set(latin1Key(bytes),
|
|
127
|
+
ctx._depositTrees.set(latin1Key(bytes), entry);
|
|
92
128
|
ctx._depositLens.add(bytes.length);
|
|
93
129
|
}
|
|
94
130
|
return tree;
|
|
@@ -12,4 +12,12 @@ export declare function reason(ctx: MindContext, query: Uint8Array, answer: Uint
|
|
|
12
12
|
* When the consensus climb finds more than one dominant point, each
|
|
13
13
|
* independent point grounds its own answer; they are bridged together
|
|
14
14
|
* by any learnt connector the graph holds between them. */
|
|
15
|
-
export declare function fuseAttention(ctx: MindContext, query: Uint8Array, primary: Uint8Array, pre: Precomputed
|
|
15
|
+
export declare function fuseAttention(ctx: MindContext, query: Uint8Array, primary: Uint8Array, pre: Precomputed,
|
|
16
|
+
/** True when `primary` never touched the consensus climb at all — e.g. a
|
|
17
|
+
* pure ALU computation, which has no anchor of its own. commitVotes
|
|
18
|
+
* ALWAYS admits the dominant root regardless of its vote (attention.ts:
|
|
19
|
+
* "roots.length === 0 || …") on the assumption a lone root already IS
|
|
20
|
+
* primary's own source; that assumption is exactly backwards when
|
|
21
|
+
* primary is unclimbed. Absent or false preserves the original
|
|
22
|
+
* behaviour exactly. */
|
|
23
|
+
unclimbed?: boolean): Promise<Uint8Array>;
|
|
@@ -111,7 +111,15 @@ export async function reason(ctx, query, answer, preConsumed, pre) {
|
|
|
111
111
|
* When the consensus climb finds more than one dominant point, each
|
|
112
112
|
* independent point grounds its own answer; they are bridged together
|
|
113
113
|
* by any learnt connector the graph holds between them. */
|
|
114
|
-
export async function fuseAttention(ctx, query, primary, pre
|
|
114
|
+
export async function fuseAttention(ctx, query, primary, pre,
|
|
115
|
+
/** True when `primary` never touched the consensus climb at all — e.g. a
|
|
116
|
+
* pure ALU computation, which has no anchor of its own. commitVotes
|
|
117
|
+
* ALWAYS admits the dominant root regardless of its vote (attention.ts:
|
|
118
|
+
* "roots.length === 0 || …") on the assumption a lone root already IS
|
|
119
|
+
* primary's own source; that assumption is exactly backwards when
|
|
120
|
+
* primary is unclimbed. Absent or false preserves the original
|
|
121
|
+
* behaviour exactly. */
|
|
122
|
+
unclimbed = false) {
|
|
115
123
|
// When the answer is structurally drawn from the query itself
|
|
116
124
|
// (extraction), it already spans all the query's pieces — fusion
|
|
117
125
|
// would only add noise from unrelated stored contexts. The gate is
|
|
@@ -125,17 +133,31 @@ export async function fuseAttention(ctx, query, primary, pre) {
|
|
|
125
133
|
// query, same k, same DF mode) — read them from Precomputed instead of
|
|
126
134
|
// re-climbing, so even a traced response pays for the climb once.
|
|
127
135
|
const forest = (await pre.attention()).roots;
|
|
128
|
-
|
|
136
|
+
// A LONE root is ordinarily primary's own source — nothing to fuse. But
|
|
137
|
+
// when primary is unclimbed, the lone root was never checked against
|
|
138
|
+
// anything: it is admitted by commitVotes unconditionally, so it may be
|
|
139
|
+
// genuine consensus (Attention.breadth dominates — most of the query's
|
|
140
|
+
// OWN regions corroborate it) or a coincidental echo (breadth does not
|
|
141
|
+
// dominate — see test/35-attention-confidence). breadth is the SCALE-
|
|
142
|
+
// INVARIANT read of exactly this question: the raw IDF vote cannot serve
|
|
143
|
+
// here, since it is an absolute ln(N)-scaled quantity (a genuine root on
|
|
144
|
+
// a large store can score BELOW its own floor while a coincidental echo
|
|
145
|
+
// on a small one scores comfortably above its own, smaller, floor).
|
|
146
|
+
const lonePromotes = unclimbed && forest.length === 1 &&
|
|
147
|
+
forest[0].breadth > 0.5;
|
|
148
|
+
if (forest.length === 0 || (forest.length <= 1 && !lonePromotes)) {
|
|
129
149
|
return primary;
|
|
150
|
+
}
|
|
130
151
|
const pieces = [
|
|
131
152
|
{ start: forest[0].start, bytes: primary },
|
|
132
153
|
];
|
|
133
154
|
const qv = pre.guide; // once, not per root
|
|
155
|
+
const rest = lonePromotes ? forest : forest.slice(1);
|
|
134
156
|
const t = ctx.trace?.enter("fuseAttention", [
|
|
135
157
|
rItem(primary, "primary"),
|
|
136
|
-
...
|
|
158
|
+
...rest.map((r) => rNode(ctx, r.anchor, "point", r.vote)),
|
|
137
159
|
]);
|
|
138
|
-
for (const root of
|
|
160
|
+
for (const root of rest) {
|
|
139
161
|
const g = await project(ctx, root.anchor, qv);
|
|
140
162
|
if (g === null || g.length === 0)
|
|
141
163
|
continue;
|