@hviana/sema 0.7.1 → 0.7.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/HOW_IT_WORKS.md +17 -16
- package/dist/src/meter.d.ts +13 -0
- package/dist/src/meter.js +27 -0
- package/dist/src/mind/junction.js +13 -1
- package/dist/src/mind/mechanisms/prefix-completion.d.ts +2 -1
- package/dist/src/mind/mechanisms/prefix-completion.js +37 -19
- package/dist/src/mind/pipeline.d.ts +3 -2
- package/dist/src/mind/pipeline.js +58 -40
- package/dist/src/mind/recognition.d.ts +16 -7
- package/dist/src/mind/recognition.js +21 -12
- package/dist/src/mind/resonance.js +14 -7
- package/dist/src/store.d.ts +33 -4
- package/dist/src/store.js +71 -14
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/meter.ts +26 -0
- package/src/mind/junction.ts +12 -1
- package/src/mind/mechanisms/prefix-completion.ts +37 -19
- package/src/mind/pipeline.ts +68 -51
- package/src/mind/recognition.ts +20 -16
- package/src/mind/resonance.ts +13 -7
- package/src/store.ts +70 -16
- package/test/96-bytes-walk-termination.test.mjs +115 -0
package/dist/src/store.js
CHANGED
|
@@ -591,9 +591,6 @@ export class AbstractStore {
|
|
|
591
591
|
this._recCache.set(id, rec);
|
|
592
592
|
return rec;
|
|
593
593
|
}
|
|
594
|
-
/** Reconstruct the bytes a node spans by traversing the DAG bottom-up.
|
|
595
|
-
* Iterative post-order on an explicit stack — the call stack never sees the
|
|
596
|
-
* tree depth, so even an adversarial chain of nodes stays safe. */
|
|
597
594
|
/** How many reads hit a MISSING node record this session (a dangling edge
|
|
598
595
|
* or kid id). Zero in a healthy store; a growing count means references
|
|
599
596
|
* outlive their records — the read degrades safely to empty bytes, this
|
|
@@ -603,6 +600,25 @@ export class AbstractStore {
|
|
|
603
600
|
* nothing is profiling. Every read below bumps it through `?.`, so an
|
|
604
601
|
* unprofiled store pays one null check per read and allocates nothing. */
|
|
605
602
|
meter = null;
|
|
603
|
+
/** Reconstruct the bytes a node spans by traversing the DAG bottom-up.
|
|
604
|
+
* Iterative post-order on an explicit stack — the call stack never sees the
|
|
605
|
+
* tree depth, so even an adversarial chain of nodes stays safe.
|
|
606
|
+
*
|
|
607
|
+
* TERMINATION. The walk memoizes into a LOCAL map, and `_bytesCache` is
|
|
608
|
+
* consulted only as a warm hint whose hit is immediately promoted into that
|
|
609
|
+
* map. It used to use `_bytesCache` itself as the memo, which is not a
|
|
610
|
+
* memo at all: it EVICTS, and its `"smallest"` policy prefers precisely the
|
|
611
|
+
* freshly-resolved small children that the pending parents on the stack are
|
|
612
|
+
* waiting for. A parent then finds them uncached again, re-pushes them,
|
|
613
|
+
* they are re-resolved, re-inserted, re-evicted — the loop makes no
|
|
614
|
+
* progress and never exits. Latent until the cache saturates, then
|
|
615
|
+
* unconditional: observed in the wild at 19.9M nodes with the 20 MB cache
|
|
616
|
+
* pinned at 19,999,962/20,000,000 bytes, spinning 8h45m on a node whose
|
|
617
|
+
* whole content was 124 bytes (5 kids, 2 of them perpetually re-evicted).
|
|
618
|
+
* Because the loop is synchronous, no timer could fire — the trainer's stall
|
|
619
|
+
* watchdog never got a turn either. A local map resolves each node at most
|
|
620
|
+
* once per call, so the walk terminates by construction and `_bytesCache`
|
|
621
|
+
* goes back to being a pure speed hint. */
|
|
606
622
|
bytes(id) {
|
|
607
623
|
if (this.meter) {
|
|
608
624
|
this.meter.byteReads++;
|
|
@@ -619,10 +635,22 @@ export class AbstractStore {
|
|
|
619
635
|
return hit;
|
|
620
636
|
const stack = [id];
|
|
621
637
|
const cache = this._bytesCache;
|
|
638
|
+
// The walk's own memo. Entries are the same shared arrays `_bytesCache`
|
|
639
|
+
// holds (no extra copy), and it lives exactly as long as this call.
|
|
640
|
+
const done = new Map();
|
|
622
641
|
while (stack.length > 0) {
|
|
623
642
|
const nid = stack[stack.length - 1]; // peek
|
|
624
|
-
// Already resolved by
|
|
625
|
-
|
|
643
|
+
// Already resolved by this walk — the ONLY authority the readiness test
|
|
644
|
+
// below trusts, because it cannot be evicted underneath us.
|
|
645
|
+
if (done.has(nid)) {
|
|
646
|
+
stack.pop();
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
// Warm hint: a hit is promoted into `done` in the same step, so from
|
|
650
|
+
// here on the entry is pinned for the rest of the walk.
|
|
651
|
+
const warm = cache.get(nid);
|
|
652
|
+
if (warm !== undefined) {
|
|
653
|
+
done.set(nid, warm);
|
|
626
654
|
stack.pop();
|
|
627
655
|
continue;
|
|
628
656
|
}
|
|
@@ -634,21 +662,26 @@ export class AbstractStore {
|
|
|
634
662
|
// The cache makes the empty read permanent for the session; the
|
|
635
663
|
// counter survives as the visible trace.
|
|
636
664
|
this.danglingReads++;
|
|
665
|
+
done.set(nid, _ZERO);
|
|
637
666
|
cache.set(nid, _ZERO);
|
|
638
667
|
stack.pop();
|
|
639
668
|
continue;
|
|
640
669
|
}
|
|
641
670
|
if (rec.leaf) {
|
|
642
|
-
|
|
671
|
+
// COPY before caching: rec.leaf is the node record's own buffer, and
|
|
672
|
+
// handing it out would let one mutating caller corrupt the record.
|
|
673
|
+
const leaf = new Uint8Array(rec.leaf);
|
|
674
|
+
done.set(nid, leaf);
|
|
675
|
+
cache.set(nid, leaf);
|
|
643
676
|
stack.pop();
|
|
644
677
|
continue;
|
|
645
678
|
}
|
|
646
|
-
// Branch — push any
|
|
647
|
-
// left-to-right). If every child is
|
|
679
|
+
// Branch — push any unresolved children (reverse order so they resolve
|
|
680
|
+
// left-to-right). If every child is resolved, concatenate now.
|
|
648
681
|
const kids = rec.kids ?? [];
|
|
649
682
|
let ready = true;
|
|
650
683
|
for (let i = kids.length - 1; i >= 0; i--) {
|
|
651
|
-
if (!
|
|
684
|
+
if (!done.has(kids[i])) {
|
|
652
685
|
stack.push(kids[i]);
|
|
653
686
|
ready = false;
|
|
654
687
|
}
|
|
@@ -656,10 +689,11 @@ export class AbstractStore {
|
|
|
656
689
|
if (!ready)
|
|
657
690
|
continue;
|
|
658
691
|
stack.pop();
|
|
659
|
-
const out = concat(kids.map((k) =>
|
|
692
|
+
const out = concat(kids.map((k) => done.get(k)));
|
|
693
|
+
done.set(nid, out);
|
|
660
694
|
cache.set(nid, out);
|
|
661
695
|
}
|
|
662
|
-
const out =
|
|
696
|
+
const out = done.get(id) ?? _ZERO;
|
|
663
697
|
if (this.meter)
|
|
664
698
|
this.meter.bytesRead += out.length;
|
|
665
699
|
return out;
|
|
@@ -1189,10 +1223,33 @@ export class AbstractStore {
|
|
|
1189
1223
|
* common-prefix / common-suffix trim: whatever remains after both trims is
|
|
1190
1224
|
* the single differing span (substitution, insertion or deletion), and both
|
|
1191
1225
|
* remainders must fit the budget. Scattered differences leave a wide
|
|
1192
|
-
* middle and are rejected.
|
|
1226
|
+
* middle and are rejected.
|
|
1227
|
+
*
|
|
1228
|
+
* Every read here is CAPPED (§2.8). It used to open with
|
|
1229
|
+
* `bytesPrefix(k, Number.MAX_SAFE_INTEGER)` — the ALL sentinel, i.e. the
|
|
1230
|
+
* full materialising `bytes()` read — on the deposit hot path, and only
|
|
1231
|
+
* then compare lengths. So a candidate the length test was about to reject
|
|
1232
|
+
* had already been reconstructed byte for byte. The LENGTHS decide first
|
|
1233
|
+
* instead, from the `contentLen` memo the interning order has already built
|
|
1234
|
+
* bottom-up, and the target's length is itself read under a cap: a target
|
|
1235
|
+
* longer than `la + W` is rejected without touching one of its bytes.
|
|
1236
|
+
* Same semantics — the old capped `b` read would have produced
|
|
1237
|
+
* `a.length + W + 1` here and failed the very same test — strictly fewer
|
|
1238
|
+
* byte reads. The `+ 1` on each byte cap keeps `_prefix`'s
|
|
1239
|
+
* "complete reconstruction" test true, so the results still cache. */
|
|
1193
1240
|
differsByOneWindow(kids, targetId, W) {
|
|
1194
|
-
const
|
|
1195
|
-
|
|
1241
|
+
const lens = kids.map((k) => this.contentLen(k));
|
|
1242
|
+
let la = 0;
|
|
1243
|
+
for (const n of lens)
|
|
1244
|
+
la += n;
|
|
1245
|
+
const cap = la + W + 1;
|
|
1246
|
+
// `contentLen` under a cap returns a clamped LOWER BOUND once the partial
|
|
1247
|
+
// sum reaches it, so `>= cap` is exactly "longer than la + W".
|
|
1248
|
+
const lb = this.contentLen(targetId, cap);
|
|
1249
|
+
if (lb >= cap || Math.abs(la - lb) > W)
|
|
1250
|
+
return false;
|
|
1251
|
+
const a = concat(kids.map((k, i) => this.bytesPrefix(k, lens[i] + 1)));
|
|
1252
|
+
const b = this.bytesPrefix(targetId, lb + 1);
|
|
1196
1253
|
if (Math.abs(a.length - b.length) > W)
|
|
1197
1254
|
return false;
|
|
1198
1255
|
const n = Math.min(a.length, b.length);
|
package/jsr.json
CHANGED
package/package.json
CHANGED
package/src/meter.ts
CHANGED
|
@@ -176,6 +176,13 @@ export class Meter {
|
|
|
176
176
|
/** Nodes popped by those ascents, against their √N·W budget — the counter
|
|
177
177
|
* that shows whether the walks are deciding early or burning the budget. */
|
|
178
178
|
junctionPops = 0;
|
|
179
|
+
/** Ascents that ended by EXHAUSTING the expansion budget rather than by
|
|
180
|
+
* deciding — the walk abstained and the caller silently fell through to a
|
|
181
|
+
* lower tier of the ladder (§2.13: a degradation nothing else reports).
|
|
182
|
+
* It rises the moment a SHARED budget is drained by an earlier walk, which
|
|
183
|
+
* is what makes "this tier answered nothing" distinguishable from "this
|
|
184
|
+
* tier never got to look". */
|
|
185
|
+
junctionBudgetExhausted = 0;
|
|
179
186
|
/** Arbitrary byte spans whose distributional company was VSA-bundled from
|
|
180
187
|
* existing episode halos. */
|
|
181
188
|
spanHalos = 0;
|
|
@@ -231,6 +238,25 @@ export class Meter {
|
|
|
231
238
|
}
|
|
232
239
|
}
|
|
233
240
|
|
|
241
|
+
/** Time one SYNCHRONOUS phase. The sync/async seam (§2.10) is a real
|
|
242
|
+
* contract — perception, recognition and the graph search are synchronous —
|
|
243
|
+
* so a synchronous layer must not be wrapped in `time`'s promise just to be
|
|
244
|
+
* measured: that would make the profiled path await where the unprofiled
|
|
245
|
+
* one does not, and a meter never changes what a layer computes. */
|
|
246
|
+
timeSync<T>(phase: string, fn: () => T): T {
|
|
247
|
+
const before = this.snapshot();
|
|
248
|
+
const t = performance.now();
|
|
249
|
+
try {
|
|
250
|
+
return fn();
|
|
251
|
+
} finally {
|
|
252
|
+
const ms = performance.now() - t;
|
|
253
|
+
const after = this.snapshot();
|
|
254
|
+
const delta: Record<string, number> = {};
|
|
255
|
+
for (const k of Object.keys(after)) delta[k] = after[k] - before[k];
|
|
256
|
+
this.charge(phase, ms, delta);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
234
260
|
/** Time one async phase and attribute the work done inside it. Returns
|
|
235
261
|
* the awaited value untouched — a meter never changes what a layer
|
|
236
262
|
* computes, only what is known about it. */
|
package/src/mind/junction.ts
CHANGED
|
@@ -264,7 +264,18 @@ export function junctionContainersFrom(
|
|
|
264
264
|
id,
|
|
265
265
|
d: 0,
|
|
266
266
|
}));
|
|
267
|
-
while (stack.length > 0 && out.length < bound
|
|
267
|
+
while (stack.length > 0 && out.length < bound) {
|
|
268
|
+
// BUDGET EXHAUSTION IS AN ABSTENTION, AND IT MUST BE VISIBLE (§2.13). The
|
|
269
|
+
// walk stops with work still on the stack, the caller reads "no container"
|
|
270
|
+
// and falls through to a lower ladder rung — indistinguishable, from the
|
|
271
|
+
// outside, from a walk that looked everywhere and found nothing. With a
|
|
272
|
+
// SHARED budget (cross-region's one k·W allowance per tier) an EARLIER
|
|
273
|
+
// pair can drain it, so a later pair's exact tier may never run at all;
|
|
274
|
+
// this counter is the only thing that says so.
|
|
275
|
+
if (b.n-- <= 0) {
|
|
276
|
+
if (ctx.meter) ctx.meter.junctionBudgetExhausted++;
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
268
279
|
const { id: x, d } = stack.pop()!;
|
|
269
280
|
if (ctx.meter) ctx.meter.junctionPops++;
|
|
270
281
|
const f = cachedRead(ctx, cache, x, maxContainer);
|
|
@@ -45,15 +45,23 @@
|
|
|
45
45
|
// from `resonate(k)` at k = 24, 256 AND 2048 — while forms scoring LOWER
|
|
46
46
|
// (Germany 0.5670, Yemen 0.5591) are returned. `k` only reorders WITHIN
|
|
47
47
|
// the IVF clusters already probed, exactly as Store.resonate's doc warns,
|
|
48
|
-
// so no k recovers it.
|
|
48
|
+
// so no k recovers it.
|
|
49
49
|
//
|
|
50
|
-
// So this is a RETRIEVABILITY gap, not a semantic one, and
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
50
|
+
// So this is a RETRIEVABILITY gap, not a semantic one, and the ANN is the wrong
|
|
51
|
+
// instrument for it: a proper prefix's gist cannot rank its own continuation.
|
|
52
|
+
// The repair is CONTENT-ADDRESSED (§2.3) — `formsOpenedBy` (traverse.ts) reads
|
|
53
|
+
// the leaf-id WINDOW index the write side already maintains and answers "which
|
|
54
|
+
// trained forms does this byte run open?" in a bounded √N walk. That is this
|
|
55
|
+
// mechanism's first supply. The response's memoised top-k `resonance()` is the
|
|
56
|
+
// second, for prefixes long enough that the gist still ranks the form; it is
|
|
57
|
+
// read, never re-issued.
|
|
58
|
+
//
|
|
59
|
+
// AN EXHAUSTIVE ANN LIST IS NOT A SUPPLY HERE, AND WAS REMOVED. This tier once
|
|
60
|
+
// read `Precomputed.wideResonance()` — a full-index `resonate(guide, √N,
|
|
61
|
+
// exhaustive)` — on the argument that the target "ranks 8 with `exhaustive`".
|
|
62
|
+
// It bought an O(k) need at O(index) cost (measured: 244K annVectorReads per
|
|
63
|
+
// refusing query, ~1.5 s) for candidates the window index proposes directly.
|
|
64
|
+
// See pipeline-mechanism.ts's REMOVED note; test/95 pins its absence.
|
|
57
65
|
//
|
|
58
66
|
// THREE GUARDS, each falsified into existence by measurement — do not drop any:
|
|
59
67
|
//
|
|
@@ -114,7 +122,8 @@ export interface PrefixCompletion {
|
|
|
114
122
|
* it, when the continuation is sub-quantum, when a candidate's continuation
|
|
115
123
|
* cannot be read through, or when the candidates disagree.
|
|
116
124
|
*
|
|
117
|
-
* `ranked` must be a list the caller has ALREADY fetched
|
|
125
|
+
* `ranked` must be a list the caller has ALREADY fetched (the write side's
|
|
126
|
+
* window index, or the response's memoised top-k); this mechanism never
|
|
118
127
|
* resonates on its own (see the header's cost note). */
|
|
119
128
|
export function prefixCompletion(
|
|
120
129
|
ctx: MindContext,
|
|
@@ -264,16 +273,25 @@ export const prefixMechanism: PipelineMechanism = {
|
|
|
264
273
|
return STEP;
|
|
265
274
|
},
|
|
266
275
|
async run(ctx, query, pre) {
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
|
|
276
|
+
// ONE SUPPLY PASS, not a two-tier `??`. The window index (exact,
|
|
277
|
+
// content-addressed) and the response's memoised top-k (approximate) are
|
|
278
|
+
// concatenated and the three guards decide ONCE over the union. A
|
|
279
|
+
// first-then-fallback chain would let the APPROXIMATE tier override the
|
|
280
|
+
// EXACT one (§2.3): when formsOpenedBy finds two continuations, guard 3
|
|
281
|
+
// returns null and the fallback re-runs the guards on resonance's top-k
|
|
282
|
+
// alone — which, seeing only one of the two forms, would voice it. That is
|
|
283
|
+
// precisely the disagreement-suppression guard 3 exists to prevent, and it
|
|
284
|
+
// is the exact tier's ambiguity being washed away by the approximate tier.
|
|
285
|
+
// Evaluating the union means a disagreement the window index saw can never
|
|
286
|
+
// be hidden by what the ANN happens to rank. The ANN read is the
|
|
287
|
+
// response's ONE memoised top-k (§2.11), already paid by recall's refusal
|
|
288
|
+
// path on the queries where this mechanism fires, so reading it here is not
|
|
289
|
+
// a second index scan.
|
|
290
|
+
const ids = [
|
|
291
|
+
...formsOpenedBy(ctx, query),
|
|
292
|
+
...(await pre.resonance()).map((h) => h.id),
|
|
293
|
+
];
|
|
294
|
+
const completed = prefixCompletion(ctx, query, ids);
|
|
277
295
|
if (completed === null) return [];
|
|
278
296
|
return [{
|
|
279
297
|
bytes: completed.form,
|
package/src/mind/pipeline.ts
CHANGED
|
@@ -144,8 +144,9 @@ export interface RegimePredictionData {
|
|
|
144
144
|
/** retrieval | composition — the two regimes R1 measured as a ~100× cost
|
|
145
145
|
* step. */
|
|
146
146
|
regime: "retrieval" | "composition";
|
|
147
|
-
/** The incumbent's grade
|
|
148
|
-
* it
|
|
147
|
+
/** The incumbent's grade once the first mechanism's turn is over (it ran, or
|
|
148
|
+
* it was skipped), or null when nothing has grounded — `best === null`,
|
|
149
|
+
* which is composition with no incumbent. */
|
|
149
150
|
incumbentGrade: number | null;
|
|
150
151
|
/** The cheapest composition floor in grade units (`grade(2 * STEP)` = 2,
|
|
151
152
|
* CAST's floor) — the bar the incumbent must sit at or below for the
|
|
@@ -190,8 +191,11 @@ export async function think(
|
|
|
190
191
|
// own store work (perceive → foldTree → resolve), which used to land in
|
|
191
192
|
// `think` and in nothing narrower — the meter's one accounting surface must
|
|
192
193
|
// charge it to itself, exactly as attention/weave/resonance are charged.
|
|
194
|
+
// SYNCHRONOUS phase: recognition is on the sync side of §2.10's seam, so it
|
|
195
|
+
// is timed with `timeSync` — wrapping it in a promise would make a profiled
|
|
196
|
+
// response await where an unprofiled one does not.
|
|
193
197
|
const rec = meter
|
|
194
|
-
?
|
|
198
|
+
? meter.timeSync("recognise", () => recognise(ctx, query))
|
|
195
199
|
: recognise(ctx, query);
|
|
196
200
|
|
|
197
201
|
// Phase 1: collect computed spans from mechanisms that implement parse()
|
|
@@ -294,12 +298,66 @@ export async function think(
|
|
|
294
298
|
const worthRunning = (floor: number) =>
|
|
295
299
|
best === null || grade(floor) < grade(best.weight);
|
|
296
300
|
|
|
301
|
+
// REGIME PREDICTION (R8) — observational only. Once the FIRST mechanism has
|
|
302
|
+
// had its turn (cover, which §2.6 places first and floors at 0), the market's
|
|
303
|
+
// outcome is already determined by the one cost ladder: the consensus climb
|
|
304
|
+
// runs exactly when `worthRunning(2 * STEP)` is true — CAST (floor 2·STEP) is
|
|
305
|
+
// the cheapest mechanism that first-touches it, so an incumbent at or below
|
|
306
|
+
// grade 2 prunes CAST and, with it, confluence (3·STEP) and extraction
|
|
307
|
+
// (CONCEPT+STEP) (retrieval); anything above — or no incumbent — runs the
|
|
308
|
+
// full market and the climb (composition). The predicate is `worthRunning`,
|
|
309
|
+
// the same function the loop itself uses — nothing is computed here that the
|
|
310
|
+
// engine had not already computed, and nothing is read back by inference.
|
|
311
|
+
//
|
|
312
|
+
// EMITTED BEFORE THE SECOND MECHANISM'S FLOOR, never after some mechanism's
|
|
313
|
+
// run: a "prediction" published after the fact could assert "the climb will
|
|
314
|
+
// not run" about a climb that already ran — which is what happens whenever
|
|
315
|
+
// the first mechanism is SKIPPED (null floor or pruned) and the block sits at
|
|
316
|
+
// the end of the first mechanism that actually ran. Emitting on entry to
|
|
317
|
+
// iteration 1 makes the claim true by construction, whatever the first
|
|
318
|
+
// mechanism did, and keeps the payload identical on the ordinary path (the
|
|
319
|
+
// incumbent cannot change between the two positions).
|
|
320
|
+
let regimeReported = false;
|
|
321
|
+
const reportRegime = () => {
|
|
322
|
+
if (regimeReported) return;
|
|
323
|
+
regimeReported = true;
|
|
324
|
+
const climbFloorGrade = grade(2 * STEP);
|
|
325
|
+
// TS narrows `best` to null in the outer flow (it cannot see the closure
|
|
326
|
+
// assignments in `consider`) — cast back, the same read-back as `decided`
|
|
327
|
+
// below.
|
|
328
|
+
const incumbent = best as Candidate | null;
|
|
329
|
+
const incumbentGrade = incumbent === null ? null : grade(incumbent.weight);
|
|
330
|
+
const regime: "retrieval" | "composition" = worthRunning(2 * STEP)
|
|
331
|
+
? "composition"
|
|
332
|
+
: "retrieval";
|
|
333
|
+
ctx.trace?.step(
|
|
334
|
+
"regimePrediction",
|
|
335
|
+
[rItem(query, "query")],
|
|
336
|
+
[],
|
|
337
|
+
regime === "retrieval"
|
|
338
|
+
? `retrieval regime — incumbent grade ${incumbentGrade} ≤ climb floor ${climbFloorGrade}, ` +
|
|
339
|
+
`so no mechanism floored above that grade runs; the consensus climb will not run`
|
|
340
|
+
: `composition regime — ${
|
|
341
|
+
incumbentGrade === null
|
|
342
|
+
? "no incumbent (nothing grounded)"
|
|
343
|
+
: `incumbent grade ${incumbentGrade}`
|
|
344
|
+
} above climb floor ${climbFloorGrade}, so the full market and climb run`,
|
|
345
|
+
undefined,
|
|
346
|
+
{
|
|
347
|
+
version: 1,
|
|
348
|
+
regime,
|
|
349
|
+
incumbentGrade,
|
|
350
|
+
climbFloorGrade,
|
|
351
|
+
} satisfies RegimePredictionData,
|
|
352
|
+
);
|
|
353
|
+
};
|
|
297
354
|
// Phase 3: grounding loop
|
|
298
355
|
// Per-mechanism accounting (src/meter.ts). The market's whole premise is
|
|
299
356
|
// that mechanisms compete on one cost scale — so the profiling read-out is
|
|
300
357
|
// also per-mechanism, uniformly: the loop never asks which one it holds.
|
|
301
|
-
let
|
|
302
|
-
|
|
358
|
+
for (let mi = 0; mi < mechanisms.length; mi++) {
|
|
359
|
+
const mech = mechanisms[mi];
|
|
360
|
+
if (mi > 0) reportRegime();
|
|
303
361
|
const floor = meter
|
|
304
362
|
? await meter.time(
|
|
305
363
|
`${mech.name}.floor`,
|
|
@@ -347,52 +405,11 @@ export async function think(
|
|
|
347
405
|
scaffolding: r.scaffolding,
|
|
348
406
|
});
|
|
349
407
|
}
|
|
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
|
-
}
|
|
395
408
|
}
|
|
409
|
+
// A market of ONE mechanism never reaches iteration 1; the step is still
|
|
410
|
+
// emitted exactly once per think(), so a consumer never has to ask whether
|
|
411
|
+
// the list was long enough for the prediction to exist.
|
|
412
|
+
reportRegime();
|
|
396
413
|
|
|
397
414
|
// (TS cannot see the closure assignments into `best` and narrows it to its
|
|
398
415
|
// initial null, so the read-back needs the assertion.)
|
|
@@ -592,7 +609,7 @@ export async function think(
|
|
|
592
609
|
reasoned,
|
|
593
610
|
pre,
|
|
594
611
|
unclimbed,
|
|
595
|
-
|
|
612
|
+
primarySpans,
|
|
596
613
|
);
|
|
597
614
|
|
|
598
615
|
done(
|
package/src/mind/recognition.ts
CHANGED
|
@@ -32,16 +32,24 @@ import type { Leaf, Site } from "./graph-search.js";
|
|
|
32
32
|
* the longest known leaf, chained into flat branches. Names forms the
|
|
33
33
|
* query's own cut cannot, and records sub-leaf boundaries as `splits`.
|
|
34
34
|
*
|
|
35
|
-
* Both O(n · maxGroup) bounded O(1) probes — never a scan of the corpus.
|
|
36
|
-
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
35
|
+
* Both O(n · maxGroup) bounded O(1) probes — never a scan of the corpus.
|
|
36
|
+
*
|
|
37
|
+
* ONE READING PER BYTE STREAM, deliberately: there is no "cheap mode" that
|
|
38
|
+
* skips the edge-trim fallbacks. A `trimmed` variant was tried and REFUTED
|
|
39
|
+
* twice over. Its premise — "the trims only recover misaligned FRAGMENTS, so
|
|
40
|
+
* a consumer whose gate rejects fragments loses nothing" — is false: the
|
|
41
|
+
* left/right trim loops below exist precisely to find WHOLE trained forms
|
|
42
|
+
* embedded at an offset the query's own fold did not cut, and such a form has
|
|
43
|
+
* no structural parents or containers, so it passes the pivot's fragment gate
|
|
44
|
+
* and is exactly the candidate a multi-hop chain steps through. Skipping them
|
|
45
|
+
* narrows the pivot's evidence silently. And a per-caller variant has to key
|
|
46
|
+
* the memo by the variant, which breaks the "computed at most once" property
|
|
47
|
+
* (§2.11): the pipeline recognises a grounded answer untrimmed for
|
|
48
|
+
* `preConsumed`, and the pivot then recognises the same bytes again — the
|
|
49
|
+
* saving inverts into a doubling on the path it was measured for. */
|
|
41
50
|
export function recognise(
|
|
42
51
|
ctx: MindContext,
|
|
43
52
|
bytes: Uint8Array,
|
|
44
|
-
trimmed = false,
|
|
45
53
|
): Recognition {
|
|
46
54
|
// Content-keyed memo — works for both single-turn respond() and multi-turn
|
|
47
55
|
// respondTurn() (where the map persists across calls). ALWAYS consulted,
|
|
@@ -82,7 +90,7 @@ export function recognise(
|
|
|
82
90
|
// not silent), so it is emitted here directly rather than only inside
|
|
83
91
|
// recogniseImpl.
|
|
84
92
|
if (ctx.recogniseMemo) {
|
|
85
|
-
const key =
|
|
93
|
+
const key = latin1Key(bytes);
|
|
86
94
|
const hit = ctx.recogniseMemo.get(key);
|
|
87
95
|
if (hit !== undefined) {
|
|
88
96
|
if (ctx.meter) ctx.meter.recogniseHits++;
|
|
@@ -100,18 +108,14 @@ export function recognise(
|
|
|
100
108
|
);
|
|
101
109
|
return hit;
|
|
102
110
|
}
|
|
103
|
-
const fresh = recogniseImpl(ctx, bytes
|
|
111
|
+
const fresh = recogniseImpl(ctx, bytes);
|
|
104
112
|
ctx.recogniseMemo.set(key, fresh);
|
|
105
113
|
return fresh;
|
|
106
114
|
}
|
|
107
|
-
return recogniseImpl(ctx, bytes
|
|
115
|
+
return recogniseImpl(ctx, bytes);
|
|
108
116
|
}
|
|
109
117
|
|
|
110
|
-
function recogniseImpl(
|
|
111
|
-
ctx: MindContext,
|
|
112
|
-
bytes: Uint8Array,
|
|
113
|
-
trimmed = false,
|
|
114
|
-
): Recognition {
|
|
118
|
+
function recogniseImpl(ctx: MindContext, bytes: Uint8Array): Recognition {
|
|
115
119
|
if (ctx.meter) {
|
|
116
120
|
ctx.meter.recognitions++;
|
|
117
121
|
ctx.meter.recognisedBytes += bytes.length;
|
|
@@ -224,7 +228,7 @@ function recogniseImpl(
|
|
|
224
228
|
// n.kids !== null enforces above) rather than degenerate into
|
|
225
229
|
// single-byte-atom territory, which atomIsHub already governs
|
|
226
230
|
// separately.
|
|
227
|
-
else if (
|
|
231
|
+
else if (end - start - 1 >= 2) {
|
|
228
232
|
// The chunk's own boundary is drawn by content geometry, not by
|
|
229
233
|
// any notion of "form" — it can include one edge byte the query's
|
|
230
234
|
// fold happened to attach here that the trained span never had
|
package/src/mind/resonance.ts
CHANGED
|
@@ -365,13 +365,13 @@ export async function pivotInto(
|
|
|
365
365
|
}
|
|
366
366
|
for (const c of n.kids) queue.push(c); // breadth-first: larger regions first
|
|
367
367
|
}
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
const rec = recognise(ctx, answer
|
|
368
|
+
// THE FULL recognition, memo-shared with every other reader of these bytes.
|
|
369
|
+
// A "skip the edge trims here" variant was refuted (see recognise's own
|
|
370
|
+
// note): those trims are what find a WHOLE trained form embedded at an
|
|
371
|
+
// offset the answer's fold did not cut, and such a form is parentless,
|
|
372
|
+
// container-free and edge-bearing — i.e. exactly what the filter below
|
|
373
|
+
// ADMITS as a pivot, not what it rejects.
|
|
374
|
+
const rec = recognise(ctx, answer);
|
|
375
375
|
for (const s of rec.sites) {
|
|
376
376
|
if (!consumed.has(s.payload) && ctx.store.hasNext(s.payload)) {
|
|
377
377
|
scored.set(s.payload, Math.max(scored.get(s.payload) ?? 0, 1));
|
|
@@ -399,6 +399,12 @@ export async function pivotInto(
|
|
|
399
399
|
let pivotId: number | null = null;
|
|
400
400
|
for (const c of ranked) {
|
|
401
401
|
const id = c.id;
|
|
402
|
+
// A ZERO-LENGTH candidate is not a pivot. `argmaxBy(…, 0, strict)` used to
|
|
403
|
+
// carry this floor in its threshold argument, and dropping it here would
|
|
404
|
+
// admit an empty node: `indexOf(answer, <empty>)` returns 0, so every
|
|
405
|
+
// filter below passes and the chain would hop through nothing (§2.13 —
|
|
406
|
+
// empty bytes are truthy).
|
|
407
|
+
if (c.len === 0) continue;
|
|
402
408
|
// A PIVOT MUST BE A THING THE CORPUS DEPOSITED, NOT A PIECE OF ONE.
|
|
403
409
|
// "Longest wins" ranks candidates but never asks whether the winner is
|
|
404
410
|
// an entity at all, and by the time a chain reaches here `consumeAll`
|