@hviana/sema 0.6.0 → 0.7.1
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/.github/workflows/release.yml +80 -0
- package/AGENTS.md +53 -9
- package/dist/src/meter.d.ts +1 -4
- package/dist/src/meter.js +0 -3
- package/dist/src/mind/attention.js +22 -20
- package/dist/src/mind/graph-search.d.ts +43 -9
- package/dist/src/mind/graph-search.js +82 -15
- package/dist/src/mind/junction.d.ts +13 -0
- package/dist/src/mind/junction.js +13 -0
- package/dist/src/mind/mechanisms/cover.js +23 -2
- package/dist/src/mind/mechanisms/prefix-completion.js +13 -11
- package/dist/src/mind/mechanisms/recall.js +8 -4
- package/dist/src/mind/pipeline-mechanism.d.ts +0 -24
- package/dist/src/mind/pipeline-mechanism.js +13 -36
- package/dist/src/mind/pipeline.d.ts +23 -0
- package/dist/src/mind/pipeline.js +51 -3
- package/dist/src/mind/recognition.d.ts +6 -1
- package/dist/src/mind/recognition.js +11 -6
- package/dist/src/mind/resonance.js +48 -13
- package/dist/src/store.js +22 -1
- package/jsr.json +1 -1
- package/package.json +7 -2
- package/src/meter.ts +1 -4
- package/src/mind/attention.ts +22 -19
- package/src/mind/graph-search.ts +93 -16
- package/src/mind/junction.ts +13 -0
- package/src/mind/mechanisms/cover.ts +23 -4
- package/src/mind/mechanisms/prefix-completion.ts +13 -11
- package/src/mind/mechanisms/recall.ts +8 -4
- package/src/mind/pipeline-mechanism.ts +13 -42
- package/src/mind/pipeline.ts +87 -3
- package/src/mind/recognition.ts +19 -6
- package/src/mind/resonance.ts +79 -50
- package/src/store.ts +21 -1
- package/test/89-completion-recursion.test.mjs +230 -0
- package/test/90-connector-read-cap.test.mjs +130 -0
- package/test/91-branch-bytes-cache.test.mjs +152 -0
- package/test/93-regime-prediction.test.mjs +148 -0
- package/test/94-cross-region-budget.test.mjs +67 -0
- package/test/95-wide-resonance-removed.test.mjs +109 -0
package/src/mind/graph-search.ts
CHANGED
|
@@ -108,6 +108,13 @@ export type GItem =
|
|
|
108
108
|
* because subtraction is more "the point" of its query). See
|
|
109
109
|
* {@link liftAnswer}. */
|
|
110
110
|
computed?: boolean;
|
|
111
|
+
/** Set on the out emitted at a chain's GENUINE FIXPOINT — the one span kind a
|
|
112
|
+
* recursive re-cover ({@link GraphSearch.recompleteNode}) may deepen. The
|
|
113
|
+
* re-cover does NOT run here: this marks the span as eligible, and
|
|
114
|
+
* {@link GraphSearch.deepen} runs it afterwards on the spans the lightest
|
|
115
|
+
* derivation actually CHOSE. Part of {@link key}, because it decides
|
|
116
|
+
* whether the span's final bytes may still change. */
|
|
117
|
+
fix?: boolean;
|
|
111
118
|
};
|
|
112
119
|
type OutItem = Extract<GItem, { kind: "out" }>;
|
|
113
120
|
|
|
@@ -153,6 +160,10 @@ export interface Seg {
|
|
|
153
160
|
/** See the `computed` field of the "out" {@link GItem} — set only for an
|
|
154
161
|
* extension's derived value, never a genuinely recognised learned form. */
|
|
155
162
|
computed?: boolean;
|
|
163
|
+
/** See the `fix` field of the "out" {@link GItem} — this span ended a chain at
|
|
164
|
+
* a genuine fixpoint, so it is the one span kind a recursive re-cover may
|
|
165
|
+
* still deepen. Consumed by {@link GraphSearch.deepen}. */
|
|
166
|
+
fix?: boolean;
|
|
156
167
|
}
|
|
157
168
|
|
|
158
169
|
/** Read the chosen spans back off a derivation: the goal is a chain of bridge
|
|
@@ -171,6 +182,7 @@ function readCover(derivation: Derivation<GItem>): Seg[] {
|
|
|
171
182
|
rec: out.rec,
|
|
172
183
|
node: out.node,
|
|
173
184
|
computed: out.computed,
|
|
185
|
+
fix: out.fix,
|
|
174
186
|
});
|
|
175
187
|
}
|
|
176
188
|
node = node.premises[0];
|
|
@@ -472,10 +484,32 @@ export class GraphSearch {
|
|
|
472
484
|
onDerivation(readDerivation(derivation, substitutions !== undefined));
|
|
473
485
|
}
|
|
474
486
|
return derivation
|
|
475
|
-
? { segs: readCover(derivation), cost: derivation.cost }
|
|
487
|
+
? { segs: this.deepen(readCover(derivation)), cost: derivation.cost }
|
|
476
488
|
: null;
|
|
477
489
|
}
|
|
478
490
|
|
|
491
|
+
/** Re-cover the CHOSEN fixpoint spans, in place.
|
|
492
|
+
*
|
|
493
|
+
* Completion is still "cover, recursively" — it just runs on the answer
|
|
494
|
+
* instead of on the exploration. A cover chooses O(segs) spans, so a level
|
|
495
|
+
* pays O(answer) re-covers however densely the corpus interconnects the forms
|
|
496
|
+
* the search passed through on the way. That is the bound
|
|
497
|
+
* {@link recompleteNode}'s contract always claimed and, running per fixpoint
|
|
498
|
+
* REACHED, never had.
|
|
499
|
+
*
|
|
500
|
+
* Deepening cannot change which cover won: the derivation is already final
|
|
501
|
+
* and every span keeps its i..j and its cost. It only replaces a chosen
|
|
502
|
+
* span's bytes with the deeper learnt form they rewrite to — what the
|
|
503
|
+
* recursion was always for. */
|
|
504
|
+
private deepen(segs: Seg[]): Seg[] {
|
|
505
|
+
for (const s of segs) {
|
|
506
|
+
if (!s.fix || s.node === undefined) continue;
|
|
507
|
+
const deeper = this.recompleteNode(s.node);
|
|
508
|
+
if (deeper !== null) s.bytes = deeper;
|
|
509
|
+
}
|
|
510
|
+
return segs;
|
|
511
|
+
}
|
|
512
|
+
|
|
479
513
|
/** The weighted deduction system the graph exploration solves (the four
|
|
480
514
|
* reductions of adapted A*LD live in {@link lightestDerivation}; this only states the
|
|
481
515
|
* items, axioms, goal, and rules — see {@link GItem} for the item kinds).
|
|
@@ -541,8 +575,8 @@ export class GraphSearch {
|
|
|
541
575
|
}`;
|
|
542
576
|
}
|
|
543
577
|
return `o${it.i}.${it.j}.${it.cover ? 1 : 0}.${it.rec ? 1 : 0}.${
|
|
544
|
-
it.
|
|
545
|
-
}.${latin1(it.bytes)}`;
|
|
578
|
+
it.fix ? 1 : 0
|
|
579
|
+
}.${it.node ?? -1}.${latin1(it.bytes)}`;
|
|
546
580
|
},
|
|
547
581
|
*axioms() {
|
|
548
582
|
yield { item: { kind: "cover", p: 0 }, cost: 0 };
|
|
@@ -855,17 +889,29 @@ export class GraphSearch {
|
|
|
855
889
|
// actual end, never per intermediate stop — so its cost tracks the
|
|
856
890
|
// ANSWER's own structure, not how densely the corpus interconnects
|
|
857
891
|
// the nodes passed through on the way there.
|
|
858
|
-
|
|
892
|
+
// MARK the fixpoint; do not re-cover it here. Re-covering at this point
|
|
893
|
+
// pays a full {@link recompleteNode} — a recognition of the node's whole
|
|
894
|
+
// bytes — for every fixpoint the exploration REACHES, and how many it
|
|
895
|
+
// reaches is set by how densely the corpus interconnects the forms passed
|
|
896
|
+
// through. Measured on an 18.9M-node store, a 2-byte query: 12,000
|
|
897
|
+
// re-covers inside ONE cover, folding 95,258 distinct spans, ~2.4 GB and
|
|
898
|
+
// climbing to a V8 fatal. The answer needs a handful.
|
|
899
|
+
//
|
|
900
|
+
// {@link deepen} runs it instead on the spans the lightest derivation
|
|
901
|
+
// CHOSE. The ladder is untouched — this out still costs 0, so a genuine
|
|
902
|
+
// fixpoint still beats any premature stop at the same depth, exactly as
|
|
903
|
+
// the ordering above states.
|
|
859
904
|
yield {
|
|
860
905
|
premises: [it],
|
|
861
906
|
conclusion: {
|
|
862
907
|
kind: "out",
|
|
863
908
|
i: it.i,
|
|
864
909
|
j: it.j,
|
|
865
|
-
bytes:
|
|
910
|
+
bytes: nodeBytes(it.node),
|
|
866
911
|
cover: true,
|
|
867
912
|
rec: true,
|
|
868
913
|
node: it.node,
|
|
914
|
+
fix: true,
|
|
869
915
|
},
|
|
870
916
|
cost: 0,
|
|
871
917
|
};
|
|
@@ -936,18 +982,45 @@ export class GraphSearch {
|
|
|
936
982
|
* ({@link resolve}) — the graph itself gates against re-expanding a contained
|
|
937
983
|
* form ("ice is cold" ⊅→ "ice is cold is cold").
|
|
938
984
|
*
|
|
939
|
-
* Termination is
|
|
940
|
-
*
|
|
941
|
-
*
|
|
942
|
-
*
|
|
943
|
-
*
|
|
944
|
-
*
|
|
985
|
+
* Termination is STRUCTURAL: a produced node is re-covered once, never inside
|
|
986
|
+
* another re-cover (see the guard below), so one cover pays at most one
|
|
987
|
+
* nested {@link solve} per distinct produced node it actually reaches, and
|
|
988
|
+
* {@link recompleteMemo} collapses a repeat to nothing.
|
|
989
|
+
*
|
|
990
|
+
* This comment used to argue termination from "distinct node ids are finite
|
|
991
|
+
* and each finished completion is memoised". That is a bound of N — the one
|
|
992
|
+
* AGENTS §2.8 forbids — and it was load-bearing, not pedantic: nested, the
|
|
993
|
+
* recursion reached depth 331 and 9.1 GB on an 18.9M-node store for a 2-byte
|
|
994
|
+
* query and did not terminate, which is what killed a 5 h training run at its
|
|
995
|
+
* checkpoint recall. Guard: test/89-completion-recursion.test.mjs. */
|
|
945
996
|
private recompleteNode(node: number): Uint8Array | null {
|
|
946
997
|
if (!this.host.recogniseSpan) return null;
|
|
947
998
|
const memo = this.recompleteMemo;
|
|
948
999
|
if (memo.has(node)) return memo.get(node) ?? null;
|
|
949
|
-
//
|
|
950
|
-
|
|
1000
|
+
// ONE re-cover per produced node — never a re-cover inside a re-cover.
|
|
1001
|
+
//
|
|
1002
|
+
// Re-covering is how a PRODUCED node's bytes enter the search at all: the
|
|
1003
|
+
// cover machinery otherwise only ever sees the QUERY's spans. That is
|
|
1004
|
+
// needed once. The alternation of decomposition and recomposition that
|
|
1005
|
+
// follows — parts rewriting several times, siblings fusing, a recomposition
|
|
1006
|
+
// feeding another — is the main search's own fuse/`rcmp` work, not this
|
|
1007
|
+
// recursion's: 15-decomposition-gap §9–§12 all pass with this method
|
|
1008
|
+
// disabled outright, and only §6 (the produced composite "p1 p2", whose
|
|
1009
|
+
// bytes nothing else brings in) needs it.
|
|
1010
|
+
//
|
|
1011
|
+
// Nesting it was the defect. Each level is a full {@link solve} with its
|
|
1012
|
+
// own agenda and chart, exploring from a node the answer never asked about,
|
|
1013
|
+
// so per-query cost tracked how densely the corpus interconnects the forms
|
|
1014
|
+
// passed through — the growth AGENTS §2.8 forbids. Measured on an
|
|
1015
|
+
// 18.9M-node store: depth 331 and 9.1 GB for a 2-byte query, not
|
|
1016
|
+
// terminating; and on the guard corpus every one of 125 nested re-covers
|
|
1017
|
+
// was REJECTED by the resolve() gate below, expanding a 70-byte node into a
|
|
1018
|
+
// 374-byte concatenation that names nothing. All of it was waste.
|
|
1019
|
+
//
|
|
1020
|
+
// `recompleteOpen` is that stack, so a non-empty stack means we are already
|
|
1021
|
+
// inside one. This subsumes the old cycle guard: a node cannot recurse
|
|
1022
|
+
// back into itself when nothing recurses at all.
|
|
1023
|
+
if (this.recompleteOpen.size > 0) return null;
|
|
951
1024
|
|
|
952
1025
|
// A leaf or single-child node has no parts to recompose; skip before the
|
|
953
1026
|
// costly recognition so a plain terminal answer pays nothing.
|
|
@@ -985,9 +1058,13 @@ export class GraphSearch {
|
|
|
985
1058
|
* outs of a long query re-cover each distinct node at most once); reset at the
|
|
986
1059
|
* top of {@link cover}. */
|
|
987
1060
|
private recompleteMemo = new Map<number, Uint8Array | null>();
|
|
988
|
-
/** The
|
|
989
|
-
*
|
|
990
|
-
* (
|
|
1061
|
+
/** The node currently being re-completed — the recursion stack, and so also
|
|
1062
|
+
* the nesting depth. {@link recompleteNode} refuses to start while it is
|
|
1063
|
+
* non-empty (one re-cover per produced node, never one inside another), which
|
|
1064
|
+
* is what keeps a query's cost proportional to its answer rather than to the
|
|
1065
|
+
* corpus; it therefore holds at most one id. A Set, not a flag, because it
|
|
1066
|
+
* states WHICH node is open — the invariant a reader needs to check the
|
|
1067
|
+
* guard, and what makes the old cycle-guard reading still hold. */
|
|
991
1068
|
private recompleteOpen = new Set<number>();
|
|
992
1069
|
|
|
993
1070
|
/** out(i,j,bytes,…): index it for the binary rules, then offer splicing a
|
package/src/mind/junction.ts
CHANGED
|
@@ -207,6 +207,19 @@ function cachedContainers(
|
|
|
207
207
|
* edgeAncestors' question and wrong for this one: a junction container
|
|
208
208
|
* is legitimately reached across many containing structures. Half the
|
|
209
209
|
* successful junctions would be lost.
|
|
210
|
+
*
|
|
211
|
+
* REFUTED EARLY-STOP (side-cone exhaustion, §2.17's "real saturation"):
|
|
212
|
+
* stopping the walk the moment ONE side's upward cone is emptied is wrong,
|
|
213
|
+
* in both a hub-guarded form and a hub-flagged form. The junction test is
|
|
214
|
+
* a BYTE containment over the UNION of the two cones, and a junction can be
|
|
215
|
+
* structurally reachable from only ONE side — the side whose seed is a
|
|
216
|
+
* FOLD sub-node of the container (test/16: "cold or hot" is reached from
|
|
217
|
+
* the window "cold", but the 3-byte answer "hot" is not a 4-byte window of
|
|
218
|
+
* it, so "hot"'s cone is empty while the junction still lies ahead in
|
|
219
|
+
* "cold"'s cone; test/34's n-ary binding fails the hub-guarded form the
|
|
220
|
+
* same way). "One cone exhausted" therefore never proves "no junction
|
|
221
|
+
* left", and the walk must keep the √N·W budget as its NET after the
|
|
222
|
+
* per-node saturations below.
|
|
210
223
|
* • per-node hub guards — parent fan-outs beyond √N are hubs (not
|
|
211
224
|
* expanded); each node contributes at most one √N page of containers;
|
|
212
225
|
* √N collected candidates decide. */
|
|
@@ -59,7 +59,9 @@ export async function resolveConnectors(
|
|
|
59
59
|
// transcript evidence: cover still needs the site for structural context,
|
|
60
60
|
// but liftAnswer will trim that continuation as already answered. Building
|
|
61
61
|
// pairwise/n-ary bridges for it can only create connectors that are later
|
|
62
|
-
// discarded
|
|
62
|
+
// discarded — a semantically neutral gate (it removes work whose product
|
|
63
|
+
// liftAnswer throws away), and a cumulative (multi-turn) query is exactly
|
|
64
|
+
// where such already-answered continuations recur.
|
|
63
65
|
let answered = 0;
|
|
64
66
|
const ordered = [...sites]
|
|
65
67
|
.sort((a, b) => a.start - b.start)
|
|
@@ -72,9 +74,26 @@ export async function resolveConnectors(
|
|
|
72
74
|
if (span && span[0] <= s.start && s.end <= span[1]) return false;
|
|
73
75
|
if (query === undefined || ctx.answeredSpans.length === 0) return true;
|
|
74
76
|
const continuations = ctx.store.nextFirst(s.payload, hubBound(ctx));
|
|
75
|
-
return !continuations.some((answer) =>
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
return !continuations.some((answer) => {
|
|
78
|
+
// PREFIX-CAPPED (AGENTS §2.8): a candidate longer than the query cannot
|
|
79
|
+
// occur INSIDE it, so read one byte past the query's length — enough to
|
|
80
|
+
// detect the overflow — and reject without reconstructing the rest.
|
|
81
|
+
// The `+ 1` is what makes the test exact rather than a truncation: a
|
|
82
|
+
// result of exactly `query.length + 1` bytes is known to be too long,
|
|
83
|
+
// and anything shorter is the candidate's COMPLETE content, so the
|
|
84
|
+
// substring test below is the same test as before. (The same overflow
|
|
85
|
+
// probe bridge.ts:256 already uses.)
|
|
86
|
+
//
|
|
87
|
+
// This loop runs up to hubBound(ctx) = √N reads PER SITE, and only on a
|
|
88
|
+
// multi-turn response — `answeredSpans` is empty for a plain respond(),
|
|
89
|
+
// so the probe does not execute there. The cap cannot reduce the read
|
|
90
|
+
// COUNT — only a semantic change to the "already answered" test could —
|
|
91
|
+
// but it bounds each read by the query instead of by the corpus, which
|
|
92
|
+
// is what §2.8 asks for and what rescues a SHORT query: at 3 bytes this
|
|
93
|
+
// reads 4 bytes per candidate instead of the ~231 it averaged before.
|
|
94
|
+
const bytes = read(ctx, answer, query.length + 1);
|
|
95
|
+
return bytes.length <= query.length && indexOf(query, bytes, 0) >= 0;
|
|
96
|
+
});
|
|
78
97
|
});
|
|
79
98
|
const bridgePair = async (l: number, r: number) => {
|
|
80
99
|
if (l === r || links.has(l + "," + r)) return;
|
|
@@ -251,9 +251,9 @@ export const prefixMechanism: PipelineMechanism = {
|
|
|
251
251
|
provenance: "prefix",
|
|
252
252
|
async floor(ctx, query, _pre, worthRunning) {
|
|
253
253
|
// One projection: the form is voiced whole, nothing is substituted.
|
|
254
|
-
// INVESTMENT DISCIPLINE — the supplies below are
|
|
255
|
-
//
|
|
256
|
-
// bound can still beat the incumbent.
|
|
254
|
+
// INVESTMENT DISCIPLINE — the supplies below are a bounded √N window walk
|
|
255
|
+
// and the response's memoised top-k resonance read, so neither is touched
|
|
256
|
+
// until the bound can still beat the incumbent.
|
|
257
257
|
if (!worthRunning(STEP)) return STEP;
|
|
258
258
|
// A query with no room for a perceivable continuation inside the phrase
|
|
259
259
|
// cap cannot clear guard 2, so it is not worth a single read.
|
|
@@ -264,14 +264,16 @@ export const prefixMechanism: PipelineMechanism = {
|
|
|
264
264
|
return STEP;
|
|
265
265
|
},
|
|
266
266
|
async run(ctx, query, pre) {
|
|
267
|
-
// The
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
267
|
+
// The write side's window index proposes FIRST: a proper prefix's gist
|
|
268
|
+
// cannot rank its own continuation (cos falls below reachThreshold at a
|
|
269
|
+
// few bytes of truncation), so the content-addressed window walk is the
|
|
270
|
+
// correct measure for this question (§2.3), and it is a bounded √N walk —
|
|
271
|
+
// cheaper than an exhaustive ANN. The top-k resonance list is the SECOND
|
|
272
|
+
// supply, for prefixes long enough that the gist still ranks the form. A
|
|
273
|
+
// second SUPPLY, not a second mechanism — the same three guards decide
|
|
274
|
+
// either way.
|
|
275
|
+
const completed = prefixCompletion(ctx, query, formsOpenedBy(ctx, query)) ??
|
|
276
|
+
prefixCompletion(ctx, query, (await pre.resonance()).map((h) => h.id));
|
|
275
277
|
if (completed === null) return [];
|
|
276
278
|
return [{
|
|
277
279
|
bytes: completed.form,
|
|
@@ -412,10 +412,14 @@ export async function recallByResonance(
|
|
|
412
412
|
}
|
|
413
413
|
}
|
|
414
414
|
// 3b. Corroborated-substitution bridge — refusal-path only (bridge.ts).
|
|
415
|
-
// The
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
|
|
415
|
+
// The bridge's proposal source is the response's ONE top-k read — the same
|
|
416
|
+
// list recall already ranked above — never an exhaustive √N scan. The
|
|
417
|
+
// bridge's own candidate cap is 2·recallQueryK, so top-k proposals are
|
|
418
|
+
// exactly the budget it can consume, and every proposal is byte-verified
|
|
419
|
+
// downstream (§2.3). Reuse the memoised `resonance()`; scanning every IVF
|
|
420
|
+
// cluster here once made every honest refusal cost hundreds of ms regardless
|
|
421
|
+
// of k.
|
|
422
|
+
const wideIds = async () => (await pre.resonance()).map((h) => h.id);
|
|
419
423
|
|
|
420
424
|
// Every gist-based tier has failed; before refusing, align the query
|
|
421
425
|
// byte-for-byte against the trained contexts its own stored windows
|
|
@@ -19,7 +19,7 @@ import type { ComputedSpan } from "../extension.js";
|
|
|
19
19
|
import type { Hit } from "../store.js";
|
|
20
20
|
import type { Vec } from "../vec.js";
|
|
21
21
|
import { indexOf } from "../bytes.js";
|
|
22
|
-
import {
|
|
22
|
+
import { dominates } from "../geometry.js";
|
|
23
23
|
import { windowIds } from "./canonical.js";
|
|
24
24
|
import { read, resolve } from "./primitives.js";
|
|
25
25
|
import {
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
skillExemplar,
|
|
31
31
|
} from "./match.js";
|
|
32
32
|
import { climbAttentionAll } from "./attention.js";
|
|
33
|
-
import {
|
|
33
|
+
import { sharedReachMemo } from "./traverse.js";
|
|
34
34
|
|
|
35
35
|
// ── Precomputed ──────────────────────────────────────────────────────────────
|
|
36
36
|
//
|
|
@@ -148,46 +148,17 @@ export class Precomputed {
|
|
|
148
148
|
);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
* THE CONDITION IS THE TOP HIT'S SCORE, NOT THE CORPUS SIZE. When nothing
|
|
163
|
-
* ranks at concept level, an exhaustive ANN only scores more vectors below
|
|
164
|
-
* the bar (profiled at 38K–40K annVectorReads per refusing query on a 325K-
|
|
165
|
-
* context store); the structural channels — junction walks, anchor climbs,
|
|
166
|
-
* the write side's window index — are the correct proposal source there,
|
|
167
|
-
* because the ANN cannot propose what the gist cannot rank. This was once
|
|
168
|
-
* spelled `corpusN(ctx) <= (k · W)³`, which asks a different question and
|
|
169
|
-
* answers it wrongly at exactly the scale it was written from: at N =
|
|
170
|
-
* 325,608 with k = 24 and W = 4 the cube is 884,736, so that store took the
|
|
171
|
-
* exhaustive branch — the very branch measured above. Measured cost of the
|
|
172
|
-
* mismatch: substitutionBridge 8,544 ms of a 19,548 ms think (44%), against
|
|
173
|
-
* 1,248 ms and 14,218 ms without it, every answer byte-identical. */
|
|
174
|
-
wideResonance(): Promise<ReadonlyArray<number>> {
|
|
175
|
-
return this._wide ??= this.shared("wideResonance", async () => {
|
|
176
|
-
const hits = await this.resonance();
|
|
177
|
-
if (
|
|
178
|
-
hits.length > 0 &&
|
|
179
|
-
hits[0].score >= conceptThreshold(this.ctx.store.D)
|
|
180
|
-
) {
|
|
181
|
-
const exhaustive = await this.ctx.store.resonate(
|
|
182
|
-
this.guide,
|
|
183
|
-
hubBound(this.ctx),
|
|
184
|
-
true,
|
|
185
|
-
);
|
|
186
|
-
return exhaustive.map((h) => h.id);
|
|
187
|
-
}
|
|
188
|
-
return hits.map((h) => h.id);
|
|
189
|
-
});
|
|
190
|
-
}
|
|
151
|
+
// REMOVED — the WIDE exhaustive-√N resonance list (`wideResonance`). It ran
|
|
152
|
+
// `resonate(guide, √N, exhaustive=true)` whenever the top hit cleared
|
|
153
|
+
// conceptThreshold, so consumers could look "past the top-k". Every consumer
|
|
154
|
+
// only ever needed ≤ 2·recallQueryK proposals (the substitution bridge's own
|
|
155
|
+
// candidate cap) or a content-addressed answer (prefix completion's
|
|
156
|
+
// formsOpenedBy), and every proposal is byte-verified downstream (§2.3), so
|
|
157
|
+
// the exhaustive scan bought recall at O(index) cost for an O(k) need —
|
|
158
|
+
// measured: 244K annVectorReads per refusing query, ~1.5 s, every answer
|
|
159
|
+
// byte-identical to a top-k read. The two consumers now read `resonance()`
|
|
160
|
+
// (the one top-k read) and the write side's window index respectively — see
|
|
161
|
+
// recall.ts and prefix-completion.ts.
|
|
191
162
|
|
|
192
163
|
private _frames?: Promise<ReadonlyArray<FrameInstance>>;
|
|
193
164
|
/** THE FRAME INVENTORY — every ranked candidate that reads as an instance of
|
package/src/mind/pipeline.ts
CHANGED
|
@@ -129,6 +129,30 @@ export interface NarrowDecisionData {
|
|
|
129
129
|
margin: number;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
/** Structured payload of the "regimePrediction" rationale step — the R8
|
|
133
|
+
* observation exposed as data. After the first mechanism (cover, which §2.6
|
|
134
|
+
* runs first) grounds or abstains, the market's whole outcome is already
|
|
135
|
+
* determined by the one cost ladder: the consensus climb runs exactly when
|
|
136
|
+
* `worthRunning(2 * STEP)` is true — CAST (floor 2·STEP) is the cheapest
|
|
137
|
+
* mechanism that first-touches it, and confluence (3·STEP) / extraction
|
|
138
|
+
* (CONCEPT+STEP) are only reached after CAST is. An incumbent at or below
|
|
139
|
+
* that floor prunes CAST and, with it, the climb (retrieval); anything above
|
|
140
|
+
* — or no incumbent — runs the full market and the climb (composition).
|
|
141
|
+
* Purely observational; never read by inference. */
|
|
142
|
+
export interface RegimePredictionData {
|
|
143
|
+
version: 1;
|
|
144
|
+
/** retrieval | composition — the two regimes R1 measured as a ~100× cost
|
|
145
|
+
* step. */
|
|
146
|
+
regime: "retrieval" | "composition";
|
|
147
|
+
/** The incumbent's grade right after the first mechanism ran, or null when
|
|
148
|
+
* it grounded nothing (best === null — composition, with no incumbent). */
|
|
149
|
+
incumbentGrade: number | null;
|
|
150
|
+
/** The cheapest composition floor in grade units (`grade(2 * STEP)` = 2,
|
|
151
|
+
* CAST's floor) — the bar the incumbent must sit at or below for the
|
|
152
|
+
* consensus climb to be skipped. */
|
|
153
|
+
climbFloorGrade: number;
|
|
154
|
+
}
|
|
155
|
+
|
|
132
156
|
/** Think: a single lightest-derivation exploration of the Sema graph.
|
|
133
157
|
*
|
|
134
158
|
* Every answer travels the same path:
|
|
@@ -161,10 +185,22 @@ export async function think(
|
|
|
161
185
|
|
|
162
186
|
// ── Pre-computation ──────────────────────────────────────────────────
|
|
163
187
|
const mechanisms = mechs ?? defaultMechanisms;
|
|
164
|
-
const
|
|
188
|
+
const meter = ctx.meter;
|
|
189
|
+
// recognition is a shared analysis (§2.14 contract 5): it does the query's
|
|
190
|
+
// own store work (perceive → foldTree → resolve), which used to land in
|
|
191
|
+
// `think` and in nothing narrower — the meter's one accounting surface must
|
|
192
|
+
// charge it to itself, exactly as attention/weave/resonance are charged.
|
|
193
|
+
const rec = meter
|
|
194
|
+
? await meter.time("recognise", async () => recognise(ctx, query))
|
|
195
|
+
: recognise(ctx, query);
|
|
165
196
|
|
|
166
197
|
// Phase 1: collect computed spans from mechanisms that implement parse()
|
|
167
|
-
const computed =
|
|
198
|
+
const computed = meter
|
|
199
|
+
? await meter.time(
|
|
200
|
+
"collectComputed",
|
|
201
|
+
() => collectComputed(ctx, mechanisms, query),
|
|
202
|
+
)
|
|
203
|
+
: await collectComputed(ctx, mechanisms, query);
|
|
168
204
|
|
|
169
205
|
if (computed.length > 0) {
|
|
170
206
|
ctx.trace?.step(
|
|
@@ -191,6 +227,9 @@ export async function think(
|
|
|
191
227
|
// method on Precomputed, first-touched by whichever mechanism's floor
|
|
192
228
|
// survives its cheap gates and the worthRunning check. A query no
|
|
193
229
|
// mechanism climbs for (e.g. one an extension decided) never climbs.
|
|
230
|
+
// NOT phased: the constructor itself is trivial (it only derives `k`), so a
|
|
231
|
+
// phase here would add a zero-work entry to every profiled report — the meter
|
|
232
|
+
// attributes WORK (§2.14); the trace already represents structure.
|
|
194
233
|
const pre = new Precomputed(ctx, query, rec, computed, ctx._edgeGuide);
|
|
195
234
|
|
|
196
235
|
// ── Grounding: ONE lightest-derivation choice among the mechanisms ────
|
|
@@ -259,7 +298,7 @@ export async function think(
|
|
|
259
298
|
// Per-mechanism accounting (src/meter.ts). The market's whole premise is
|
|
260
299
|
// that mechanisms compete on one cost scale — so the profiling read-out is
|
|
261
300
|
// also per-mechanism, uniformly: the loop never asks which one it holds.
|
|
262
|
-
|
|
301
|
+
let regimeReported = false;
|
|
263
302
|
for (const mech of mechanisms) {
|
|
264
303
|
const floor = meter
|
|
265
304
|
? await meter.time(
|
|
@@ -308,6 +347,51 @@ export async function think(
|
|
|
308
347
|
scaffolding: r.scaffolding,
|
|
309
348
|
});
|
|
310
349
|
}
|
|
350
|
+
// REGIME PREDICTION (R8) — observational only. After the FIRST mechanism
|
|
351
|
+
// runs (cover, which §2.6 places first and floors at 0), the market's
|
|
352
|
+
// outcome is already determined: the consensus climb runs exactly when
|
|
353
|
+
// `worthRunning(2 * STEP)` is true — CAST (floor 2·STEP) is the cheapest
|
|
354
|
+
// mechanism that first-touches it, so an incumbent at or below grade 2
|
|
355
|
+
// prunes CAST and, with it, confluence (3·STEP) and extraction
|
|
356
|
+
// (CONCEPT+STEP) (retrieval); anything above — or no incumbent — runs the
|
|
357
|
+
// full market and the climb (composition). The predicate is
|
|
358
|
+
// `worthRunning`, the same function the loop just used — nothing is
|
|
359
|
+
// computed here that the engine had not already computed, and nothing is
|
|
360
|
+
// read back by inference.
|
|
361
|
+
if (!regimeReported) {
|
|
362
|
+
regimeReported = true;
|
|
363
|
+
const climbFloorGrade = grade(2 * STEP);
|
|
364
|
+
// TS narrows `best` to null in the outer flow (it cannot see the closure
|
|
365
|
+
// assignments in `consider`) — cast back, the same read-back as `decided`
|
|
366
|
+
// below.
|
|
367
|
+
const incumbent = best as Candidate | null;
|
|
368
|
+
const incumbentGrade = incumbent === null
|
|
369
|
+
? null
|
|
370
|
+
: grade(incumbent.weight);
|
|
371
|
+
const regime: "retrieval" | "composition" = worthRunning(2 * STEP)
|
|
372
|
+
? "composition"
|
|
373
|
+
: "retrieval";
|
|
374
|
+
ctx.trace?.step(
|
|
375
|
+
"regimePrediction",
|
|
376
|
+
[rItem(query, "query")],
|
|
377
|
+
[],
|
|
378
|
+
regime === "retrieval"
|
|
379
|
+
? `retrieval regime — incumbent grade ${incumbentGrade} ≤ climb floor ${climbFloorGrade}, so no composition mechanism runs; ` +
|
|
380
|
+
`the consensus climb will not run`
|
|
381
|
+
: `composition regime — ${
|
|
382
|
+
incumbentGrade === null
|
|
383
|
+
? "no incumbent (nothing grounded)"
|
|
384
|
+
: `incumbent grade ${incumbentGrade}`
|
|
385
|
+
} above climb floor ${climbFloorGrade}, so the full market and climb run`,
|
|
386
|
+
undefined,
|
|
387
|
+
{
|
|
388
|
+
version: 1,
|
|
389
|
+
regime,
|
|
390
|
+
incumbentGrade,
|
|
391
|
+
climbFloorGrade,
|
|
392
|
+
} satisfies RegimePredictionData,
|
|
393
|
+
);
|
|
394
|
+
}
|
|
311
395
|
}
|
|
312
396
|
|
|
313
397
|
// (TS cannot see the closure assignments into `best` and narrows it to its
|
package/src/mind/recognition.ts
CHANGED
|
@@ -33,7 +33,16 @@ import type { Leaf, Site } from "./graph-search.js";
|
|
|
33
33
|
* query's own cut cannot, and records sub-leaf boundaries as `splits`.
|
|
34
34
|
*
|
|
35
35
|
* Both O(n · maxGroup) bounded O(1) probes — never a scan of the corpus. */
|
|
36
|
-
|
|
36
|
+
/** Decompose `bytes` into the learnt forms it contains. `trimmed` skips the
|
|
37
|
+
* edge-trim fallbacks (which recover misaligned FRAGMENTS) — for callers whose
|
|
38
|
+
* own gate rejects fragments anyway (the pivot), so the O(n·W²) trim search is
|
|
39
|
+
* paid only where its output can be used. Byte-identical for every caller
|
|
40
|
+
* that keeps only top-level forms. */
|
|
41
|
+
export function recognise(
|
|
42
|
+
ctx: MindContext,
|
|
43
|
+
bytes: Uint8Array,
|
|
44
|
+
trimmed = false,
|
|
45
|
+
): Recognition {
|
|
37
46
|
// Content-keyed memo — works for both single-turn respond() and multi-turn
|
|
38
47
|
// respondTurn() (where the map persists across calls). ALWAYS consulted,
|
|
39
48
|
// regardless of tracing — matching perceive()'s own memo, which carries no
|
|
@@ -73,7 +82,7 @@ export function recognise(ctx: MindContext, bytes: Uint8Array): Recognition {
|
|
|
73
82
|
// not silent), so it is emitted here directly rather than only inside
|
|
74
83
|
// recogniseImpl.
|
|
75
84
|
if (ctx.recogniseMemo) {
|
|
76
|
-
const key = latin1Key(bytes);
|
|
85
|
+
const key = (trimmed ? "t" : "f") + latin1Key(bytes);
|
|
77
86
|
const hit = ctx.recogniseMemo.get(key);
|
|
78
87
|
if (hit !== undefined) {
|
|
79
88
|
if (ctx.meter) ctx.meter.recogniseHits++;
|
|
@@ -91,14 +100,18 @@ export function recognise(ctx: MindContext, bytes: Uint8Array): Recognition {
|
|
|
91
100
|
);
|
|
92
101
|
return hit;
|
|
93
102
|
}
|
|
94
|
-
const fresh = recogniseImpl(ctx, bytes);
|
|
103
|
+
const fresh = recogniseImpl(ctx, bytes, trimmed);
|
|
95
104
|
ctx.recogniseMemo.set(key, fresh);
|
|
96
105
|
return fresh;
|
|
97
106
|
}
|
|
98
|
-
return recogniseImpl(ctx, bytes);
|
|
107
|
+
return recogniseImpl(ctx, bytes, trimmed);
|
|
99
108
|
}
|
|
100
109
|
|
|
101
|
-
function recogniseImpl(
|
|
110
|
+
function recogniseImpl(
|
|
111
|
+
ctx: MindContext,
|
|
112
|
+
bytes: Uint8Array,
|
|
113
|
+
trimmed = false,
|
|
114
|
+
): Recognition {
|
|
102
115
|
if (ctx.meter) {
|
|
103
116
|
ctx.meter.recognitions++;
|
|
104
117
|
ctx.meter.recognisedBytes += bytes.length;
|
|
@@ -211,7 +224,7 @@ function recogniseImpl(ctx: MindContext, bytes: Uint8Array): Recognition {
|
|
|
211
224
|
// n.kids !== null enforces above) rather than degenerate into
|
|
212
225
|
// single-byte-atom territory, which atomIsHub already governs
|
|
213
226
|
// separately.
|
|
214
|
-
else if (end - start - 1 >= 2) {
|
|
227
|
+
else if (!trimmed && end - start - 1 >= 2) {
|
|
215
228
|
// The chunk's own boundary is drawn by content geometry, not by
|
|
216
229
|
// any notion of "form" — it can include one edge byte the query's
|
|
217
230
|
// fold happened to attach here that the trained span never had
|