@hviana/sema 0.6.0 → 0.7.2

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.
Files changed (42) hide show
  1. package/.github/workflows/release.yml +80 -0
  2. package/AGENTS.md +53 -9
  3. package/HOW_IT_WORKS.md +17 -16
  4. package/dist/src/meter.d.ts +14 -4
  5. package/dist/src/meter.js +27 -3
  6. package/dist/src/mind/attention.js +22 -20
  7. package/dist/src/mind/graph-search.d.ts +43 -9
  8. package/dist/src/mind/graph-search.js +82 -15
  9. package/dist/src/mind/junction.d.ts +13 -0
  10. package/dist/src/mind/junction.js +26 -1
  11. package/dist/src/mind/mechanisms/cover.js +23 -2
  12. package/dist/src/mind/mechanisms/prefix-completion.d.ts +2 -1
  13. package/dist/src/mind/mechanisms/prefix-completion.js +40 -20
  14. package/dist/src/mind/mechanisms/recall.js +8 -4
  15. package/dist/src/mind/pipeline-mechanism.d.ts +0 -24
  16. package/dist/src/mind/pipeline-mechanism.js +13 -36
  17. package/dist/src/mind/pipeline.d.ts +24 -0
  18. package/dist/src/mind/pipeline.js +71 -5
  19. package/dist/src/mind/recognition.d.ts +15 -1
  20. package/dist/src/mind/recognition.js +15 -1
  21. package/dist/src/mind/resonance.js +54 -12
  22. package/dist/src/store.js +22 -1
  23. package/jsr.json +1 -1
  24. package/package.json +7 -2
  25. package/src/meter.ts +27 -4
  26. package/src/mind/attention.ts +22 -19
  27. package/src/mind/graph-search.ts +93 -16
  28. package/src/mind/junction.ts +25 -1
  29. package/src/mind/mechanisms/cover.ts +23 -4
  30. package/src/mind/mechanisms/prefix-completion.ts +40 -20
  31. package/src/mind/mechanisms/recall.ts +8 -4
  32. package/src/mind/pipeline-mechanism.ts +13 -42
  33. package/src/mind/pipeline.ts +106 -5
  34. package/src/mind/recognition.ts +19 -2
  35. package/src/mind/resonance.ts +84 -49
  36. package/src/store.ts +21 -1
  37. package/test/89-completion-recursion.test.mjs +230 -0
  38. package/test/90-connector-read-cap.test.mjs +130 -0
  39. package/test/91-branch-bytes-cache.test.mjs +152 -0
  40. package/test/93-regime-prediction.test.mjs +148 -0
  41. package/test/94-cross-region-budget.test.mjs +67 -0
  42. package/test/95-wide-resonance-removed.test.mjs +109 -0
@@ -96,6 +96,19 @@ export declare function cachedRead(ctx: MindContext, cache: WalkCache | null, id
96
96
  * edgeAncestors' question and wrong for this one: a junction container
97
97
  * is legitimately reached across many containing structures. Half the
98
98
  * successful junctions would be lost.
99
+ *
100
+ * REFUTED EARLY-STOP (side-cone exhaustion, §2.17's "real saturation"):
101
+ * stopping the walk the moment ONE side's upward cone is emptied is wrong,
102
+ * in both a hub-guarded form and a hub-flagged form. The junction test is
103
+ * a BYTE containment over the UNION of the two cones, and a junction can be
104
+ * structurally reachable from only ONE side — the side whose seed is a
105
+ * FOLD sub-node of the container (test/16: "cold or hot" is reached from
106
+ * the window "cold", but the 3-byte answer "hot" is not a 4-byte window of
107
+ * it, so "hot"'s cone is empty while the junction still lies ahead in
108
+ * "cold"'s cone; test/34's n-ary binding fails the hub-guarded form the
109
+ * same way). "One cone exhausted" therefore never proves "no junction
110
+ * left", and the walk must keep the √N·W budget as its NET after the
111
+ * per-node saturations below.
99
112
  * • per-node hub guards — parent fan-outs beyond √N are hubs (not
100
113
  * expanded); each node contributes at most one √N page of containers;
101
114
  * √N collected candidates decide. */
@@ -134,6 +134,19 @@ function cachedContainers(ctx, cache, id, limit) {
134
134
  * edgeAncestors' question and wrong for this one: a junction container
135
135
  * is legitimately reached across many containing structures. Half the
136
136
  * successful junctions would be lost.
137
+ *
138
+ * REFUTED EARLY-STOP (side-cone exhaustion, §2.17's "real saturation"):
139
+ * stopping the walk the moment ONE side's upward cone is emptied is wrong,
140
+ * in both a hub-guarded form and a hub-flagged form. The junction test is
141
+ * a BYTE containment over the UNION of the two cones, and a junction can be
142
+ * structurally reachable from only ONE side — the side whose seed is a
143
+ * FOLD sub-node of the container (test/16: "cold or hot" is reached from
144
+ * the window "cold", but the 3-byte answer "hot" is not a 4-byte window of
145
+ * it, so "hot"'s cone is empty while the junction still lies ahead in
146
+ * "cold"'s cone; test/34's n-ary binding fails the hub-guarded form the
147
+ * same way). "One cone exhausted" therefore never proves "no junction
148
+ * left", and the walk must keep the √N·W budget as its NET after the
149
+ * per-node saturations below.
137
150
  * • per-node hub guards — parent fan-outs beyond √N are hubs (not
138
151
  * expanded); each node contributes at most one √N page of containers;
139
152
  * √N collected candidates decide. */
@@ -172,7 +185,19 @@ unordered = false) {
172
185
  id,
173
186
  d: 0,
174
187
  }));
175
- while (stack.length > 0 && out.length < bound && b.n-- > 0) {
188
+ while (stack.length > 0 && out.length < bound) {
189
+ // BUDGET EXHAUSTION IS AN ABSTENTION, AND IT MUST BE VISIBLE (§2.13). The
190
+ // walk stops with work still on the stack, the caller reads "no container"
191
+ // and falls through to a lower ladder rung — indistinguishable, from the
192
+ // outside, from a walk that looked everywhere and found nothing. With a
193
+ // SHARED budget (cross-region's one k·W allowance per tier) an EARLIER
194
+ // pair can drain it, so a later pair's exact tier may never run at all;
195
+ // this counter is the only thing that says so.
196
+ if (b.n-- <= 0) {
197
+ if (ctx.meter)
198
+ ctx.meter.junctionBudgetExhausted++;
199
+ break;
200
+ }
176
201
  const { id: x, d } = stack.pop();
177
202
  if (ctx.meter)
178
203
  ctx.meter.junctionPops++;
@@ -42,7 +42,9 @@ export async function resolveConnectors(ctx, sites, query) {
42
42
  // transcript evidence: cover still needs the site for structural context,
43
43
  // but liftAnswer will trim that continuation as already answered. Building
44
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.
45
+ // discarded a semantically neutral gate (it removes work whose product
46
+ // liftAnswer throws away), and a cumulative (multi-turn) query is exactly
47
+ // where such already-answered continuations recur.
46
48
  let answered = 0;
47
49
  const ordered = [...sites]
48
50
  .sort((a, b) => a.start - b.start)
@@ -56,7 +58,26 @@ export async function resolveConnectors(ctx, sites, query) {
56
58
  if (query === undefined || ctx.answeredSpans.length === 0)
57
59
  return true;
58
60
  const continuations = ctx.store.nextFirst(s.payload, hubBound(ctx));
59
- return !continuations.some((answer) => indexOf(query, read(ctx, answer), 0) >= 0);
61
+ return !continuations.some((answer) => {
62
+ // PREFIX-CAPPED (AGENTS §2.8): a candidate longer than the query cannot
63
+ // occur INSIDE it, so read one byte past the query's length — enough to
64
+ // detect the overflow — and reject without reconstructing the rest.
65
+ // The `+ 1` is what makes the test exact rather than a truncation: a
66
+ // result of exactly `query.length + 1` bytes is known to be too long,
67
+ // and anything shorter is the candidate's COMPLETE content, so the
68
+ // substring test below is the same test as before. (The same overflow
69
+ // probe bridge.ts:256 already uses.)
70
+ //
71
+ // This loop runs up to hubBound(ctx) = √N reads PER SITE, and only on a
72
+ // multi-turn response — `answeredSpans` is empty for a plain respond(),
73
+ // so the probe does not execute there. The cap cannot reduce the read
74
+ // COUNT — only a semantic change to the "already answered" test could —
75
+ // but it bounds each read by the query instead of by the corpus, which
76
+ // is what §2.8 asks for and what rescues a SHORT query: at 3 bytes this
77
+ // reads 4 bytes per candidate instead of the ~231 it averaged before.
78
+ const bytes = read(ctx, answer, query.length + 1);
79
+ return bytes.length <= query.length && indexOf(query, bytes, 0) >= 0;
80
+ });
60
81
  });
61
82
  const bridgePair = async (l, r) => {
62
83
  if (l === r || links.has(l + "," + r))
@@ -16,7 +16,8 @@ export interface PrefixCompletion {
16
16
  * it, when the continuation is sub-quantum, when a candidate's continuation
17
17
  * cannot be read through, or when the candidates disagree.
18
18
  *
19
- * `ranked` must be a list the caller has ALREADY fetched; this mechanism never
19
+ * `ranked` must be a list the caller has ALREADY fetched (the write side's
20
+ * window index, or the response's memoised top-k); this mechanism never
20
21
  * resonates on its own (see the header's cost note). */
21
22
  export declare function prefixCompletion(ctx: MindContext, query: Uint8Array, ranked: ReadonlyArray<number>): PrefixCompletion | null;
22
23
  export declare const prefixMechanism: PipelineMechanism;
@@ -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. With `exhaustive` it ranks 8.
48
+ // so no k recovers it.
49
49
  //
50
- // So this is a RETRIEVABILITY gap, not a semantic one, and it is repaired by
51
- // reading the candidate list recall's refusal path has ALREADY fetched
52
- // exhaustively for the substitution bridgenever by resonating on its own.
53
- // Measured cost of the scan over those 570 candidates: 2.9 ms warm, 20.4 ms
54
- // cold, against a ~700 ms refusal path. Issuing a FRESH exhaustive call would
55
- // cost 490 ms median against 13 ms non-exhaustive (36×), which is why this tier
56
- // takes the candidate list as an argument and adds nothing to it.
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
  //
@@ -97,7 +105,8 @@ import { STEP } from "../graph-search.js";
97
105
  * it, when the continuation is sub-quantum, when a candidate's continuation
98
106
  * cannot be read through, or when the candidates disagree.
99
107
  *
100
- * `ranked` must be a list the caller has ALREADY fetched; this mechanism never
108
+ * `ranked` must be a list the caller has ALREADY fetched (the write side's
109
+ * window index, or the response's memoised top-k); this mechanism never
101
110
  * resonates on its own (see the header's cost note). */
102
111
  export function prefixCompletion(ctx, query, ranked) {
103
112
  const W = ctx.space.maxGroup;
@@ -204,9 +213,9 @@ export const prefixMechanism = {
204
213
  provenance: "prefix",
205
214
  async floor(ctx, query, _pre, worthRunning) {
206
215
  // One projection: the form is voiced whole, nothing is substituted.
207
- // INVESTMENT DISCIPLINE — the supplies below are the response's wide
208
- // candidate list and a bounded √N walk, so neither is touched until the
209
- // bound can still beat the incumbent.
216
+ // INVESTMENT DISCIPLINE — the supplies below are a bounded √N window walk
217
+ // and the response's memoised top-k resonance read, so neither is touched
218
+ // until the bound can still beat the incumbent.
210
219
  if (!worthRunning(STEP))
211
220
  return STEP;
212
221
  // A query with no room for a perceivable continuation inside the phrase
@@ -218,14 +227,25 @@ export const prefixMechanism = {
218
227
  return STEP;
219
228
  },
220
229
  async run(ctx, query, pre) {
221
- // The response's shared wide list first; only when it supplies nothing does
222
- // the write side's window index propose. That ordering is the whole cost
223
- // story: a query the ranked list can already explain pays not one extra
224
- // read, and the bounded walk is spent only where the alternative is an
225
- // empty answer. A second SUPPLY, not a second mechanism — the same three
226
- // guards decide either way.
227
- const completed = prefixCompletion(ctx, query, await pre.wideResonance()) ??
228
- prefixCompletion(ctx, query, formsOpenedBy(ctx, query));
230
+ // ONE SUPPLY PASS, not a two-tier `??`. The window index (exact,
231
+ // content-addressed) and the response's memoised top-k (approximate) are
232
+ // concatenated and the three guards decide ONCE over the union. A
233
+ // first-then-fallback chain would let the APPROXIMATE tier override the
234
+ // EXACT one (§2.3): when formsOpenedBy finds two continuations, guard 3
235
+ // returns null and the fallback re-runs the guards on resonance's top-k
236
+ // alone which, seeing only one of the two forms, would voice it. That is
237
+ // precisely the disagreement-suppression guard 3 exists to prevent, and it
238
+ // is the exact tier's ambiguity being washed away by the approximate tier.
239
+ // Evaluating the union means a disagreement the window index saw can never
240
+ // be hidden by what the ANN happens to rank. The ANN read is the
241
+ // response's ONE memoised top-k (§2.11), already paid by recall's refusal
242
+ // path on the queries where this mechanism fires, so reading it here is not
243
+ // a second index scan.
244
+ const ids = [
245
+ ...formsOpenedBy(ctx, query),
246
+ ...(await pre.resonance()).map((h) => h.id),
247
+ ];
248
+ const completed = prefixCompletion(ctx, query, ids);
229
249
  if (completed === null)
230
250
  return [];
231
251
  return [{
@@ -306,10 +306,14 @@ export async function recallByResonance(ctx, query, pre) {
306
306
  }
307
307
  }
308
308
  // 3b. Corroborated-substitution bridge — refusal-path only (bridge.ts).
309
- // The WIDE candidate list every past-the-top-k mechanism reads lives on
310
- // Precomputed (see wideResonance): shared across the whole response, so the
311
- // exhaustive branch runs at most once whoever first-touches it.
312
- const wideIds = () => pre.wideResonance();
309
+ // The bridge's proposal source is the response's ONE top-k read the same
310
+ // list recall already ranked above never an exhaustive √N scan. The
311
+ // bridge's own candidate cap is 2·recallQueryK, so top-k proposals are
312
+ // exactly the budget it can consume, and every proposal is byte-verified
313
+ // downstream (§2.3). Reuse the memoised `resonance()`; scanning every IVF
314
+ // cluster here once made every honest refusal cost hundreds of ms regardless
315
+ // of k.
316
+ const wideIds = async () => (await pre.resonance()).map((h) => h.id);
313
317
  // Every gist-based tier has failed; before refusing, align the query
314
318
  // byte-for-byte against the trained contexts its own stored windows
315
319
  // anchor, accepting mismatches only as corpus-attested, concept-bar
@@ -63,30 +63,6 @@ export declare class Precomputed {
63
63
  * duplication a profile shows as doubled `annVectorReads` with nothing to
64
64
  * account for it. Cached BY PROMISE, so a second caller awaits the first. */
65
65
  resonance(): Promise<ReadonlyArray<Hit>>;
66
- private _wide?;
67
- /** The response's WIDE candidate list — the top-k when the query's gist has
68
- * no concept-level match anywhere, and an exhaustive √N read when it does.
69
- *
70
- * Every mechanism that has to look PAST the top-k reads this one list: the
71
- * substitution bridge, prefix completion and the frame filler all did, and
72
- * it was memoised inside recall for exactly that reason (measured: 490 ms
73
- * median re-issued against 13 ms non-exhaustive, 36x). A memo inside one
74
- * mechanism only serves that mechanism's own tiers, so it lives here now —
75
- * the same move `resonance` made for the top-k.
76
- *
77
- * THE CONDITION IS THE TOP HIT'S SCORE, NOT THE CORPUS SIZE. When nothing
78
- * ranks at concept level, an exhaustive ANN only scores more vectors below
79
- * the bar (profiled at 38K–40K annVectorReads per refusing query on a 325K-
80
- * context store); the structural channels — junction walks, anchor climbs,
81
- * the write side's window index — are the correct proposal source there,
82
- * because the ANN cannot propose what the gist cannot rank. This was once
83
- * spelled `corpusN(ctx) <= (k · W)³`, which asks a different question and
84
- * answers it wrongly at exactly the scale it was written from: at N =
85
- * 325,608 with k = 24 and W = 4 the cube is 884,736, so that store took the
86
- * exhaustive branch — the very branch measured above. Measured cost of the
87
- * mismatch: substitutionBridge 8,544 ms of a 19,548 ms think (44%), against
88
- * 1,248 ms and 14,218 ms without it, every answer byte-identical. */
89
- wideResonance(): Promise<ReadonlyArray<number>>;
90
66
  private _frames?;
91
67
  /** THE FRAME INVENTORY — every ranked candidate that reads as an instance of
92
68
  * the same frame as the query, each with the query spans it leaves VARIABLE
@@ -13,12 +13,12 @@
13
13
  // 4. TRAVELING EVIDENCE — run() returns MechanismResult with accounted, moves,
14
14
  // and unexplained. The pipeline computes the weight.
15
15
  import { indexOf } from "../bytes.js";
16
- import { conceptThreshold, dominates } from "../geometry.js";
16
+ import { dominates } from "../geometry.js";
17
17
  import { windowIds } from "./canonical.js";
18
18
  import { read, resolve } from "./primitives.js";
19
19
  import { alignGraded, frameSlots, skillExemplar, } from "./match.js";
20
20
  import { climbAttentionAll } from "./attention.js";
21
- import { hubBound, sharedReachMemo } from "./traverse.js";
21
+ import { sharedReachMemo } from "./traverse.js";
22
22
  // ── Precomputed ──────────────────────────────────────────────────────────────
23
23
  //
24
24
  // Precomputed is a LAZY container for structural analyses of the query — the
@@ -128,40 +128,17 @@ export class Precomputed {
128
128
  resonance() {
129
129
  return this._resonance ??= this.shared("resonance", () => this.ctx.store.resonate(this.guide, this.k));
130
130
  }
131
- _wide;
132
- /** The response's WIDE candidate list — the top-k when the query's gist has
133
- * no concept-level match anywhere, and an exhaustive √N read when it does.
134
- *
135
- * Every mechanism that has to look PAST the top-k reads this one list: the
136
- * substitution bridge, prefix completion and the frame filler all did, and
137
- * it was memoised inside recall for exactly that reason (measured: 490 ms
138
- * median re-issued against 13 ms non-exhaustive, 36x). A memo inside one
139
- * mechanism only serves that mechanism's own tiers, so it lives here now —
140
- * the same move `resonance` made for the top-k.
141
- *
142
- * THE CONDITION IS THE TOP HIT'S SCORE, NOT THE CORPUS SIZE. When nothing
143
- * ranks at concept level, an exhaustive ANN only scores more vectors below
144
- * the bar (profiled at 38K–40K annVectorReads per refusing query on a 325K-
145
- * context store); the structural channels — junction walks, anchor climbs,
146
- * the write side's window index — are the correct proposal source there,
147
- * because the ANN cannot propose what the gist cannot rank. This was once
148
- * spelled `corpusN(ctx) <= (k · W)³`, which asks a different question and
149
- * answers it wrongly at exactly the scale it was written from: at N =
150
- * 325,608 with k = 24 and W = 4 the cube is 884,736, so that store took the
151
- * exhaustive branch — the very branch measured above. Measured cost of the
152
- * mismatch: substitutionBridge 8,544 ms of a 19,548 ms think (44%), against
153
- * 1,248 ms and 14,218 ms without it, every answer byte-identical. */
154
- wideResonance() {
155
- return this._wide ??= this.shared("wideResonance", async () => {
156
- const hits = await this.resonance();
157
- if (hits.length > 0 &&
158
- hits[0].score >= conceptThreshold(this.ctx.store.D)) {
159
- const exhaustive = await this.ctx.store.resonate(this.guide, hubBound(this.ctx), true);
160
- return exhaustive.map((h) => h.id);
161
- }
162
- return hits.map((h) => h.id);
163
- });
164
- }
131
+ // REMOVED — the WIDE exhaustive-√N resonance list (`wideResonance`). It ran
132
+ // `resonate(guide, √N, exhaustive=true)` whenever the top hit cleared
133
+ // conceptThreshold, so consumers could look "past the top-k". Every consumer
134
+ // only ever needed ≤ 2·recallQueryK proposals (the substitution bridge's own
135
+ // candidate cap) or a content-addressed answer (prefix completion's
136
+ // formsOpenedBy), and every proposal is byte-verified downstream (§2.3), so
137
+ // the exhaustive scan bought recall at O(index) cost for an O(k) need
138
+ // measured: 244K annVectorReads per refusing query, ~1.5 s, every answer
139
+ // byte-identical to a top-k read. The two consumers now read `resonance()`
140
+ // (the one top-k read) and the write side's window index respectively — see
141
+ // recall.ts and prefix-completion.ts.
165
142
  _frames;
166
143
  /** THE FRAME INVENTORY — every ranked candidate that reads as an instance of
167
144
  * the same frame as the query, each with the query spans it leaves VARIABLE
@@ -37,6 +37,30 @@ export interface NarrowDecisionData {
37
37
  version: 1;
38
38
  margin: number;
39
39
  }
40
+ /** Structured payload of the "regimePrediction" rationale step — the R8
41
+ * observation exposed as data. After the first mechanism (cover, which §2.6
42
+ * runs first) grounds or abstains, the market's whole outcome is already
43
+ * determined by the one cost ladder: the consensus climb runs exactly when
44
+ * `worthRunning(2 * STEP)` is true — CAST (floor 2·STEP) is the cheapest
45
+ * mechanism that first-touches it, and confluence (3·STEP) / extraction
46
+ * (CONCEPT+STEP) are only reached after CAST is. An incumbent at or below
47
+ * that floor prunes CAST and, with it, the climb (retrieval); anything above
48
+ * — or no incumbent — runs the full market and the climb (composition).
49
+ * Purely observational; never read by inference. */
50
+ export interface RegimePredictionData {
51
+ version: 1;
52
+ /** retrieval | composition — the two regimes R1 measured as a ~100× cost
53
+ * step. */
54
+ regime: "retrieval" | "composition";
55
+ /** The incumbent's grade once the first mechanism's turn is over (it ran, or
56
+ * it was skipped), or null when nothing has grounded — `best === null`,
57
+ * which is composition with no incumbent. */
58
+ incumbentGrade: number | null;
59
+ /** The cheapest composition floor in grade units (`grade(2 * STEP)` = 2,
60
+ * CAST's floor) — the bar the incumbent must sit at or below for the
61
+ * consensus climb to be skipped. */
62
+ climbFloorGrade: number;
63
+ }
40
64
  /** Think: a single lightest-derivation exploration of the Sema graph.
41
65
  *
42
66
  * Every answer travels the same path:
@@ -92,9 +92,21 @@ export async function think(ctx, query, mechs) {
92
92
  };
93
93
  // ── Pre-computation ──────────────────────────────────────────────────
94
94
  const mechanisms = mechs ?? defaultMechanisms;
95
- const rec = recognise(ctx, query);
95
+ const meter = ctx.meter;
96
+ // recognition is a shared analysis (§2.14 contract 5): it does the query's
97
+ // own store work (perceive → foldTree → resolve), which used to land in
98
+ // `think` and in nothing narrower — the meter's one accounting surface must
99
+ // charge it to itself, exactly as attention/weave/resonance are charged.
100
+ // SYNCHRONOUS phase: recognition is on the sync side of §2.10's seam, so it
101
+ // is timed with `timeSync` — wrapping it in a promise would make a profiled
102
+ // response await where an unprofiled one does not.
103
+ const rec = meter
104
+ ? meter.timeSync("recognise", () => recognise(ctx, query))
105
+ : recognise(ctx, query);
96
106
  // Phase 1: collect computed spans from mechanisms that implement parse()
97
- const computed = await collectComputed(ctx, mechanisms, query);
107
+ const computed = meter
108
+ ? await meter.time("collectComputed", () => collectComputed(ctx, mechanisms, query))
109
+ : await collectComputed(ctx, mechanisms, query);
98
110
  if (computed.length > 0) {
99
111
  ctx.trace?.step("computeExtensions", [rItem(query, "query")], computed.map((u) => rItem(query.subarray(u.i, u.j), "operand", undefined, [u.i, u.j])), `extensions recognised and evaluated ${computed.length} computation(s)`);
100
112
  for (const u of computed) {
@@ -107,6 +119,9 @@ export async function think(ctx, query, mechs) {
107
119
  // method on Precomputed, first-touched by whichever mechanism's floor
108
120
  // survives its cheap gates and the worthRunning check. A query no
109
121
  // mechanism climbs for (e.g. one an extension decided) never climbs.
122
+ // NOT phased: the constructor itself is trivial (it only derives `k`), so a
123
+ // phase here would add a zero-work entry to every profiled report — the meter
124
+ // attributes WORK (§2.14); the trace already represents structure.
110
125
  const pre = new Precomputed(ctx, query, rec, computed, ctx._edgeGuide);
111
126
  const grade = (w) => Math.floor(w / STEP);
112
127
  const unaccounted = (spans) => unexplainedSpans(query.length, spans)
@@ -151,12 +166,59 @@ export async function think(ctx, query, mechs) {
151
166
  best = c;
152
167
  };
153
168
  const worthRunning = (floor) => best === null || grade(floor) < grade(best.weight);
169
+ // REGIME PREDICTION (R8) — observational only. Once the FIRST mechanism has
170
+ // had its turn (cover, which §2.6 places first and floors at 0), the market's
171
+ // outcome is already determined by the one cost ladder: the consensus climb
172
+ // runs exactly when `worthRunning(2 * STEP)` is true — CAST (floor 2·STEP) is
173
+ // the cheapest mechanism that first-touches it, so an incumbent at or below
174
+ // grade 2 prunes CAST and, with it, confluence (3·STEP) and extraction
175
+ // (CONCEPT+STEP) (retrieval); anything above — or no incumbent — runs the
176
+ // full market and the climb (composition). The predicate is `worthRunning`,
177
+ // the same function the loop itself uses — nothing is computed here that the
178
+ // engine had not already computed, and nothing is read back by inference.
179
+ //
180
+ // EMITTED BEFORE THE SECOND MECHANISM'S FLOOR, never after some mechanism's
181
+ // run: a "prediction" published after the fact could assert "the climb will
182
+ // not run" about a climb that already ran — which is what happens whenever
183
+ // the first mechanism is SKIPPED (null floor or pruned) and the block sits at
184
+ // the end of the first mechanism that actually ran. Emitting on entry to
185
+ // iteration 1 makes the claim true by construction, whatever the first
186
+ // mechanism did, and keeps the payload identical on the ordinary path (the
187
+ // incumbent cannot change between the two positions).
188
+ let regimeReported = false;
189
+ const reportRegime = () => {
190
+ if (regimeReported)
191
+ return;
192
+ regimeReported = true;
193
+ const climbFloorGrade = grade(2 * STEP);
194
+ // TS narrows `best` to null in the outer flow (it cannot see the closure
195
+ // assignments in `consider`) — cast back, the same read-back as `decided`
196
+ // below.
197
+ const incumbent = best;
198
+ const incumbentGrade = incumbent === null ? null : grade(incumbent.weight);
199
+ const regime = worthRunning(2 * STEP)
200
+ ? "composition"
201
+ : "retrieval";
202
+ ctx.trace?.step("regimePrediction", [rItem(query, "query")], [], regime === "retrieval"
203
+ ? `retrieval regime — incumbent grade ${incumbentGrade} ≤ climb floor ${climbFloorGrade}, ` +
204
+ `so no mechanism floored above that grade runs; the consensus climb will not run`
205
+ : `composition regime — ${incumbentGrade === null
206
+ ? "no incumbent (nothing grounded)"
207
+ : `incumbent grade ${incumbentGrade}`} above climb floor ${climbFloorGrade}, so the full market and climb run`, undefined, {
208
+ version: 1,
209
+ regime,
210
+ incumbentGrade,
211
+ climbFloorGrade,
212
+ });
213
+ };
154
214
  // Phase 3: grounding loop
155
215
  // Per-mechanism accounting (src/meter.ts). The market's whole premise is
156
216
  // that mechanisms compete on one cost scale — so the profiling read-out is
157
217
  // also per-mechanism, uniformly: the loop never asks which one it holds.
158
- const meter = ctx.meter;
159
- for (const mech of mechanisms) {
218
+ for (let mi = 0; mi < mechanisms.length; mi++) {
219
+ const mech = mechanisms[mi];
220
+ if (mi > 0)
221
+ reportRegime();
160
222
  const floor = meter
161
223
  ? await meter.time(`${mech.name}.floor`, () => mech.floor(ctx, query, pre, worthRunning))
162
224
  : await mech.floor(ctx, query, pre, worthRunning);
@@ -193,6 +255,10 @@ export async function think(ctx, query, mechs) {
193
255
  });
194
256
  }
195
257
  }
258
+ // A market of ONE mechanism never reaches iteration 1; the step is still
259
+ // emitted exactly once per think(), so a consumer never has to ask whether
260
+ // the list was long enough for the prediction to exist.
261
+ reportRegime();
196
262
  // (TS cannot see the closure assignments into `best` and narrows it to its
197
263
  // initial null, so the read-back needs the assertion.)
198
264
  const decided = best;
@@ -337,7 +403,7 @@ export async function think(ctx, query, mechs) {
337
403
  ? reasoned
338
404
  : meter
339
405
  ? await meter.time("fuse", () => fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans))
340
- : await fuseAttention(ctx, query, reasoned, pre, unclimbed, decided.accounted);
406
+ : await fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans);
341
407
  done(fused, "grounded, reasoned forward, fused across points of attention");
342
408
  return { bytes: fused, provenance };
343
409
  }
@@ -10,7 +10,21 @@ import type { MindContext, Recognition, Segment } from "./types.js";
10
10
  * the longest known leaf, chained into flat branches. Names forms the
11
11
  * query's own cut cannot, and records sub-leaf boundaries as `splits`.
12
12
  *
13
- * Both O(n · maxGroup) bounded O(1) probes — never a scan of the corpus. */
13
+ * Both O(n · maxGroup) bounded O(1) probes — never a scan of the corpus.
14
+ *
15
+ * ONE READING PER BYTE STREAM, deliberately: there is no "cheap mode" that
16
+ * skips the edge-trim fallbacks. A `trimmed` variant was tried and REFUTED
17
+ * twice over. Its premise — "the trims only recover misaligned FRAGMENTS, so
18
+ * a consumer whose gate rejects fragments loses nothing" — is false: the
19
+ * left/right trim loops below exist precisely to find WHOLE trained forms
20
+ * embedded at an offset the query's own fold did not cut, and such a form has
21
+ * no structural parents or containers, so it passes the pivot's fragment gate
22
+ * and is exactly the candidate a multi-hop chain steps through. Skipping them
23
+ * narrows the pivot's evidence silently. And a per-caller variant has to key
24
+ * the memo by the variant, which breaks the "computed at most once" property
25
+ * (§2.11): the pipeline recognises a grounded answer untrimmed for
26
+ * `preConsumed`, and the pivot then recognises the same bytes again — the
27
+ * saving inverts into a doubling on the path it was measured for. */
14
28
  export declare function recognise(ctx: MindContext, bytes: Uint8Array): Recognition;
15
29
  /** Segment bytes using the geometry's own groupings — leaf-parent
16
30
  * nodes from the perceived tree, with consecutive bare leaves merged
@@ -21,7 +21,21 @@ import { isChunk } from "../sema.js";
21
21
  * the longest known leaf, chained into flat branches. Names forms the
22
22
  * query's own cut cannot, and records sub-leaf boundaries as `splits`.
23
23
  *
24
- * Both O(n · maxGroup) bounded O(1) probes — never a scan of the corpus. */
24
+ * Both O(n · maxGroup) bounded O(1) probes — never a scan of the corpus.
25
+ *
26
+ * ONE READING PER BYTE STREAM, deliberately: there is no "cheap mode" that
27
+ * skips the edge-trim fallbacks. A `trimmed` variant was tried and REFUTED
28
+ * twice over. Its premise — "the trims only recover misaligned FRAGMENTS, so
29
+ * a consumer whose gate rejects fragments loses nothing" — is false: the
30
+ * left/right trim loops below exist precisely to find WHOLE trained forms
31
+ * embedded at an offset the query's own fold did not cut, and such a form has
32
+ * no structural parents or containers, so it passes the pivot's fragment gate
33
+ * and is exactly the candidate a multi-hop chain steps through. Skipping them
34
+ * narrows the pivot's evidence silently. And a per-caller variant has to key
35
+ * the memo by the variant, which breaks the "computed at most once" property
36
+ * (§2.11): the pipeline recognises a grounded answer untrimmed for
37
+ * `preConsumed`, and the pivot then recognises the same bytes again — the
38
+ * saving inverts into a doubling on the path it was measured for. */
25
39
  export function recognise(ctx, bytes) {
26
40
  // Content-keyed memo — works for both single-turn respond() and multi-turn
27
41
  // respondTurn() (where the map persists across calls). ALWAYS consulted,
@@ -9,7 +9,7 @@ import { mergeThreshold } from "../geometry.js";
9
9
  import { concat2, concatBytes, indexOf } from "../bytes.js";
10
10
  import { gistOf, read, resolve, walkTree } from "./primitives.js";
11
11
  import { perceive } from "./primitives.js";
12
- import { argmaxBy, argmaxCosine, candidateGist, hubBound } from "./traverse.js";
12
+ import { argmaxCosine, candidateGist, hubBound } from "./traverse.js";
13
13
  import { cachedRead, junctionContainers, junctionSynonyms, walkCache, } from "./junction.js";
14
14
  import { recognise } from "./recognition.js";
15
15
  // ── The bridge — the junction between two adjacent results ──────────────────
@@ -288,6 +288,12 @@ export async function pivotInto(ctx, answer, consumed, voiced = []) {
288
288
  for (const c of n.kids)
289
289
  queue.push(c); // breadth-first: larger regions first
290
290
  }
291
+ // THE FULL recognition, memo-shared with every other reader of these bytes.
292
+ // A "skip the edge trims here" variant was refuted (see recognise's own
293
+ // note): those trims are what find a WHOLE trained form embedded at an
294
+ // offset the answer's fold did not cut, and such a form is parentless,
295
+ // container-free and edge-bearing — i.e. exactly what the filter below
296
+ // ADMITS as a pivot, not what it rejects.
291
297
  const rec = recognise(ctx, answer);
292
298
  for (const s of rec.sites) {
293
299
  if (!consumed.has(s.payload) && ctx.store.hasNext(s.payload)) {
@@ -296,7 +302,33 @@ export async function pivotInto(ctx, answer, consumed, voiced = []) {
296
302
  }
297
303
  // Byte containment, longest wins — the answer literally contains the
298
304
  // pivot's bytes, and the biggest well-evidenced span is the real pivot.
299
- const found = argmaxBy(scored.keys(), (id) => {
305
+ //
306
+ // REAL SATURATION, not a hard cap: the score IS the candidate's byte
307
+ // length, so the scan is DECIDED the moment the first candidate that passes
308
+ // every filter is found in DESCENDING length order — a shorter candidate can
309
+ // never outscore it. `contentLen` (the prefix-capped length read, §2.8) is
310
+ // the cheap ordering key, and the first-inserted tie-break is made explicit
311
+ // (`a.index - b.index`) so equal lengths keep `scored`'s insertion order —
312
+ // exactly the tie argmaxBy(strict) used to keep. The bytes of at most ONE
313
+ // winning candidate are read; every shorter candidate the probes proposed is
314
+ // skipped without reconstruction, where the old argmax read them all.
315
+ const ranked = [...scored.keys()]
316
+ .map((id, index) => ({
317
+ id,
318
+ index,
319
+ len: ctx.store.contentLen(id, answer.length + 1),
320
+ }))
321
+ .sort((a, b) => b.len - a.len || a.index - b.index);
322
+ let pivotId = null;
323
+ for (const c of ranked) {
324
+ const id = c.id;
325
+ // A ZERO-LENGTH candidate is not a pivot. `argmaxBy(…, 0, strict)` used to
326
+ // carry this floor in its threshold argument, and dropping it here would
327
+ // admit an empty node: `indexOf(answer, <empty>)` returns 0, so every
328
+ // filter below passes and the chain would hop through nothing (§2.13 —
329
+ // empty bytes are truthy).
330
+ if (c.len === 0)
331
+ continue;
300
332
  // A PIVOT MUST BE A THING THE CORPUS DEPOSITED, NOT A PIECE OF ONE.
301
333
  // "Longest wins" ranks candidates but never asks whether the winner is
302
334
  // an entity at all, and by the time a chain reaches here `consumeAll`
@@ -330,18 +362,28 @@ export async function pivotInto(ctx, answer, consumed, voiced = []) {
330
362
  // what `parents`/`containers` record. Reasoning steps THROUGH a fact;
331
363
  // a span that was never a fact on its own is not one to step through.
332
364
  // No constant enters — it is a structural predicate, not a threshold.
333
- if (ctx.store.hasParents(id) || ctx.store.hasContainers(id)) {
334
- return -Infinity;
335
- }
365
+ if (ctx.store.hasParents(id) || ctx.store.hasContainers(id))
366
+ continue;
367
+ // A candidate whose bytes are LONGER than the answer cannot be a
368
+ // substring of it — `indexOf` would return −1 regardless. Prune by
369
+ // length BEFORE reconstructing the bytes: `read` is an UNCAPPED read
370
+ // (AGENTS §2.8), and a resonated context far longer than the answer is
371
+ // exactly the candidate that makes it cost a whole deposit's worth of
372
+ // reconstruction for a containment test that must fail. `contentLen`
373
+ // with the `answer.length + 1` cap is the prefix-capped length read the
374
+ // same contract prescribes; the prune is byte-identical to the old
375
+ // `indexOf` miss (it returns −1 for a needle longer than the haystack).
376
+ if (c.len > answer.length)
377
+ continue;
336
378
  const bytes = read(ctx, id);
337
379
  if (indexOf(answer, bytes, 0) < 0)
338
- return -Infinity;
339
- for (const v of voiced)
340
- if (indexOf(v, bytes, 0) >= 0)
341
- return -Infinity;
342
- return bytes.length;
343
- }, 0, true);
344
- return found?.item ?? null;
380
+ continue;
381
+ if (voiced.some((v) => indexOf(v, bytes, 0) >= 0))
382
+ continue;
383
+ pivotId = id;
384
+ break;
385
+ }
386
+ return pivotId;
345
387
  }
346
388
  /** Which of the given labelled forms a span MEANS — generic resonance over
347
389
  * perceived gists. Each anchor form's gist is memoised; the span's gist