@hviana/sema 0.4.1 → 0.4.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/ingest-cache.js +4 -0
- package/dist/src/meter.d.ts +5 -0
- package/dist/src/meter.js +5 -0
- package/dist/src/mind/attention.js +19 -2
- package/dist/src/mind/bridge.js +265 -56
- package/dist/src/mind/junction.d.ts +7 -4
- package/dist/src/mind/junction.js +16 -5
- package/dist/src/mind/match.d.ts +15 -0
- package/dist/src/mind/match.js +92 -2
- package/dist/src/mind/mechanisms/cast.js +12 -1
- package/dist/src/mind/mechanisms/confluence.js +31 -1
- package/dist/src/mind/mechanisms/cover.d.ts +1 -1
- package/dist/src/mind/mechanisms/cover.js +29 -5
- package/dist/src/mind/mechanisms/recall.js +17 -41
- package/dist/src/mind/mind.d.ts +7 -0
- package/dist/src/mind/mind.js +25 -2
- package/dist/src/mind/pipeline-mechanism.d.ts +2 -2
- package/dist/src/mind/pipeline-mechanism.js +87 -4
- package/dist/src/mind/pipeline.js +1 -1
- package/dist/src/mind/reasoning.js +19 -11
- package/dist/src/mind/recognition.js +41 -0
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/traverse.d.ts +3 -1
- package/dist/src/mind/traverse.js +14 -13
- package/dist/src/mind/types.d.ts +10 -0
- package/package.json +1 -1
- package/src/ingest-cache.ts +4 -0
- package/src/meter.ts +5 -0
- package/src/mind/attention.ts +18 -1
- package/src/mind/bridge.ts +292 -54
- package/src/mind/junction.ts +21 -7
- package/src/mind/match.ts +92 -1
- package/src/mind/mechanisms/cast.ts +12 -0
- package/src/mind/mechanisms/confluence.ts +30 -1
- package/src/mind/mechanisms/cover.ts +36 -4
- package/src/mind/mechanisms/recall.ts +21 -44
- package/src/mind/mind.ts +39 -2
- package/src/mind/pipeline-mechanism.ts +86 -4
- package/src/mind/pipeline.ts +1 -1
- package/src/mind/reasoning.ts +15 -8
- package/src/mind/recognition.ts +40 -0
- package/src/mind/resonance.ts +0 -0
- package/src/mind/traverse.ts +17 -15
- package/src/mind/types.ts +10 -0
- package/test/49-natural-units-synonym-bridge.test.mjs +56 -15
|
@@ -53,15 +53,24 @@ export function junctionSeeds(ctx, b) {
|
|
|
53
53
|
return [wids[0], wids[wids.length - 1]];
|
|
54
54
|
}
|
|
55
55
|
const walkCaches = new WeakMap();
|
|
56
|
+
const WALK_CACHE_MAX = 100_000;
|
|
56
57
|
export function walkCache(ctx) {
|
|
57
58
|
if (ctx.climbMemo === null)
|
|
58
59
|
return null;
|
|
59
|
-
let c = walkCaches.get(ctx.
|
|
60
|
+
let c = walkCaches.get(ctx._structMemoKey);
|
|
60
61
|
if (c === undefined) {
|
|
61
|
-
walkCaches.set(ctx.
|
|
62
|
+
walkCaches.set(ctx._structMemoKey, c = { reads: new Map(), parents: new Map(), containers: new Map() });
|
|
63
|
+
}
|
|
64
|
+
else if (c.reads.size + c.parents.size + c.containers.size >= WALK_CACHE_MAX) {
|
|
65
|
+
c.reads.clear();
|
|
66
|
+
c.parents.clear();
|
|
67
|
+
c.containers.clear();
|
|
62
68
|
}
|
|
63
69
|
return c;
|
|
64
70
|
}
|
|
71
|
+
export function invalidateJunctionCache(ctx) {
|
|
72
|
+
walkCaches.delete(ctx._structMemoKey);
|
|
73
|
+
}
|
|
65
74
|
export function cachedRead(ctx, cache, id, cap) {
|
|
66
75
|
if (cache === null)
|
|
67
76
|
return read(ctx, id, cap + 1);
|
|
@@ -253,13 +262,14 @@ export function junctionContainers(ctx, left, right, maxContainer, unordered = f
|
|
|
253
262
|
* cost is bounded at √N·W pops total regardless of how many siblings are
|
|
254
263
|
* tried. A sibling whose bytes exceed `maxInterior` is skipped (it
|
|
255
264
|
* cannot be junction-sized). */
|
|
256
|
-
export async function junctionSynonyms(ctx, left, right, maxInterior, unordered = false, sides) {
|
|
265
|
+
export async function junctionSynonyms(ctx, left, right, maxInterior, unordered = false, sides, sharedBudget) {
|
|
257
266
|
const s = sides ?? await loadJunctionSynonymSides(ctx, left, right);
|
|
258
267
|
if (s.leftId === null && s.rightId === null)
|
|
259
268
|
return [];
|
|
260
269
|
// ── Tier 2.5a: single-synonym — one side replaced by a halo sibling ──────
|
|
261
270
|
// ONE shared expansion budget across BOTH directions of this tier.
|
|
262
|
-
const singleBudget =
|
|
271
|
+
const singleBudget = sharedBudget ??
|
|
272
|
+
{ n: hubBound(ctx) * ctx.space.maxGroup };
|
|
263
273
|
const singleOut = new Map();
|
|
264
274
|
const keepBest = (map, j, tier, confidence) => {
|
|
265
275
|
const prev = map.get(j.id);
|
|
@@ -313,7 +323,8 @@ export async function junctionSynonyms(ctx, left, right, maxInterior, unordered
|
|
|
313
323
|
a.l.id - b.l.id ||
|
|
314
324
|
a.r.id - b.r.id);
|
|
315
325
|
const doubleOut = new Map();
|
|
316
|
-
const budget =
|
|
326
|
+
const budget = sharedBudget ??
|
|
327
|
+
{ n: hubBound(ctx) * ctx.space.maxGroup };
|
|
317
328
|
const tries = Math.min(pairs.length, ctx.cfg.haloQueryK);
|
|
318
329
|
for (let i = 0; i < tries; i++) {
|
|
319
330
|
const { l, r, confidence } = pairs[i];
|
package/dist/src/mind/match.d.ts
CHANGED
|
@@ -67,6 +67,21 @@ export declare function bestHaloMate<T>(ctx: MindContext, halo: Vec, items: Iter
|
|
|
67
67
|
score: number;
|
|
68
68
|
} | null;
|
|
69
69
|
export declare function haloSiblings(ctx: MindContext, id: number, halo?: Vec | null, bar?: number): Promise<Hit[]>;
|
|
70
|
+
/** Bundle the distributional company of every addressable W-window in a
|
|
71
|
+
* byte span. This is the query-time counterpart of the write-side halo
|
|
72
|
+
* pours: no lexical unit or storage row is invented; the span is represented
|
|
73
|
+
* by VSA superposition of the window concepts the store already knows.
|
|
74
|
+
*
|
|
75
|
+
* Components are normalized before bundling so repetition mass remains
|
|
76
|
+
* evidence about each stored node, not an accidental weight on one window
|
|
77
|
+
* inside the composed phrase. Returns null when the corpus provides no
|
|
78
|
+
* distributional evidence for the span. */
|
|
79
|
+
export declare function spanHalo(ctx: MindContext, bytes: Uint8Array, from?: number, to?: number): Vec | null;
|
|
80
|
+
/** Distributional synonym evidence between arbitrary byte spans. Whole words
|
|
81
|
+
* need not be independently interned: their stored W-window occurrences are
|
|
82
|
+
* lifted to episode halos, bundled, and compared. The caller chooses the
|
|
83
|
+
* derived gate appropriate to its claim (concept identity or analogy). */
|
|
84
|
+
export declare function spanSynonymStrength(ctx: MindContext, a: Uint8Array, b: Uint8Array): number;
|
|
70
85
|
/** The DISTRIBUTIONAL matcher between two nodes: mutual-nearest-neighbour
|
|
71
86
|
* strength, not a pick. Returns the direct halo cosine, or failing that the
|
|
72
87
|
* highest mutual-halo-sibling min-score (second-order analogy), or failing
|
package/dist/src/mind/match.js
CHANGED
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
// the PROJECTIONS (follow, conceptHop, reverseContext, project) — so each
|
|
25
25
|
// mechanism file states only its configuration, never its own copy of the
|
|
26
26
|
// machinery. The gates all live in geometry.ts (derived, never tuned).
|
|
27
|
-
import { cosine } from "../vec.js";
|
|
27
|
+
import { addInto, cosine, dot, normalize, zeros } from "../vec.js";
|
|
28
28
|
import { conceptThreshold, identityBar, significanceBar } from "../geometry.js";
|
|
29
29
|
import { indexOf } from "../bytes.js";
|
|
30
30
|
import { leafIdRun } from "./canonical.js";
|
|
31
31
|
import { foldTree, gistOf, perceive, read, resolve } from "./primitives.js";
|
|
32
|
-
import { argmaxCosine, chooseAmong, chooseNext, guidedFirst, hubBound, hubCap, } from "./traverse.js";
|
|
32
|
+
import { argmaxCosine, chooseAmong, chooseNext, corpusN, edgeAncestors, guidedFirst, hubBound, hubCap, sharedReachMemo, } from "./traverse.js";
|
|
33
33
|
import { recognise, segment } from "./recognition.js";
|
|
34
34
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
35
35
|
// MATCHERS — locating learned structure in/against bytes, by graded strictness
|
|
@@ -276,6 +276,96 @@ export async function haloSiblings(ctx, id, halo, bar = conceptThreshold(ctx.sto
|
|
|
276
276
|
memo.set(id, out);
|
|
277
277
|
return out;
|
|
278
278
|
}
|
|
279
|
+
/** Bundle the distributional company of every addressable W-window in a
|
|
280
|
+
* byte span. This is the query-time counterpart of the write-side halo
|
|
281
|
+
* pours: no lexical unit or storage row is invented; the span is represented
|
|
282
|
+
* by VSA superposition of the window concepts the store already knows.
|
|
283
|
+
*
|
|
284
|
+
* Components are normalized before bundling so repetition mass remains
|
|
285
|
+
* evidence about each stored node, not an accidental weight on one window
|
|
286
|
+
* inside the composed phrase. Returns null when the corpus provides no
|
|
287
|
+
* distributional evidence for the span. */
|
|
288
|
+
export function spanHalo(ctx, bytes, from = 0, to = bytes.length) {
|
|
289
|
+
const W = ctx.space.maxGroup;
|
|
290
|
+
if (to - from < W)
|
|
291
|
+
return null;
|
|
292
|
+
if (ctx.meter)
|
|
293
|
+
ctx.meter.spanHalos++;
|
|
294
|
+
const out = zeros(ctx.store.D);
|
|
295
|
+
let found = false;
|
|
296
|
+
const added = new Set();
|
|
297
|
+
const episodeRoots = [];
|
|
298
|
+
const N = corpusN(ctx);
|
|
299
|
+
const reachMemo = sharedReachMemo(ctx);
|
|
300
|
+
const addHalo = (id) => {
|
|
301
|
+
if (added.has(id))
|
|
302
|
+
return;
|
|
303
|
+
const halo = ctx.store.halo(id);
|
|
304
|
+
if (halo === null)
|
|
305
|
+
return;
|
|
306
|
+
const norm = Math.sqrt(dot(halo, halo));
|
|
307
|
+
if (norm === 0)
|
|
308
|
+
return;
|
|
309
|
+
added.add(id);
|
|
310
|
+
addInto(out, halo, 1 / norm);
|
|
311
|
+
found = true;
|
|
312
|
+
};
|
|
313
|
+
const windowCount = to - from - W + 1;
|
|
314
|
+
const offsets = [];
|
|
315
|
+
const samples = Math.min(W, windowCount);
|
|
316
|
+
for (let i = 0; i < samples; i++) {
|
|
317
|
+
const relative = samples === 1
|
|
318
|
+
? 0
|
|
319
|
+
: Math.floor((i * (windowCount - 1)) / (samples - 1));
|
|
320
|
+
const off = from + relative;
|
|
321
|
+
if (offsets[offsets.length - 1] !== off)
|
|
322
|
+
offsets.push(off);
|
|
323
|
+
}
|
|
324
|
+
for (const off of offsets) {
|
|
325
|
+
if (ctx.meter)
|
|
326
|
+
ctx.meter.spanHaloWindows++;
|
|
327
|
+
const ids = leafIdRun(ctx, bytes, off, off + W);
|
|
328
|
+
if (ids === null)
|
|
329
|
+
continue;
|
|
330
|
+
const id = ctx.store.findBranch(ids);
|
|
331
|
+
if (id === null)
|
|
332
|
+
continue;
|
|
333
|
+
addHalo(id);
|
|
334
|
+
// Canonical flat windows are retrieval addresses and normally carry no
|
|
335
|
+
// halo themselves. Their bounded structural ascent reaches the learned
|
|
336
|
+
// episode forms that contain them; bundling those forms' company is the
|
|
337
|
+
// distributional meaning of the window, derived entirely from existing
|
|
338
|
+
// containment and halo state.
|
|
339
|
+
if (!added.has(id)) {
|
|
340
|
+
episodeRoots.push(edgeAncestors(ctx, id, N, reachMemo).roots);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
for (let rank = 0; added.size < ctx.cfg.haloQueryK; rank++) {
|
|
344
|
+
let any = false;
|
|
345
|
+
for (const roots of episodeRoots) {
|
|
346
|
+
if (rank >= roots.length)
|
|
347
|
+
continue;
|
|
348
|
+
any = true;
|
|
349
|
+
addHalo(roots[rank]);
|
|
350
|
+
if (added.size >= ctx.cfg.haloQueryK)
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
if (!any)
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
return found ? normalize(out) : null;
|
|
357
|
+
}
|
|
358
|
+
/** Distributional synonym evidence between arbitrary byte spans. Whole words
|
|
359
|
+
* need not be independently interned: their stored W-window occurrences are
|
|
360
|
+
* lifted to episode halos, bundled, and compared. The caller chooses the
|
|
361
|
+
* derived gate appropriate to its claim (concept identity or analogy). */
|
|
362
|
+
export function spanSynonymStrength(ctx, a, b) {
|
|
363
|
+
const ah = spanHalo(ctx, a);
|
|
364
|
+
const bh = spanHalo(ctx, b);
|
|
365
|
+
if (ah === null || bh === null)
|
|
366
|
+
return 0;
|
|
367
|
+
return cosine(ah, bh);
|
|
368
|
+
}
|
|
279
369
|
export async function analogyStrength(ctx, a, b) {
|
|
280
370
|
const ha = ctx.store.halo(a);
|
|
281
371
|
const hb = ctx.store.halo(b);
|
|
@@ -221,7 +221,18 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
221
221
|
...roots.map((r) => rNode(ctx, r.anchor, "committed-root")),
|
|
222
222
|
], `${points.length} aligned structure(s), but none is one of the climb's ` +
|
|
223
223
|
`${roots.length} committed root(s) — CAST refuses to transfer through ` +
|
|
224
|
-
`content the climb itself never settled on
|
|
224
|
+
`content the climb itself never settled on`, {
|
|
225
|
+
aligned: points.map((p) => ({
|
|
226
|
+
anchor: p.anchor,
|
|
227
|
+
vote: p.vote,
|
|
228
|
+
runs: p.runs.map((r) => ({ ...r })),
|
|
229
|
+
coveredBytes: p.runs.reduce((n, r) => n + r.qe - r.qs, 0),
|
|
230
|
+
})),
|
|
231
|
+
committedRoots: roots.map((r) => ({
|
|
232
|
+
anchor: r.anchor,
|
|
233
|
+
vote: r.vote,
|
|
234
|
+
})),
|
|
235
|
+
});
|
|
225
236
|
return [];
|
|
226
237
|
}
|
|
227
238
|
const woven = points.some((p) => p.runs.some((r) => !pre.rec.sites.some((s) => r.qs >= s.start && r.qe <= s.end)));
|
|
@@ -68,7 +68,23 @@ export async function confluenceJoin(ctx, query, pre) {
|
|
|
68
68
|
// with the query is the CONSTRAINT being re-named (or its scaffolding),
|
|
69
69
|
// never the open seat the question asks for — subtracted by identity,
|
|
70
70
|
// below.
|
|
71
|
-
|
|
71
|
+
// Completed assistant turns are context the engine already produced, not
|
|
72
|
+
// independent constraints asserted by the asker. Treating their windows as
|
|
73
|
+
// fresh constraints makes a cumulative dialogue's confluence work grow with
|
|
74
|
+
// every prior answer and can join the engine's own prose back into a reply.
|
|
75
|
+
// Recognition and attention still see the full transcript; only this
|
|
76
|
+
// mechanism's constraint population excludes answered spans.
|
|
77
|
+
const queryWin = new Map();
|
|
78
|
+
let answered = 0;
|
|
79
|
+
for (const [off, id] of pre.queryWindows) {
|
|
80
|
+
while (answered < ctx.answeredSpans.length &&
|
|
81
|
+
ctx.answeredSpans[answered][1] <= off)
|
|
82
|
+
answered++;
|
|
83
|
+
const span = ctx.answeredSpans[answered];
|
|
84
|
+
if (span && span[0] <= off && off + W <= span[1])
|
|
85
|
+
continue;
|
|
86
|
+
queryWin.set(off, id);
|
|
87
|
+
}
|
|
72
88
|
const queryIds = new Set(queryWin.values());
|
|
73
89
|
// A constraint must bind a CONSTITUENT, not a shard. A genuinely shared
|
|
74
90
|
// form weaves a contiguous RUN of shared discriminative windows — its
|
|
@@ -84,6 +100,17 @@ export async function confluenceJoin(ctx, query, pre) {
|
|
|
84
100
|
const bindsAConstituent = (cover) => cover.some(([cs, ce]) => ce - cs >= 2 * W);
|
|
85
101
|
const streams = [];
|
|
86
102
|
const rankedCapped = ranked.length > pre.k ? ranked.slice(0, pre.k) : ranked;
|
|
103
|
+
// CONJUNCTIVITY EARLY-EXIT: a conjunctive query's top-ranked anchors
|
|
104
|
+
// (largest vote weight) must already form at least two independent
|
|
105
|
+
// constraint streams. When the first W anchors yield fewer than 2, the
|
|
106
|
+
// query has at most one topic — no join to compute. The full pre.k scan
|
|
107
|
+
// would produce the same null result after reading every anchor's bytes
|
|
108
|
+
// and computing window identities (profiled at 18K–50K leaf lookups per
|
|
109
|
+
// refusing query), so cutting the scan short here saves 50–70% of
|
|
110
|
+
// confluence cost on non-conjunctive queries while preserving every
|
|
111
|
+
// genuinely conjunctive case (whose top anchors ARE its constraints).
|
|
112
|
+
const earlyExit = Math.min(rankedCapped.length, ctx.space.maxGroup * 2);
|
|
113
|
+
let exitAfter = earlyExit;
|
|
87
114
|
for (const cand of rankedCapped) {
|
|
88
115
|
if (streams.some((s) => s.anchor === cand.anchor))
|
|
89
116
|
continue;
|
|
@@ -111,6 +138,9 @@ export async function confluenceJoin(ctx, query, pre) {
|
|
|
111
138
|
if (cover.length > 0 && bindsAConstituent(cover)) {
|
|
112
139
|
streams.push({ anchor: cand.anchor, vote: cand.vote, ids, cover, held });
|
|
113
140
|
}
|
|
141
|
+
// Early-exit: after 2W anchors, a non-conjunctive query is decided.
|
|
142
|
+
if (--exitAfter <= 0 && streams.length < 2)
|
|
143
|
+
return null;
|
|
114
144
|
}
|
|
115
145
|
if (streams.length < 2)
|
|
116
146
|
return null;
|
|
@@ -2,5 +2,5 @@ import type { MindContext } from "../types.js";
|
|
|
2
2
|
import type { Site } from "../graph-search.js";
|
|
3
3
|
import type { PipelineMechanism } from "../pipeline-mechanism.js";
|
|
4
4
|
export declare function resolveConcepts(ctx: MindContext, sites: Site[]): Promise<Map<number, number>>;
|
|
5
|
-
export declare function resolveConnectors(ctx: MindContext, sites: ReadonlyArray<Site
|
|
5
|
+
export declare function resolveConnectors(ctx: MindContext, sites: ReadonlyArray<Site>, query?: Uint8Array): Promise<Map<string, Uint8Array>>;
|
|
6
6
|
export declare const coverMechanism: PipelineMechanism;
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
// near-zero-cost incumbent that prunes the other mechanisms through the
|
|
10
10
|
// ordinary admissible-floor check, with no extension special-case anywhere.
|
|
11
11
|
import { read, resolve } from "../primitives.js";
|
|
12
|
-
import { guidedFirst } from "../traverse.js";
|
|
12
|
+
import { guidedFirst, hubBound } from "../traverse.js";
|
|
13
13
|
import { conceptHop } from "../match.js";
|
|
14
14
|
import { bridge } from "../resonance.js";
|
|
15
15
|
import { liftAnswer, segRestatesQuery } from "../types.js";
|
|
16
16
|
import { decodeText, unexplainedLabel } from "../rationale.js";
|
|
17
|
+
import { indexOf } from "../../bytes.js";
|
|
17
18
|
import { rItem, rNode, traceDerivation } from "../trace.js";
|
|
18
19
|
// ── Concept / connector pre-resolution ──────────────────────────────────────
|
|
19
20
|
export async function resolveConcepts(ctx, sites) {
|
|
@@ -34,10 +35,29 @@ export async function resolveConcepts(ctx, sites) {
|
|
|
34
35
|
}
|
|
35
36
|
return target;
|
|
36
37
|
}
|
|
37
|
-
export async function resolveConnectors(ctx, sites) {
|
|
38
|
+
export async function resolveConnectors(ctx, sites, query) {
|
|
38
39
|
const links = new Map();
|
|
39
|
-
const ordered = [...sites].sort((a, b) => a.start - b.start);
|
|
40
40
|
const answerOf = (n) => guidedFirst(ctx, n) ?? n;
|
|
41
|
+
// A site's continuation already present elsewhere in the query is stale
|
|
42
|
+
// transcript evidence: cover still needs the site for structural context,
|
|
43
|
+
// but liftAnswer will trim that continuation as already answered. Building
|
|
44
|
+
// pairwise/n-ary bridges for it can only create connectors that are later
|
|
45
|
+
// discarded, and on cumulative dialogue that dominated the whole search.
|
|
46
|
+
let answered = 0;
|
|
47
|
+
const ordered = [...sites]
|
|
48
|
+
.sort((a, b) => a.start - b.start)
|
|
49
|
+
.filter((s) => {
|
|
50
|
+
while (answered < ctx.answeredSpans.length &&
|
|
51
|
+
ctx.answeredSpans[answered][1] <= s.start)
|
|
52
|
+
answered++;
|
|
53
|
+
const span = ctx.answeredSpans[answered];
|
|
54
|
+
if (span && span[0] <= s.start && s.end <= span[1])
|
|
55
|
+
return false;
|
|
56
|
+
if (query === undefined || ctx.answeredSpans.length === 0)
|
|
57
|
+
return true;
|
|
58
|
+
const continuations = ctx.store.nextFirst(s.payload, hubBound(ctx));
|
|
59
|
+
return !continuations.some((answer) => indexOf(query, read(ctx, answer), 0) >= 0);
|
|
60
|
+
});
|
|
41
61
|
const bridgePair = async (l, r) => {
|
|
42
62
|
if (l === r || links.has(l + "," + r))
|
|
43
63
|
return;
|
|
@@ -119,7 +139,9 @@ export const coverMechanism = {
|
|
|
119
139
|
}
|
|
120
140
|
if (sites.length === 0 && computed.length === 0)
|
|
121
141
|
return [];
|
|
122
|
-
const connectors =
|
|
142
|
+
const connectors = ctx.meter
|
|
143
|
+
? await ctx.meter.time("cover.resolveConnectors", () => resolveConnectors(ctx, sites, query))
|
|
144
|
+
: await resolveConnectors(ctx, sites, query);
|
|
123
145
|
let splits = rec.splits;
|
|
124
146
|
let starts = rec.starts;
|
|
125
147
|
if (computed.length > 0) {
|
|
@@ -135,7 +157,9 @@ export const coverMechanism = {
|
|
|
135
157
|
starts.add(u.j);
|
|
136
158
|
}
|
|
137
159
|
}
|
|
138
|
-
const concepts =
|
|
160
|
+
const concepts = ctx.meter
|
|
161
|
+
? await ctx.meter.time("cover.resolveConcepts", () => resolveConcepts(ctx, sites))
|
|
162
|
+
: await resolveConcepts(ctx, sites);
|
|
139
163
|
const coverDeps = [
|
|
140
164
|
ctx.trace?.lastIndex("recognise"),
|
|
141
165
|
ctx.trace?.lastIndex("computeExtensions"),
|
|
@@ -228,48 +228,24 @@ export async function recallByResonance(ctx, query, pre) {
|
|
|
228
228
|
// refusing on the reach bar). Approximate scores propose; the bridge's
|
|
229
229
|
// byte-exact alignment and attestation gates decide.
|
|
230
230
|
//
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
// search EXHAUSTIVELY. Both matter: the IVF only ever probes
|
|
236
|
-
// ⌈√clusters⌉ of them (store.ts's efFor) REGARDLESS of k — widening k
|
|
237
|
-
// alone just returns more hits from the SAME already-probed clusters,
|
|
238
|
-
// never a hit whose vector lives in an unprobed one. Measured live:
|
|
239
|
-
// "What is the chemical symbol for water?" needs "What is the
|
|
240
|
-
// chemical formula for water?", scoring only 0.58 against the
|
|
241
|
-
// query's gist (a MIDDLE-of-string word swap perturbs the river-fold
|
|
242
|
-
// tree hash far more than a same-length TAIL swap like the "carbon"/
|
|
243
|
-
// "oxygen" neighbours that outrank it at 0.87+) — absent from the
|
|
244
|
-
// resonance list even at k=5000, present and byte-exact-verified the
|
|
245
|
-
// moment it's force-fed to the bridge directly. `exhaustive` is the
|
|
246
|
-
// natural, tuning-free ceiling (probe every cluster) for a call that
|
|
247
|
-
// is ALREADY refusal-path-only and must not miss a candidate hiding
|
|
248
|
-
// behind an unlucky structural distance.
|
|
249
|
-
//
|
|
250
|
-
// MEASURED COST, AND WHY IT STAYS (17.9M vectors / 325K contexts):
|
|
251
|
-
// this one call is ~570 ms and ~45% of all inference time on a refusing
|
|
252
|
-
// query. The cost is entirely `exhaustive` (nprobe = every cluster),
|
|
253
|
-
// NOT the widened k — timed on that store: k=571 exhaustive 632 ms,
|
|
254
|
-
// k=24 exhaustive 536 ms, k=571 NON-exhaustive 12 ms. So narrowing k
|
|
255
|
-
// buys nothing and the 50x is the whole-index scan itself.
|
|
256
|
-
// It is load-bearing: over an 18-query battery the bridge produced a
|
|
257
|
-
// winner 4 times, and ALL FOUR winners came from this proposal channel
|
|
258
|
-
// — the anchor-climb channel won nothing on its own. Reordering the
|
|
259
|
-
// channels (climb first, resonate only on failure) would therefore pay
|
|
260
|
-
// the climb, fail, and pay this anyway. Do not weaken it without
|
|
261
|
-
// re-running that measurement.
|
|
262
|
-
// Handed to the bridge as a THUNK: this exhaustive whole-index probe is
|
|
263
|
-
// the most expensive single act on the refusal path, and the bridge's
|
|
264
|
-
// own cheap gates (query length, the O(|query|) stored-window anchor
|
|
265
|
-
// scan) can refuse without any proposal at all. See substitutionBridge.
|
|
231
|
+
// Reuse recall's already-ranked proposals. Never scan every IVF cluster:
|
|
232
|
+
// exact co-occurrence and bounded anchor ascent are the bridge's structural
|
|
233
|
+
// proposal channels, while an exhaustive ANN call made every honest
|
|
234
|
+
// refusal cost hundreds of milliseconds regardless of k.
|
|
266
235
|
const wideIds = async () => {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
236
|
+
// When the top resonance hit is below the concept threshold, the query
|
|
237
|
+
// gist has no concept-level match to any stored form — an exhaustive √N
|
|
238
|
+
// ANN would only score more vectors below the bar (profiled at 38K–40K
|
|
239
|
+
// annVectorReads per refusing query on a 325K-context store). The
|
|
240
|
+
// bridge's structural channels (junction walks, anchor climbs) are the
|
|
241
|
+
// correct proposal source for a query whose gist has no clean match;
|
|
242
|
+
// the ANN cannot propose what the gist cannot rank.
|
|
243
|
+
const marketScale = k * ctx.space.maxGroup;
|
|
244
|
+
if (corpusN(ctx) <= marketScale ** 3) {
|
|
245
|
+
const exhaustive = await ctx.store.resonate(queryGist, hubBound(ctx), true);
|
|
246
|
+
return exhaustive.map((h) => h.id);
|
|
247
|
+
}
|
|
248
|
+
return whole.map((h) => h.id);
|
|
273
249
|
};
|
|
274
250
|
const bridged = await substitutionBridge(ctx, query, wideIds);
|
|
275
251
|
if (bridged !== null) {
|
package/dist/src/mind/mind.d.ts
CHANGED
|
@@ -34,6 +34,10 @@ export interface ConversationState {
|
|
|
34
34
|
* is `boundaries[0]`; the second turn starts at that offset, and so
|
|
35
35
|
* on. Empty for a single-turn or new conversation. */
|
|
36
36
|
boundaries: number[];
|
|
37
|
+
/** Byte spans occupied by replies produced by this Mind. Unlike boundary
|
|
38
|
+
* parity, this remains exact when a turn receives an empty reply. Optional
|
|
39
|
+
* so states saved before the field existed remain restorable. */
|
|
40
|
+
answeredSpans?: Array<[number, number]>;
|
|
37
41
|
}
|
|
38
42
|
/** An active conversation handle. Opaque — interact through the Mind's
|
|
39
43
|
* conversation methods ({@link Mind.beginConversation},
|
|
@@ -111,6 +115,7 @@ export declare class Mind implements MindContext {
|
|
|
111
115
|
lastCost: CostReport | null;
|
|
112
116
|
/** Memo of the consensus climb — content-keyed. See {@link MindContext.climbMemo}. */
|
|
113
117
|
climbMemo: Map<string, Map<string, AttentionRead>> | null;
|
|
118
|
+
_structMemoKey: object;
|
|
114
119
|
/** Memo of recognise() — content-keyed. See {@link MindContext.recogniseMemo}. */
|
|
115
120
|
recogniseMemo: Map<string, Recognition> | null;
|
|
116
121
|
/** Memo of perceive() — content-keyed. See {@link MindContext.perceiveMemo}. */
|
|
@@ -120,6 +125,8 @@ export declare class Mind implements MindContext {
|
|
|
120
125
|
id: number;
|
|
121
126
|
len: number;
|
|
122
127
|
}> | null;
|
|
128
|
+
answeredSpans: ReadonlyArray<readonly [number, number]>;
|
|
129
|
+
currentTurnStart: number;
|
|
123
130
|
/** The perceived gist of the query currently being answered. Set by `think`
|
|
124
131
|
* before the graph search runs; `chooseNext` consults it as a gate (a null
|
|
125
132
|
* guide means no query is in flight, so structural walkers keep plain
|
package/dist/src/mind/mind.js
CHANGED
|
@@ -20,7 +20,8 @@ import { GraphSearch, } from "./graph-search.js";
|
|
|
20
20
|
import { Alu } from "../alu/src/index.js";
|
|
21
21
|
import { decodeText, Rationale, } from "./rationale.js";
|
|
22
22
|
import { gistOf, inputBytes, latin1Key, perceive as perceiveImpl, resolve as resolveImpl, } from "./primitives.js";
|
|
23
|
-
import { chooseNext, edgeAncestors as edgeAncestorsFn } from "./traverse.js";
|
|
23
|
+
import { chooseNext, edgeAncestors as edgeAncestorsFn, invalidateStructuralCaches, } from "./traverse.js";
|
|
24
|
+
import { invalidateJunctionCache } from "./junction.js";
|
|
24
25
|
import { follow } from "./match.js";
|
|
25
26
|
import { recognise, segment } from "./recognition.js";
|
|
26
27
|
import { meaningOf } from "./resonance.js";
|
|
@@ -71,12 +72,15 @@ export class Mind {
|
|
|
71
72
|
lastCost = null;
|
|
72
73
|
/** Memo of the consensus climb — content-keyed. See {@link MindContext.climbMemo}. */
|
|
73
74
|
climbMemo = null;
|
|
75
|
+
_structMemoKey = {};
|
|
74
76
|
/** Memo of recognise() — content-keyed. See {@link MindContext.recogniseMemo}. */
|
|
75
77
|
recogniseMemo = null;
|
|
76
78
|
/** Memo of perceive() — content-keyed. See {@link MindContext.perceiveMemo}. */
|
|
77
79
|
perceiveMemo = null;
|
|
78
80
|
/** Subtree-resolution cache. See {@link MindContext._resolvedSubtrees}. */
|
|
79
81
|
_resolvedSubtrees = null;
|
|
82
|
+
answeredSpans = [];
|
|
83
|
+
currentTurnStart = 0;
|
|
80
84
|
/** The perceived gist of the query currently being answered. Set by `think`
|
|
81
85
|
* before the graph search runs; `chooseNext` consults it as a gate (a null
|
|
82
86
|
* guide means no query is in flight, so structural walkers keep plain
|
|
@@ -212,6 +216,11 @@ export class Mind {
|
|
|
212
216
|
this.recogniseMemo = conv ? conv.recogniseMemo : new Map();
|
|
213
217
|
this.perceiveMemo = conv ? conv.perceiveMemo : new Map();
|
|
214
218
|
this._resolvedSubtrees = conv ? conv.resolvedSubtrees : null;
|
|
219
|
+
// Inference is a pure function of cumulative bytes. Conversation
|
|
220
|
+
// boundaries remain persistence/API metadata and must not select a
|
|
221
|
+
// different mechanism path than respond() on the identical byte stream.
|
|
222
|
+
this.answeredSpans = [];
|
|
223
|
+
this.currentTurnStart = 0;
|
|
215
224
|
this.canon = canon ?? null;
|
|
216
225
|
this.canonMemo = canon ? new Map() : null;
|
|
217
226
|
this._beginMeter();
|
|
@@ -259,6 +268,8 @@ export class Mind {
|
|
|
259
268
|
this.recogniseMemo = null;
|
|
260
269
|
this.perceiveMemo = null;
|
|
261
270
|
this._resolvedSubtrees = null;
|
|
271
|
+
this.answeredSpans = [];
|
|
272
|
+
this.currentTurnStart = 0;
|
|
262
273
|
this.canon = null;
|
|
263
274
|
this.canonMemo = null;
|
|
264
275
|
this._edgeGuide = null;
|
|
@@ -335,11 +346,17 @@ export class Mind {
|
|
|
335
346
|
const id = this._nextConvId++;
|
|
336
347
|
const initBytes = state?.context ?? new Uint8Array(0);
|
|
337
348
|
const initBoundaries = state?.boundaries ? [...state.boundaries] : [];
|
|
349
|
+
const initAnswered = state?.answeredSpans
|
|
350
|
+
? state.answeredSpans.map(([start, end]) => [start, end])
|
|
351
|
+
: initBoundaries.flatMap((start, i, cuts) => i % 2 === 0 && i + 1 < cuts.length
|
|
352
|
+
? [[start, cuts[i + 1]]]
|
|
353
|
+
: []);
|
|
338
354
|
const tree = bytesToTree(this.space, this.alphabet, initBytes, undefined, undefined, initBoundaries.length > 0 ? initBoundaries : undefined);
|
|
339
355
|
this._conversations.set(id, {
|
|
340
356
|
tree,
|
|
341
357
|
bytes: initBytes,
|
|
342
358
|
boundaries: initBoundaries,
|
|
359
|
+
answeredSpans: initAnswered,
|
|
343
360
|
perceiveMemo: new Map(),
|
|
344
361
|
recogniseMemo: new Map(),
|
|
345
362
|
climbMemo: new Map(),
|
|
@@ -361,6 +378,7 @@ export class Mind {
|
|
|
361
378
|
return {
|
|
362
379
|
context: data.bytes,
|
|
363
380
|
boundaries: [...data.boundaries],
|
|
381
|
+
answeredSpans: data.answeredSpans.map(([start, end]) => [start, end]),
|
|
364
382
|
};
|
|
365
383
|
}
|
|
366
384
|
/** Append a turn to a conversation's accumulated context WITHOUT
|
|
@@ -447,8 +465,11 @@ export class Mind {
|
|
|
447
465
|
// the cumulative continuous shape multi-turn training deposits, so a
|
|
448
466
|
// later turn can refer to what was ANSWERED ("which of those two…"),
|
|
449
467
|
// not only to what was asked.
|
|
450
|
-
if (response.bytes.length > 0)
|
|
468
|
+
if (response.bytes.length > 0) {
|
|
469
|
+
const start = data.bytes.length;
|
|
451
470
|
this.addTurn(conv, response.bytes);
|
|
471
|
+
data.answeredSpans.push([start, data.bytes.length]);
|
|
472
|
+
}
|
|
452
473
|
return { response, state: this.conversationState(conv) };
|
|
453
474
|
}
|
|
454
475
|
finally {
|
|
@@ -487,6 +508,8 @@ export class Mind {
|
|
|
487
508
|
* reports each ingested item's deposited root node ids
|
|
488
509
|
* ({@link DepositReport}); purely observational. */
|
|
489
510
|
async ingest(input, second, onDeposit) {
|
|
511
|
+
invalidateStructuralCaches(this);
|
|
512
|
+
invalidateJunctionCache(this);
|
|
490
513
|
return ingest(this, input, second, onDeposit);
|
|
491
514
|
}
|
|
492
515
|
// ── Extension Surface ────────────────────────────────────────────────────
|
|
@@ -41,8 +41,8 @@ export declare class Precomputed {
|
|
|
41
41
|
* serves every mechanism that prices commonality — AND the consensus
|
|
42
42
|
* climb, which is the largest consumer and used to build its own. The
|
|
43
43
|
* ONE definition of its lifetime lives in traverse.ts
|
|
44
|
-
* ({@link sharedReachMemo}):
|
|
45
|
-
*
|
|
44
|
+
* ({@link sharedReachMemo}): session-scoped between writes and always cold
|
|
45
|
+
* under a trace. */
|
|
46
46
|
private _reach?;
|
|
47
47
|
get reachMemo(): Map<number, AncestorReach>;
|
|
48
48
|
/** Charge a lazily-shared analysis to its OWN phase rather than to the
|
|
@@ -94,8 +94,8 @@ export class Precomputed {
|
|
|
94
94
|
* serves every mechanism that prices commonality — AND the consensus
|
|
95
95
|
* climb, which is the largest consumer and used to build its own. The
|
|
96
96
|
* ONE definition of its lifetime lives in traverse.ts
|
|
97
|
-
* ({@link sharedReachMemo}):
|
|
98
|
-
*
|
|
97
|
+
* ({@link sharedReachMemo}): session-scoped between writes and always cold
|
|
98
|
+
* under a trace. */
|
|
99
99
|
_reach;
|
|
100
100
|
get reachMemo() {
|
|
101
101
|
return this._reach ??= sharedReachMemo(this.ctx);
|
|
@@ -185,7 +185,9 @@ function computeWeave(ctx, query, pre, climb) {
|
|
|
185
185
|
// recognising) a corpus-sized deposit: profiled on a 17.7M-node store,
|
|
186
186
|
// uncapped weaves spent 5–8s per query recognising conversation-length
|
|
187
187
|
// anchors that could never form a weave point.
|
|
188
|
-
const
|
|
188
|
+
const askerBytes = query.length -
|
|
189
|
+
ctx.answeredSpans.reduce((n, [start, end]) => n + end - start, 0);
|
|
190
|
+
const capBytes = askerBytes * quantum;
|
|
189
191
|
// EXCLUSIVITY IS ARBITRATED BY THE CLIMB'S VOTE ORDER, DELIBERATELY. A query
|
|
190
192
|
// byte can only be independent evidence for ONE point, so points are built in
|
|
191
193
|
// ranked order and each new point's runs are trimmed against every point
|
|
@@ -214,11 +216,92 @@ function computeWeave(ctx, query, pre, climb) {
|
|
|
214
216
|
// frame reading depend on its rank, and the proposed-run gate below needs the
|
|
215
217
|
// real thing.
|
|
216
218
|
const cands = [];
|
|
219
|
+
const querySegments = [];
|
|
220
|
+
let segmentStart = 0;
|
|
221
|
+
for (const [start, end] of ctx.answeredSpans) {
|
|
222
|
+
if (segmentStart < start)
|
|
223
|
+
querySegments.push([segmentStart, start]);
|
|
224
|
+
segmentStart = Math.max(segmentStart, end);
|
|
225
|
+
}
|
|
226
|
+
if (segmentStart < query.length) {
|
|
227
|
+
querySegments.push([segmentStart, query.length]);
|
|
228
|
+
}
|
|
229
|
+
const weaveLength = querySegments.reduce((n, [s, e]) => n + e - s, 0);
|
|
230
|
+
const weaveQuery = new Uint8Array(weaveLength);
|
|
231
|
+
const weaveMap = [];
|
|
232
|
+
let compactStart = 0;
|
|
233
|
+
for (const [start, end] of querySegments) {
|
|
234
|
+
weaveQuery.set(query.subarray(start, end), compactStart);
|
|
235
|
+
weaveMap.push({
|
|
236
|
+
compactStart,
|
|
237
|
+
originalStart: start,
|
|
238
|
+
length: end - start,
|
|
239
|
+
});
|
|
240
|
+
compactStart += end - start;
|
|
241
|
+
}
|
|
242
|
+
const segmentOf = (start, end) => {
|
|
243
|
+
let lo = 0;
|
|
244
|
+
let hi = weaveMap.length;
|
|
245
|
+
while (lo < hi) {
|
|
246
|
+
const mid = (lo + hi) >>> 1;
|
|
247
|
+
if (weaveMap[mid].originalStart <= start)
|
|
248
|
+
lo = mid + 1;
|
|
249
|
+
else
|
|
250
|
+
hi = mid;
|
|
251
|
+
}
|
|
252
|
+
const part = lo > 0 ? weaveMap[lo - 1] : undefined;
|
|
253
|
+
return part && end <= part.originalStart + part.length ? part : undefined;
|
|
254
|
+
};
|
|
255
|
+
const weaveSites = pre.rec.sites.flatMap((s) => {
|
|
256
|
+
const part = segmentOf(s.start, s.end);
|
|
257
|
+
return part
|
|
258
|
+
? [{
|
|
259
|
+
...s,
|
|
260
|
+
start: part.compactStart + s.start - part.originalStart,
|
|
261
|
+
end: part.compactStart + s.end - part.originalStart,
|
|
262
|
+
}]
|
|
263
|
+
: [];
|
|
264
|
+
});
|
|
217
265
|
for (const cand of rankedCapped) {
|
|
218
266
|
const ctxBytes = read(ctx, cand.anchor, capBytes + 1);
|
|
219
267
|
if (ctxBytes.length === 0 || ctxBytes.length > capBytes)
|
|
220
268
|
continue;
|
|
221
|
-
|
|
269
|
+
// CAST compares structures stated by the asker. Completed assistant turns
|
|
270
|
+
// remain available to recognition and the climb as conversation context,
|
|
271
|
+
// but aligning every candidate across their full prose makes weave work
|
|
272
|
+
// grow with answer length and lets the engine analogise against its own
|
|
273
|
+
// previous output. The compact asker stream is aligned once (so candidate
|
|
274
|
+
// windows are not rebuilt per turn), then every run is split back across
|
|
275
|
+
// the original turn segments so no evidence crosses an omitted boundary.
|
|
276
|
+
const raw = alignGraded(ctx, weaveQuery, ctxBytes, weaveSites).flatMap((r) => {
|
|
277
|
+
let lo = 0;
|
|
278
|
+
let hi = weaveMap.length;
|
|
279
|
+
while (lo < hi) {
|
|
280
|
+
const mid = (lo + hi) >>> 1;
|
|
281
|
+
if (weaveMap[mid].compactStart <= r.qs)
|
|
282
|
+
lo = mid + 1;
|
|
283
|
+
else
|
|
284
|
+
hi = mid;
|
|
285
|
+
}
|
|
286
|
+
const out = [];
|
|
287
|
+
for (let pi = Math.max(0, lo - 1); pi < weaveMap.length; pi++) {
|
|
288
|
+
const part = weaveMap[pi];
|
|
289
|
+
if (part.compactStart >= r.qe)
|
|
290
|
+
break;
|
|
291
|
+
const partEnd = part.compactStart + part.length;
|
|
292
|
+
const start = Math.max(r.qs, part.compactStart);
|
|
293
|
+
const end = Math.min(r.qe, partEnd);
|
|
294
|
+
if (start >= end)
|
|
295
|
+
continue;
|
|
296
|
+
out.push({
|
|
297
|
+
...r,
|
|
298
|
+
qs: part.originalStart + start - part.compactStart,
|
|
299
|
+
qe: part.originalStart + end - part.compactStart,
|
|
300
|
+
cs: r.cs + start - r.qs,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return out;
|
|
304
|
+
});
|
|
222
305
|
if (raw.length === 0)
|
|
223
306
|
continue;
|
|
224
307
|
for (const r of raw) {
|