@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.
- package/dist/src/geometry.d.ts +28 -0
- package/dist/src/geometry.js +35 -0
- package/dist/src/mind/articulation.js +1 -1
- package/dist/src/mind/attention.d.ts +4 -0
- package/dist/src/mind/attention.js +65 -12
- package/dist/src/mind/graph-search.d.ts +16 -1
- package/dist/src/mind/graph-search.js +34 -5
- package/dist/src/mind/learning.d.ts +1 -1
- package/dist/src/mind/learning.js +20 -5
- package/dist/src/mind/mechanisms/alu.js +8 -1
- package/dist/src/mind/mechanisms/cast.js +35 -11
- package/dist/src/mind/mechanisms/cover.js +40 -1
- package/dist/src/mind/mechanisms/extraction.js +75 -30
- package/dist/src/mind/mechanisms/recall.js +26 -2
- package/dist/src/mind/mind.d.ts +3 -2
- package/dist/src/mind/mind.js +28 -54
- package/dist/src/mind/pipeline.js +34 -2
- package/dist/src/mind/primitives.d.ts +16 -9
- package/dist/src/mind/primitives.js +58 -22
- package/dist/src/mind/reasoning.d.ts +9 -1
- package/dist/src/mind/reasoning.js +26 -4
- package/dist/src/mind/recognition.js +30 -6
- package/dist/src/mind/traverse.js +15 -0
- package/dist/src/mind/types.d.ts +58 -10
- package/dist/src/mind/types.js +15 -0
- package/package.json +1 -1
- package/src/geometry.ts +61 -1
- package/src/mind/articulation.ts +1 -0
- package/src/mind/attention.ts +80 -12
- package/src/mind/graph-search.ts +59 -2
- package/src/mind/learning.ts +20 -3
- package/src/mind/mechanisms/alu.ts +8 -1
- package/src/mind/mechanisms/cast.ts +46 -8
- package/src/mind/mechanisms/cover.ts +46 -0
- package/src/mind/mechanisms/extraction.ts +101 -40
- package/src/mind/mechanisms/recall.ts +30 -2
- package/src/mind/mind.ts +38 -61
- package/src/mind/pipeline.ts +37 -2
- package/src/mind/primitives.ts +71 -29
- package/src/mind/reasoning.ts +26 -3
- package/src/mind/recognition.ts +28 -5
- package/src/mind/traverse.ts +18 -0
- package/src/mind/types.ts +73 -10
- package/test/35-attention-confidence.test.mjs +139 -0
package/src/mind/graph-search.ts
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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
|
}
|
package/src/mind/learning.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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)
|
|
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)
|
|
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)))
|
|
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)
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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 (
|
|
152
|
+
if (shapeMisses === searched.length) {
|
|
86
153
|
ctx.trace?.step(
|
|
87
154
|
"trySkillAnchors",
|
|
88
155
|
[],
|
|
89
156
|
[],
|
|
90
|
-
`none of ${
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
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
|
|
278
|
+
return null;
|
|
204
279
|
}
|
|
205
280
|
|
|
206
281
|
const out = pieces.length === 1 ? pieces[0] : concatBytes(pieces);
|
|
207
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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",
|