@hviana/sema 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/src/geometry.d.ts +28 -0
  2. package/dist/src/geometry.js +35 -0
  3. package/dist/src/mind/articulation.js +1 -1
  4. package/dist/src/mind/attention.d.ts +4 -0
  5. package/dist/src/mind/attention.js +65 -12
  6. package/dist/src/mind/graph-search.d.ts +16 -1
  7. package/dist/src/mind/graph-search.js +34 -5
  8. package/dist/src/mind/learning.d.ts +1 -1
  9. package/dist/src/mind/learning.js +20 -5
  10. package/dist/src/mind/mechanisms/alu.js +8 -1
  11. package/dist/src/mind/mechanisms/cast.js +35 -11
  12. package/dist/src/mind/mechanisms/cover.js +40 -1
  13. package/dist/src/mind/mechanisms/extraction.js +75 -30
  14. package/dist/src/mind/mechanisms/recall.js +26 -2
  15. package/dist/src/mind/mind.d.ts +3 -2
  16. package/dist/src/mind/mind.js +28 -54
  17. package/dist/src/mind/pipeline.js +34 -2
  18. package/dist/src/mind/primitives.d.ts +16 -9
  19. package/dist/src/mind/primitives.js +58 -22
  20. package/dist/src/mind/reasoning.d.ts +9 -1
  21. package/dist/src/mind/reasoning.js +26 -4
  22. package/dist/src/mind/recognition.js +30 -6
  23. package/dist/src/mind/traverse.js +15 -0
  24. package/dist/src/mind/types.d.ts +58 -10
  25. package/dist/src/mind/types.js +15 -0
  26. package/package.json +1 -1
  27. package/src/geometry.ts +61 -1
  28. package/src/mind/articulation.ts +1 -0
  29. package/src/mind/attention.ts +80 -12
  30. package/src/mind/graph-search.ts +59 -2
  31. package/src/mind/learning.ts +20 -3
  32. package/src/mind/mechanisms/alu.ts +8 -1
  33. package/src/mind/mechanisms/cast.ts +46 -8
  34. package/src/mind/mechanisms/cover.ts +46 -0
  35. package/src/mind/mechanisms/extraction.ts +101 -40
  36. package/src/mind/mechanisms/recall.ts +30 -2
  37. package/src/mind/mind.ts +38 -61
  38. package/src/mind/pipeline.ts +37 -2
  39. package/src/mind/primitives.ts +71 -29
  40. package/src/mind/reasoning.ts +26 -3
  41. package/src/mind/recognition.ts +28 -5
  42. package/src/mind/traverse.ts +18 -0
  43. package/src/mind/types.ts +73 -10
  44. package/test/35-attention-confidence.test.mjs +139 -0
@@ -96,6 +96,18 @@ export type GItem =
96
96
  cover: boolean;
97
97
  rec: boolean;
98
98
  node?: number;
99
+ /** Set only for a {@link ComputedResult} axiom (an extension's derived
100
+ * value, e.g. ALU arithmetic) — as opposed to a genuinely RECOGNISED
101
+ * learned form. Both set `rec: true` (a computation bridges the cover
102
+ * exactly like a learned terminal answer), but they mean different
103
+ * things for `i..j`'s WIDTH: a recognised form's query-span width is
104
+ * real evidence of how much of the query's meaning it accounts for
105
+ * (liftAnswer's half-dominance framing decision); a computed span's
106
+ * width is operand digit-count, uncorrelated with meaning ("1000 - 421"
107
+ * is wider than "15 * 7" only because the numbers are bigger, not
108
+ * because subtraction is more "the point" of its query). See
109
+ * {@link liftAnswer}. */
110
+ computed?: boolean;
99
111
  };
100
112
  type OutItem = Extract<GItem, { kind: "out" }>;
101
113
 
@@ -138,6 +150,9 @@ export interface Seg {
138
150
  bytes: Uint8Array;
139
151
  rec: boolean;
140
152
  node?: number;
153
+ /** See the `computed` field of the "out" {@link GItem} — set only for an
154
+ * extension's derived value, never a genuinely recognised learned form. */
155
+ computed?: boolean;
141
156
  }
142
157
 
143
158
  /** Read the chosen spans back off a derivation: the goal is a chain of bridge
@@ -155,6 +170,7 @@ function readCover(derivation: Derivation<GItem>): Seg[] {
155
170
  bytes: out.bytes,
156
171
  rec: out.rec,
157
172
  node: out.node,
173
+ computed: out.computed,
158
174
  });
159
175
  }
160
176
  node = node.premises[0];
@@ -356,6 +372,7 @@ export class GraphSearch {
356
372
  conceptTarget: ReadonlyMap<number, number>,
357
373
  leaves: ReadonlyArray<Leaf>,
358
374
  splits: ReadonlySet<number>,
375
+ starts: ReadonlySet<number>,
359
376
  substitutions?: ReadonlyMap<number, Uint8Array>,
360
377
  connectors?: ReadonlyMap<string, Uint8Array>,
361
378
  computedResults?: ReadonlyArray<ComputedResult>,
@@ -377,6 +394,7 @@ export class GraphSearch {
377
394
  sites,
378
395
  leaves,
379
396
  splits,
397
+ starts,
380
398
  },
381
399
  conceptTarget,
382
400
  substitutions,
@@ -405,6 +423,7 @@ export class GraphSearch {
405
423
  sites: ReadonlyArray<Site>;
406
424
  leaves: ReadonlyArray<Leaf>;
407
425
  splits: ReadonlySet<number>;
426
+ starts: ReadonlySet<number>;
408
427
  },
409
428
  conceptTarget: ReadonlyMap<number, number>,
410
429
  substitutions?: ReadonlyMap<number, Uint8Array>,
@@ -418,6 +437,7 @@ export class GraphSearch {
418
437
  conceptTarget,
419
438
  recognition.leaves,
420
439
  recognition.splits,
440
+ recognition.starts,
421
441
  substitutions,
422
442
  connectors,
423
443
  computedResults,
@@ -448,11 +468,22 @@ export class GraphSearch {
448
468
  conceptTarget: ReadonlyMap<number, number>,
449
469
  leaves: ReadonlyArray<Leaf>,
450
470
  splits: ReadonlySet<number>,
471
+ starts: ReadonlySet<number>,
451
472
  substitutions?: ReadonlyMap<number, Uint8Array>,
452
473
  connectors?: ReadonlyMap<string, Uint8Array>,
453
474
  computedResults?: ReadonlyArray<ComputedResult>,
454
475
  ): DeductionSystem<GItem> {
455
476
  const W = this.maxGroup; // fusible span ceiling (shortest composite bound)
477
+ // Same corpus-scale hub floor {@link atomIsHub}/{@link atomReach} (traverse.ts)
478
+ // derive for byte atoms — duplicated here rather than imported because this
479
+ // module is deliberately host-based (no MindContext), and both inputs
480
+ // (maxGroup, edgeSourceCount) are already in scope with no ctx needed. If
481
+ // the formula ever changes, it must change in BOTH places (see canonical.ts's
482
+ // header for the same write/read-side duplication convention).
483
+ const atomsAreHubs = Math.max(
484
+ 1,
485
+ Math.ceil((this.store.edgeSourceCount() * W) / 256),
486
+ ) > Math.ceil(Math.sqrt(Math.max(2, this.store.edgeSourceCount())));
456
487
  const nodeBytes = (n: number) => this.store.bytesPrefix(n, ALL);
457
488
  // Content-addressed probes over the store's hash-cons maps — the same keys
458
489
  // training filled. No byte-by-byte trie walk.
@@ -550,6 +581,7 @@ export class GraphSearch {
550
581
  cover: true,
551
582
  rec: true,
552
583
  node: u.node,
584
+ computed: true,
553
585
  },
554
586
  cost: STEP,
555
587
  };
@@ -588,6 +620,8 @@ export class GraphSearch {
588
620
  return this.outRules(it, {
589
621
  W,
590
622
  splits,
623
+ starts,
624
+ atomsAreHubs,
591
625
  coversDone,
592
626
  outsByStart,
593
627
  outsByEnd,
@@ -949,6 +983,8 @@ export class GraphSearch {
949
983
  ctx: {
950
984
  W: number;
951
985
  splits: ReadonlySet<number>;
986
+ starts: ReadonlySet<number>;
987
+ atomsAreHubs: boolean;
952
988
  coversDone: Set<number>;
953
989
  outsByStart: Map<number, OutItem[]>;
954
990
  outsByEnd: Map<number, OutItem[]>;
@@ -1095,12 +1131,30 @@ export class GraphSearch {
1095
1131
  r: OutItem,
1096
1132
  ctx: {
1097
1133
  W: number;
1134
+ starts: ReadonlySet<number>;
1135
+ atomsAreHubs: boolean;
1098
1136
  findLeafU: (b: Uint8Array) => number | undefined;
1099
1137
  findBranchU: (k: number[]) => number | undefined;
1100
1138
  },
1101
1139
  ): Iterable<Rule<GItem>> {
1102
1140
  const bytes = concat2(l.bytes, r.bytes);
1103
- let node = bytes.length <= ctx.W ? ctx.findLeafU(bytes) : undefined;
1141
+ // A PURE leaf-leaf fuse (neither side already a recognised completion)
1142
+ // is opportunistic cross-leaf recovery exactly like recognition.ts's own
1143
+ // canonical chain — findLeaf/findBranch here have no idea WHY these two
1144
+ // leaves are adjacent, only that their concatenation happens to spell a
1145
+ // trained form ("hi" recovered from "W[hi]ch"). The same corpus-scale
1146
+ // caution recognition.ts's `boundary` gate applies: trust it fully when
1147
+ // `l.i` is a position the query's OWN fold chose as a boundary (real
1148
+ // structural evidence); past the scale where atoms themselves stop
1149
+ // discriminating, an interior offset's opportunistic match is noise, not
1150
+ // a genuine cross-leaf recovery. A completion-involved fuse (l.rec ||
1151
+ // r.rec) is a different, legitimate case — a rewrite growing into its
1152
+ // neighbour — and is exempt, same as recognition.ts's rec-derived sites.
1153
+ const trusted = l.rec || r.rec || ctx.starts.has(l.i) ||
1154
+ !ctx.atomsAreHubs;
1155
+ let node = (trusted && bytes.length <= ctx.W)
1156
+ ? ctx.findLeafU(bytes)
1157
+ : undefined;
1104
1158
  // Whether this pair ACTUALLY forms a 2-child branch — the hard evidence
1105
1159
  // that the fused bytes are a learned form worth keeping alive. Derived
1106
1160
  // from the same findBranchU probe that sets `node`; when false, the pair
@@ -1108,7 +1162,10 @@ export class GraphSearch {
1108
1162
  // node cannot contribute to any further fusion (findBranch needs two
1109
1163
  // nodes, resolve needs a completion, and findLeaf already had its chance).
1110
1164
  let pairFormsBranch = false;
1111
- if (node === undefined && l.node !== undefined && r.node !== undefined) {
1165
+ if (
1166
+ trusted && node === undefined && l.node !== undefined &&
1167
+ r.node !== undefined
1168
+ ) {
1112
1169
  node = ctx.findBranchU([l.node, r.node]);
1113
1170
  pairFormsBranch = node !== undefined;
1114
1171
  }
@@ -9,6 +9,7 @@ import type { Input, MindContext } from "./types.js";
9
9
  import { changedNodes } from "./types.js";
10
10
  import {
11
11
  inputBytes,
12
+ latin1Key,
12
13
  perceive,
13
14
  perceiveDeposit,
14
15
  resolve,
@@ -119,6 +120,7 @@ export async function deposit(
119
120
  ctx: MindContext,
120
121
  input: Input,
121
122
  track: boolean,
123
+ conversational = false,
122
124
  ): Promise<
123
125
  { tree: Sema; rootId: number; ids: Map<Sema, number>; changed: Sema[] }
124
126
  > {
@@ -129,8 +131,13 @@ export async function deposit(
129
131
  // (no store-probe fallback): a knownPrefixLength scan on every novel fact
130
132
  // would cost O(n²) hashing, while conversation replays are always warm —
131
133
  // re-deposition replays from the first turn, rebuilding the cache as it
132
- // goes.
133
- const tree = perceiveDeposit(ctx, bytes);
134
+ // goes. `conversational` scopes the STABLE-PREFIX variant (turn-boundary
135
+ // folding, matching query-time perception) to ingestPair's own growing
136
+ // context argument — a bare ingestOne deposit whose bytes merely happen
137
+ // to extend an earlier UNRELATED deposit (no conversational relationship)
138
+ // must keep the plain fold, or two coincidentally-prefix-sharing facts
139
+ // would stop sharing structure with each other.
140
+ const tree = perceiveDeposit(ctx, bytes, conversational);
134
141
 
135
142
  const ids = new Map<Sema, number>();
136
143
  const rootId = await internTreeIds(ctx, tree, ids);
@@ -214,10 +221,20 @@ export async function ingestPair(
214
221
  ctxInput: Input,
215
222
  cont: Input,
216
223
  ): Promise<void> {
217
- const c = await deposit(ctx, ctxInput, true);
224
+ const c = await deposit(ctx, ctxInput, true, true);
218
225
  const cont_ = await deposit(ctx, cont, false);
219
226
  const ctxId = c.rootId, contId = cont_.rootId;
220
227
 
228
+ // Stamp this turn's continuation onto its own cache entry — the proof a
229
+ // FUTURE, longer ctxInput needs (see perceiveDeposit) to recognise itself
230
+ // as this conversation's genuine next turn rather than an unrelated fact
231
+ // that happens to share this ctxInput's byte prefix.
232
+ {
233
+ const ctxBytes = inputBytes(ctx, ctxInput);
234
+ const entry = ctx._depositTrees.get(latin1Key(ctxBytes));
235
+ if (entry !== undefined) entry.nextBytes = inputBytes(ctx, cont);
236
+ }
237
+
221
238
  await ctx.store.link(ctxId, contId);
222
239
  await propagateSuffixes(ctx, ctxId, contId);
223
240
 
@@ -16,7 +16,14 @@ import type { PipelineMechanism } from "../pipeline-mechanism.js";
16
16
  export function aluToMechanism(alu: Alu): PipelineMechanism {
17
17
  return {
18
18
  name: "alu",
19
- provenance: "cover",
19
+ // Not a cover derivation: cover.ts composes an answer by walking
20
+ // recognised query STRUCTURE; the ALU evaluates a recognised expression
21
+ // to its authoritative result and hands the bytes back untouched. It
22
+ // shares cover's near-zero floor (computation always wins, masked into
23
+ // cover's own search — see mechanisms/cover.ts), but the candidate this
24
+ // produces is not one of cover's derivations, so it carries its own
25
+ // honest label, the same way extract/cast/recall each carry theirs.
26
+ provenance: "alu",
20
27
  parse: (query) => alu.parse(query),
21
28
  async floor(_ctx, _query, pre, _worthRunning) {
22
29
  return pre.computed.length > 0 ? 0 : null;
@@ -102,18 +102,43 @@ export async function counterfactualTransfer(
102
102
  query: Uint8Array,
103
103
  pre: Precomputed,
104
104
  ): Promise<CastResult[]> {
105
+ // Opened unconditionally, at entry — the same convention recall.ts's
106
+ // recallByResonance and extraction.ts's extractBySkill use, so every exit
107
+ // path (five gates below, then the schemas themselves) closes through
108
+ // ONE scope and inspectRationale never hits a silent dead end. Only the
109
+ // first two gates duplicate floor()'s own admissible bound (query length,
110
+ // ranked anchor count) — required to stay in sync per this function's own
111
+ // doc comment above, and effectively dead through the ordinary pipeline
112
+ // (floor() returning null already stops run() from being called at all),
113
+ // but this function is also exported and callable directly, so they stay
114
+ // and get the same honest trace as everything past them.
115
+ const t = ctx.trace?.enter("counterfactual", [rItem(query, "query")]);
116
+ const fail = (note: string): CastResult[] => {
117
+ t?.done([], note);
118
+ return [];
119
+ };
120
+
105
121
  const quantum = ctx.space.maxGroup;
106
122
  if (query.length < 2 * quantum || ctx.store.edgeSourceCount() === 0) {
107
- return [];
123
+ return fail("query below the two-quantum floor, or no edges learnt yet");
108
124
  }
109
125
  const { roots, ranked } = await pre.attention();
110
- if (ranked.length < 2) return [];
126
+ if (ranked.length < 2) {
127
+ return fail(
128
+ `only ${ranked.length} ranked anchor(s) — CAST needs at least two`,
129
+ );
130
+ }
111
131
 
112
132
  const weave = await pre.weave();
113
133
  const points = weave.points;
114
134
  const depth = weave.depth;
115
135
  const aligned = points.length;
116
- if (aligned < 2) return [];
136
+ if (aligned < 2) {
137
+ return fail(
138
+ `only ${aligned} structure(s) aligned across the query — CAST needs ` +
139
+ `at least two to transfer between`,
140
+ );
141
+ }
117
142
 
118
143
  type Point = typeof points[0];
119
144
 
@@ -154,18 +179,31 @@ export async function counterfactualTransfer(
154
179
  const isRoot = (id: number) => roots.some((r) => r.anchor === id);
155
180
  // The weave must touch a COMMITTED point of attention: the dominant
156
181
  // structure itself, or another aligned point the climb committed to.
157
- if (!points.some((p) => isRoot(p.anchor))) return [];
182
+ if (!points.some((p) => isRoot(p.anchor))) {
183
+ t?.done(
184
+ [
185
+ ...points.map((p) => rNode(ctx, p.anchor, "aligned")),
186
+ ...roots.map((r) => rNode(ctx, r.anchor, "committed-root")),
187
+ ],
188
+ `${points.length} aligned structure(s), but none is one of the climb's ` +
189
+ `${roots.length} committed root(s) — CAST refuses to transfer through ` +
190
+ `content the climb itself never settled on`,
191
+ );
192
+ return [];
193
+ }
158
194
 
159
195
  const woven = points.some((p) =>
160
196
  p.runs.some((r) =>
161
197
  !pre.rec.sites.some((s) => r.qs >= s.start && r.qe <= s.end)
162
198
  )
163
199
  );
164
- if (!woven) return [];
200
+ if (!woven) {
201
+ return fail(
202
+ `every aligned run restates a recognised query site — nothing was ` +
203
+ `actually WOVEN across structures, so there is nothing to transfer`,
204
+ );
205
+ }
165
206
 
166
- const t = ctx.trace?.enter("counterfactual", [
167
- rItem(query, "query"),
168
- ]);
169
207
  // Each schema tried below RECORDS its candidate (when it fires) rather than
170
208
  // returning immediately — every schema that succeeds contributes its own
171
209
  // candidate, and the grounding decider's own weight comparison (not CAST's
@@ -11,6 +11,7 @@
11
11
 
12
12
  import type { MindContext } from "../types.js";
13
13
  import type { ComputedResult, Site } from "../graph-search.js";
14
+ import { bytesEqual, indexOf } from "../../bytes.js";
14
15
  import { read, resolve } from "../primitives.js";
15
16
  import { guidedFirst } from "../traverse.js";
16
17
  import { conceptHop } from "../match.js";
@@ -158,11 +159,18 @@ export const coverMechanism: PipelineMechanism = {
158
159
 
159
160
  const connectors = await resolveConnectors(ctx, sites);
160
161
  let splits = rec.splits;
162
+ let starts = rec.starts;
161
163
  if (computed.length > 0) {
162
164
  splits = new Set(rec.splits);
165
+ starts = new Set(rec.starts);
163
166
  for (const u of computed) {
164
167
  splits.add(u.i);
165
168
  splits.add(u.j);
169
+ // A computation's own boundaries carry the same fold-level evidence
170
+ // a chunk boundary does — "computation always wins" (see the header
171
+ // comment) extends to being trusted ground for cross-leaf recovery.
172
+ starts.add(u.i);
173
+ starts.add(u.j);
166
174
  }
167
175
  }
168
176
  const concepts = await resolveConcepts(ctx, sites);
@@ -198,6 +206,7 @@ export const coverMechanism: PipelineMechanism = {
198
206
  concepts,
199
207
  rec.leaves,
200
208
  splits,
209
+ starts,
201
210
  undefined,
202
211
  connectors,
203
212
  computedResults,
@@ -217,6 +226,43 @@ export const coverMechanism: PipelineMechanism = {
217
226
 
218
227
  if (segs === null) return [];
219
228
 
229
+ // A chosen span's SUBSTITUTED bytes (an edge followed from a recognised
230
+ // site, not the site's own literal text read back) that equal a byte
231
+ // span the query ALREADY CONTAINS elsewhere restates part of the
232
+ // question — never an answer (the same principle recall.ts's tiers
233
+ // apply to a whole-query projection: "a projection that is a proper
234
+ // byte-subspan of the query restates part of the question"). A
235
+ // recognised site that is itself an entire PRIOR TURN of a multi-turn
236
+ // query is exactly this shape: it carries a genuine learnt
237
+ // continuation, but that continuation is something the asker already
238
+ // said moments later in the SAME query, not a new answer — the cover
239
+ // search has no notion of "turn" to gate this itself, so the check
240
+ // belongs here, over the derivation it already chose.
241
+ const W = ctx.space.maxGroup;
242
+ for (const s of segs) {
243
+ if (!s.rec) continue;
244
+ const literal = s.j - s.i === s.bytes.length &&
245
+ bytesEqual(s.bytes, query.subarray(s.i, s.j));
246
+ if (literal) continue;
247
+ // A span shorter than one river window is exactly the "ice"/"c"
248
+ // case: a chain hop's short terminal coincidentally recurring as a
249
+ // substring of an unrelated longer word. Below the quantum, byte
250
+ // overlap is chance, not evidence — the same floor identityBar and
251
+ // reachThreshold hold every other structural-overlap claim to.
252
+ if (
253
+ s.bytes.length >= W && s.bytes.length < query.length &&
254
+ indexOf(query, s.bytes, 0) >= 0
255
+ ) {
256
+ ctx.trace?.step(
257
+ "restatedSpan",
258
+ [rItem(s.bytes, "substituted", s.node, [s.i, s.j])],
259
+ [],
260
+ "the chosen span's substitution already occurs elsewhere in the query — restates it, not an answer",
261
+ );
262
+ return [];
263
+ }
264
+ }
265
+
220
266
  const composed = liftAnswer(segs, query.length);
221
267
  if (composed === null) return [];
222
268
 
@@ -70,43 +70,118 @@ export async function extractBySkill(
70
70
  if (ranked.length === 0) {
71
71
  return fail("no consensus anchor — no skill to apply");
72
72
  }
73
- let exemplar: SkillInfo | null = null;
74
- let chosenAnchor: number | null = null;
75
- let skipped = 0;
76
- for (const cand of ranked) {
77
- const ex = await pre.spanShapedOf(cand.anchor);
78
- if (ex) {
79
- exemplar = ex;
80
- chosenAnchor = cand.anchor;
81
- break;
73
+
74
+ // Try ranked anchors IN ORDER until one yields a USABLE extraction — not
75
+ // merely a span-shaped exemplar, but one whose extracted span clears the
76
+ // same one-river-fold quantum (W) cover.ts's restatedSpan gate already
77
+ // treats as the floor below which byte overlap is chance, not evidence.
78
+ // isSpanShaped (spanShapedOf) is a deliberately permissive sparse-
79
+ // subsequence check — see the section note below — so it accepts exemplars
80
+ // whose relation to the query is coincidental gap-matching, not genuine
81
+ // structure. Stopping at the FIRST such exemplar let a coincidental match
82
+ // early in the ranked list win outright and read out a sub-quantum
83
+ // fragment (observed: a 3-byte "Hel" pulled from an unrelated exemplar,
84
+ // while a later ranked anchor would have read the query's own "Hello…"
85
+ // correctly). Trying further anchors when one produces nothing usable is
86
+ // the same idiom this loop already uses for non-exemplars — extended to
87
+ // cover a bad extraction, not just a structural non-match.
88
+ //
89
+ // The retry is bounded at pre.k — the SAME evidence-breadth constant every
90
+ // other consumer of a ranked list already self-limits to (resonance, the
91
+ // weave, the climb itself; see Precomputed.k's own doc comment) — not the
92
+ // full ranked list. locate()'s frame match has an EXACT-byte tier with no
93
+ // significance correction of its own (short W-byte frames are cheap to
94
+ // match by pure chance), so trying every ranked anchor turns that per-
95
+ // anchor chance into a near-certainty over enough attempts: on a pure-
96
+ // gibberish query, 170 anchors deep found an unrelated Zulu exemplar whose
97
+ // short frame happened to byte-match, producing "xyzzy pl" — a coincidence
98
+ // no different in kind from the "RaBitQ estimate overshot the reach bar
99
+ // and grounded pure gibberish" failure recall.ts's own significance
100
+ // correction exists to prevent (see recallByResonance's reach-threshold
101
+ // comment). Bounding the search to the ranked list's own top-k restores
102
+ // the "genuinely relevant but not root-significant" exemplars this loop
103
+ // was built for, without the unbounded tail's chance collisions.
104
+ const W = ctx.space.maxGroup;
105
+ const searched = ranked.slice(0, pre.k);
106
+ let shapeMisses = 0;
107
+ let subQuantum = 0;
108
+ for (const cand of searched) {
109
+ const exemplar = await pre.spanShapedOf(cand.anchor);
110
+ if (!exemplar) {
111
+ shapeMisses++;
112
+ continue;
113
+ }
114
+ const built = buildFromExemplar(ctx, query, pre, exemplar);
115
+ if (built === null || built.bytes.length < W) {
116
+ subQuantum++;
117
+ continue;
118
+ }
119
+ if (shapeMisses > 0 || subQuantum > 0) {
120
+ ctx.trace?.step(
121
+ "trySkillAnchors",
122
+ [
123
+ rItem(
124
+ query.subarray(0, 0),
125
+ `skipped ${shapeMisses + subQuantum}`,
126
+ ),
127
+ rNode(ctx, cand.anchor, "chosen"),
128
+ ],
129
+ [],
130
+ `skipped ${shapeMisses} non-exemplar and ${subQuantum} sub-quantum ` +
131
+ `anchor(s) before one yielded a usable extraction`,
132
+ );
82
133
  }
83
- skipped++;
134
+ t?.done(
135
+ [rItem(built.bytes, "extracted")],
136
+ built.pieces === 1
137
+ ? `apply a learnt extraction skill — read the analogous span of the query` +
138
+ ` framed like "${
139
+ decodeText(exemplar.answerBytes)
140
+ }" sits in its exemplar`
141
+ : `apply a learnt MULTI-PIECE skill — read ${built.pieces} analogous` +
142
+ ` pieces of the query and synthesize them like "${
143
+ decodeText(exemplar.answerBytes)
144
+ }"`,
145
+ );
146
+ return {
147
+ bytes: built.bytes,
148
+ accounted: built.accounted,
149
+ unexplained: unexplainedLabel(query, built.accounted),
150
+ };
84
151
  }
85
- if (exemplar === null) {
152
+ if (shapeMisses === searched.length) {
86
153
  ctx.trace?.step(
87
154
  "trySkillAnchors",
88
155
  [],
89
156
  [],
90
- `none of ${ranked.length} ranked anchor(s) is a span-shaped skill exemplar`,
157
+ `none of the top ${searched.length} ranked anchor(s) (of ${ranked.length} total) ` +
158
+ `is a span-shaped skill exemplar`,
91
159
  );
92
160
  return fail("no consensus root is a span-shaped skill exemplar");
93
161
  }
94
- if (skipped > 0) {
95
- ctx.trace?.step(
96
- "trySkillAnchors",
97
- [
98
- rItem(query.subarray(0, 0), `skipped ${skipped}`),
99
- rNode(ctx, chosenAnchor!, "chosen"),
100
- ],
101
- [],
102
- `skipped ${skipped} anchor(s) that are not skill exemplars`,
103
- );
104
- }
162
+ return fail(
163
+ "no ranked anchor yielded an extraction at or above the quantum floor",
164
+ );
165
+ }
166
+
167
+ /** Build the extracted bytes for ONE already-accepted span-shaped exemplar —
168
+ * factored out of {@link extractBySkill} so its anchor loop can try
169
+ * successive ranked candidates instead of committing to the first
170
+ * structural match. Null when the exemplar's answer does not decompose
171
+ * against its context, or no piece's frame locates in the query. */
172
+ function buildFromExemplar(
173
+ ctx: MindContext,
174
+ query: Uint8Array,
175
+ pre: Precomputed,
176
+ exemplar: SkillInfo,
177
+ ):
178
+ | { bytes: Uint8Array; accounted: Array<[number, number]>; pieces: number }
179
+ | null {
105
180
  const { contextBytes, answerBytes } = exemplar;
106
181
 
107
182
  const ansCtxRuns = answerRunsInContext(ctx, contextBytes, answerBytes);
108
183
  if (ansCtxRuns === null || ansCtxRuns.length === 0) {
109
- return fail("answer is not a subsequence of the context");
184
+ return null;
110
185
  }
111
186
 
112
187
  if (ansCtxRuns.length > 1) {
@@ -200,25 +275,11 @@ export async function extractBySkill(
200
275
  if (preBounded && postBounded) accounted.push([start, end]);
201
276
  }
202
277
  if (pieces.length === 0) {
203
- return fail("no answer piece's frame located in the query");
278
+ return null;
204
279
  }
205
280
 
206
281
  const out = pieces.length === 1 ? pieces[0] : concatBytes(pieces);
207
- t?.done(
208
- [rItem(out, "extracted")],
209
- pieces.length === 1
210
- ? `apply a learnt extraction skill — read the analogous span of the query` +
211
- ` framed like "${decodeText(answerBytes)}" sits in its exemplar`
212
- : `apply a learnt MULTI-PIECE skill — read ${pieces.length} analogous` +
213
- ` pieces of the query and synthesize them like "${
214
- decodeText(answerBytes)
215
- }"`,
216
- );
217
- return {
218
- bytes: out,
219
- accounted,
220
- unexplained: unexplainedLabel(query, accounted),
221
- };
282
+ return { bytes: out, accounted, pieces: pieces.length };
222
283
  }
223
284
 
224
285
  // ── The two span-shape readings: OPEN acceptance vs. STRONG decomposition ──
@@ -113,10 +113,38 @@ export async function recallByResonance(
113
113
  maximal.push(s);
114
114
  maxEnd = s.end;
115
115
  }
116
- if (maximal.length === 1) {
116
+ // The wrapper must actually BE scaffolding: RC8's own premise is "the
117
+ // wrapper is scaffolding; the argument is the span that leads
118
+ // somewhere" ("How do you say 'thank you' in French?" — everything
119
+ // outside the argument is a small fixed template). When the query
120
+ // instead has ANOTHER substantial recognised form (≥ W2, the same
121
+ // constituent bar the argument itself must clear) sitting OUTSIDE the
122
+ // chosen argument, the query is not one argument in a wrapper — it is
123
+ // several complete, independently-meaningful pieces (a multi-turn
124
+ // conversation's own accumulated turns are exactly this shape), and
125
+ // binding to the argument's continuation would answer past content
126
+ // the query itself already carries forward. Derived from the same W2
127
+ // bar the argument itself is held to, never a separate tuned number.
128
+ const hasSubstantialOutside = maximal.length === 1 &&
129
+ pre.rec.sites.some((s) =>
130
+ s.end - s.start >= W2 &&
131
+ (s.end <= maximal[0].start || s.start >= maximal[0].end)
132
+ );
133
+ if (maximal.length === 1 && !hasSubstantialOutside) {
117
134
  const arg = maximal[0];
118
135
  const g = await follow(ctx, arg.payload, queryGist);
119
- if (g !== null && g.length > 0) {
136
+ // The same "no restated fragment" guard tier 2 applies below (§ "the
137
+ // anchor cleared the consensus floor..."): a followed continuation
138
+ // that is itself a proper byte-subspan of the QUERY restates part of
139
+ // the question — never an answer. A multi-turn query's own later
140
+ // turns are exact, content-addressed matches for exactly this reason
141
+ // (each turn is its own previously-learnt form), so without this
142
+ // guard the argument's OWN later restatement in the same
143
+ // conversation reads as if it were the next thing to say.
144
+ if (
145
+ g !== null && g.length > 0 &&
146
+ !(g.length < query.length && indexOf(query, g, 0) >= 0)
147
+ ) {
120
148
  return ground(
121
149
  g,
122
150
  "argument binding — the query's sole edge-source constituent, continuation followed",