@hviana/sema 0.5.2 → 0.5.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/AGENTS.md +114 -52
- package/HOW_IT_WORKS.md +275 -184
- package/dist/src/mind/bridge.d.ts +5 -7
- package/dist/src/mind/bridge.js +6 -97
- package/dist/src/mind/match.d.ts +159 -0
- package/dist/src/mind/match.js +300 -7
- package/dist/src/mind/mechanisms/prefix-completion.d.ts +22 -0
- package/dist/src/mind/{prefix-completion.js → mechanisms/prefix-completion.js} +64 -91
- package/dist/src/mind/mechanisms/recall.js +10 -108
- package/dist/src/mind/mechanisms/reference.d.ts +6 -0
- package/dist/src/mind/mechanisms/reference.js +296 -0
- package/dist/src/mind/mind.d.ts +1 -1
- package/dist/src/mind/pipeline-mechanism.d.ts +56 -1
- package/dist/src/mind/pipeline-mechanism.js +104 -3
- package/dist/src/mind/pipeline.d.ts +1 -1
- package/dist/src/mind/pipeline.js +13 -1
- package/dist/src/mind/traverse.d.ts +38 -0
- package/dist/src/mind/traverse.js +91 -1
- package/dist/src/store.d.ts +4 -4
- package/jsr.json +6 -0
- package/package.json +1 -1
- package/src/mind/bridge.ts +10 -104
- package/src/mind/match.ts +416 -7
- package/src/mind/{prefix-completion.ts → mechanisms/prefix-completion.ts} +66 -92
- package/src/mind/mechanisms/recall.ts +9 -126
- package/src/mind/mechanisms/reference.ts +343 -0
- package/src/mind/mind.ts +12 -8
- package/src/mind/pipeline-mechanism.ts +120 -3
- package/src/mind/pipeline.ts +16 -2
- package/src/mind/traverse.ts +92 -1
- package/src/store.ts +13 -4
- package/test/33-multi-candidate.test.mjs +21 -11
- package/test/70-prefix-completion.test.mjs +1 -1
- package/test/72-prefix-candidate-supply.test.mjs +7 -9
- package/test/74-prefix-trap-not-sprung-early.test.mjs +1 -1
- package/test/76-reference-binding.test.mjs +471 -0
- package/dist/src/mind/frame-filler.d.ts +0 -15
- package/dist/src/mind/frame-filler.js +0 -535
- package/dist/src/mind/prefix-completion.d.ts +0 -59
- package/src/mind/frame-filler.ts +0 -604
- package/test/69-frame-filler.test.mjs +0 -115
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// mechanisms/recall.ts — Recall by resonance (Grounding
|
|
1
|
+
// mechanisms/recall.ts — Recall by resonance (Grounding VI).
|
|
2
2
|
//
|
|
3
3
|
// The recall mechanism resonates the whole query's gist against the content
|
|
4
4
|
// index and grounds the nearest learned form. Four tiers, orderly degrading
|
|
@@ -23,8 +23,6 @@ import { unexplainedLabel } from "../rationale.js";
|
|
|
23
23
|
import type { PipelineMechanism, Precomputed } from "../pipeline-mechanism.js";
|
|
24
24
|
import { rItem, rNode } from "../trace.js";
|
|
25
25
|
import { substitutionBridge } from "../bridge.js";
|
|
26
|
-
import { frameFillerSubstitution } from "../frame-filler.js";
|
|
27
|
-
import { prefixCandidates, prefixCompletion } from "../prefix-completion.js";
|
|
28
26
|
|
|
29
27
|
/** A recall result. */
|
|
30
28
|
export interface RecallResult {
|
|
@@ -165,7 +163,10 @@ export async function recallByResonance(
|
|
|
165
163
|
}
|
|
166
164
|
}
|
|
167
165
|
|
|
168
|
-
|
|
166
|
+
// The response's ONE top-k read (Precomputed.resonance) — the same list the
|
|
167
|
+
// frame inventory is assembled from, so a query that reaches both pays for a
|
|
168
|
+
// single ANN query rather than two identical ones.
|
|
169
|
+
const whole = await pre.resonance();
|
|
169
170
|
if (whole.length === 0) {
|
|
170
171
|
return ground(null, "empty store — nothing to resonate with", [], 0);
|
|
171
172
|
}
|
|
@@ -385,48 +386,10 @@ export async function recallByResonance(
|
|
|
385
386
|
}
|
|
386
387
|
}
|
|
387
388
|
// 3b. Corroborated-substitution bridge — refusal-path only (bridge.ts).
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
|
|
392
|
-
// 13 ms non-exhaustive (36x).
|
|
393
|
-
const wideIdsOnce = async (): Promise<ReadonlyArray<number>> => {
|
|
394
|
-
// When the top resonance hit is below the concept threshold, the query
|
|
395
|
-
// gist has no concept-level match to any stored form — an exhaustive √N
|
|
396
|
-
// ANN would only score more vectors below the bar (profiled at 38K–40K
|
|
397
|
-
// annVectorReads per refusing query on a 325K-context store). The
|
|
398
|
-
// bridge's structural channels (junction walks, anchor climbs) are the
|
|
399
|
-
// correct proposal source for a query whose gist has no clean match;
|
|
400
|
-
// the ANN cannot propose what the gist cannot rank.
|
|
401
|
-
// The condition above is the SCORE of the top hit, not the size of the
|
|
402
|
-
// corpus. It used to be spelled `corpusN(ctx) <= (k · W)³`, which asks
|
|
403
|
-
// a different question and answers it wrongly at exactly the scale the
|
|
404
|
-
// note was written from: on the trained store N = 325,608 with k = 24
|
|
405
|
-
// and W = 4 puts the cube at 884,736, so that store took the exhaustive
|
|
406
|
-
// branch — the very branch measured here as 38K–40K annVectorReads.
|
|
407
|
-
// Measured cost of the mismatch: substitutionBridge 8,544ms of a
|
|
408
|
-
// 19,548ms think (44%), against 1,248ms and 14,218ms without it, with
|
|
409
|
-
// every answer in the battery byte-identical and the suite unchanged
|
|
410
|
-
// at 445/445. Corpus size was never the discriminator; whether the
|
|
411
|
-
// gist ranks ANYTHING at concept level is.
|
|
412
|
-
//
|
|
413
|
-
// Reading it as the note states also removes a duplicated (k · W)³ —
|
|
414
|
-
// the same cube gates crossRegionVotes' walk budget, where it likewise
|
|
415
|
-
// never engages at real scale (see attention.ts).
|
|
416
|
-
if (whole.length > 0 && whole[0].score >= conceptThreshold(ctx.store.D)) {
|
|
417
|
-
const exhaustive = await ctx.store.resonate(
|
|
418
|
-
queryGist,
|
|
419
|
-
hubBound(ctx),
|
|
420
|
-
true,
|
|
421
|
-
);
|
|
422
|
-
return exhaustive.map((h) => h.id);
|
|
423
|
-
}
|
|
424
|
-
return whole.map((h) => h.id);
|
|
425
|
-
};
|
|
426
|
-
let wide: Promise<ReadonlyArray<number>> | null = null;
|
|
427
|
-
const wideIds = (): Promise<
|
|
428
|
-
ReadonlyArray<number>
|
|
429
|
-
> => (wide ??= wideIdsOnce());
|
|
389
|
+
// The WIDE candidate list every past-the-top-k mechanism reads lives on
|
|
390
|
+
// Precomputed (see wideResonance): shared across the whole response, so the
|
|
391
|
+
// exhaustive branch runs at most once whoever first-touches it.
|
|
392
|
+
const wideIds = () => pre.wideResonance();
|
|
430
393
|
|
|
431
394
|
// Every gist-based tier has failed; before refusing, align the query
|
|
432
395
|
// byte-for-byte against the trained contexts its own stored windows
|
|
@@ -547,86 +510,6 @@ export async function recallByResonance(
|
|
|
547
510
|
);
|
|
548
511
|
}
|
|
549
512
|
}
|
|
550
|
-
|
|
551
|
-
// 3b′. PREFIX COMPLETION — refusal-path only (prefix-completion.ts).
|
|
552
|
-
// Inside the bridge's block, and deliberately: it consumes `wideIds`, the
|
|
553
|
-
// list the bridge has already fetched, so it costs a bounded byte compare
|
|
554
|
-
// per candidate and not one resonance. The claim it makes is the
|
|
555
|
-
// strongest in the ladder — every query byte is a LITERAL match from
|
|
556
|
-
// offset zero of a trained form — so it needs no projection and no reach
|
|
557
|
-
// gate. It runs after the bridge only because the bridge answers the
|
|
558
|
-
// richer relation when it can; a prefix match that the bridge also
|
|
559
|
-
// explains is the same trained form either way.
|
|
560
|
-
{
|
|
561
|
-
// The resonance list first; only when it supplies nothing does the
|
|
562
|
-
// write side's leaf-id window index propose (prefixCandidates). That
|
|
563
|
-
// ordering is the whole cost story: a query the ranked list can already
|
|
564
|
-
// explain pays not one extra read, and the fallback's bounded walk is
|
|
565
|
-
// spent only where the alternative is an empty answer. It is a second
|
|
566
|
-
// SUPPLY, not a second mechanism — the same three guards decide.
|
|
567
|
-
const completed = prefixCompletion(ctx, query, await wideIds()) ??
|
|
568
|
-
prefixCompletion(ctx, query, prefixCandidates(ctx, query));
|
|
569
|
-
if (completed !== null) {
|
|
570
|
-
return ground(
|
|
571
|
-
completed.form,
|
|
572
|
-
"prefix completion — the query IS the opening of exactly one " +
|
|
573
|
-
"trained form, which this grounds whole",
|
|
574
|
-
// Every query byte is literally matched against the form. The
|
|
575
|
-
// completion is the form's own continuation, not a substitution, so
|
|
576
|
-
// there is nothing to be humble about in the accounting — the same
|
|
577
|
-
// reading the IDENTITY bridge above takes.
|
|
578
|
-
whole_,
|
|
579
|
-
STEP,
|
|
580
|
-
false,
|
|
581
|
-
// NOT complete: the query is a proper PREFIX, so the form may carry
|
|
582
|
-
// more past the remainder this tier voiced.
|
|
583
|
-
false,
|
|
584
|
-
);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// 3c. FRAME-FILLER SUBSTITUTION — refusal-path only (frame-filler.ts).
|
|
590
|
-
// The bridge has failed, and for the shape this tier answers it MUST fail:
|
|
591
|
-
// a definite description standing where a proper noun stands is not a
|
|
592
|
-
// similarity relation the bridge can price (raw balance refuses
|
|
593
|
-
// `dominates(6, 37)`, and correctly — that is the France/Spain trap). This
|
|
594
|
-
// tier makes a different claim: not that the two spans resemble each other,
|
|
595
|
-
// but that the store ALREADY HOLDS this query with the filler in the
|
|
596
|
-
// description's place, byte-exactly. A key the store does not hold is
|
|
597
|
-
// discarded, so the answer is always a trained continuation.
|
|
598
|
-
{
|
|
599
|
-
// THE COHORT NEEDS EVIDENCE, AND THE REFUSAL PATH HAS ALREADY BOUGHT IT.
|
|
600
|
-
// This tier reads constituency from what a cohort of exemplars does NOT
|
|
601
|
-
// share, so its resolution is bounded by how many instances of the frame it
|
|
602
|
-
// can see. The top-k resonance hits are too few — on the two-hop probe the
|
|
603
|
-
// exemplars holding the query's discriminative content number TWO, and two
|
|
604
|
-
// structures agree on so little that a whole clause reads as content. The
|
|
605
|
-
// exhaustive list the bridge fetched is the same evidence at ~570 wide, and
|
|
606
|
-
// it is already paid for (memoised above, so this costs no ANN call).
|
|
607
|
-
const filled = frameFillerSubstitution(ctx, query, await wideIds());
|
|
608
|
-
if (filled !== null) {
|
|
609
|
-
const g = await project(ctx, filled.id, queryGist);
|
|
610
|
-
// The same restated-fragment and manufactured-answer guards every tier
|
|
611
|
-
// above applies: a projection contained in the FILLER is the
|
|
612
|
-
// substitution restated as if it were knowledge, not knowledge.
|
|
613
|
-
if (
|
|
614
|
-
g !== null && g.length > 0 && !restates(g) &&
|
|
615
|
-
indexOf(filled.filler, g, 0) < 0 &&
|
|
616
|
-
!(g.length < query.length && indexOf(query, g, 0) >= 0)
|
|
617
|
-
) {
|
|
618
|
-
return ground(
|
|
619
|
-
g,
|
|
620
|
-
"frame-filler substitution — a trained form IS this query with a " +
|
|
621
|
-
"corroborated filler in the described span's place",
|
|
622
|
-
// The frame is literally matched against the resolved form and the
|
|
623
|
-
// described span is explained by the substitution — the same
|
|
624
|
-
// matched-plus-substituted accounting the bridge reports.
|
|
625
|
-
[[0, query.length]],
|
|
626
|
-
CONCEPT + STEP,
|
|
627
|
-
);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
513
|
}
|
|
631
514
|
|
|
632
515
|
// The refusal/echo decision. The echo returns a stored form's bytes AS
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
// mechanisms/reference.ts — Reference: voice a slot with the context's own
|
|
2
|
+
// bytes (Grounding IV).
|
|
3
|
+
//
|
|
4
|
+
// This file is a CONFIGURATION of the shared frame reading in match.ts, not a
|
|
5
|
+
// pipeline of its own. The three parts it configures live where §2.5 puts
|
|
6
|
+
// them and are reachable by any mechanism:
|
|
7
|
+
//
|
|
8
|
+
// matcher Precomputed.frames() — the frame INVENTORY: which ranked
|
|
9
|
+
// candidates read as instances of the query's own frame, and
|
|
10
|
+
// where each leaves the query VARIABLE. Shared, election-free.
|
|
11
|
+
// projection follow() — each instance's own learnt continuation.
|
|
12
|
+
// gate carriesFillers() — the carriage licence.
|
|
13
|
+
//
|
|
14
|
+
// What this file adds is the reading and the price: elect one frame from the
|
|
15
|
+
// inventory, demand the licence of it, splice, and state the cost.
|
|
16
|
+
//
|
|
17
|
+
// WHAT KIND OF ACT THIS IS. bridge.ts refuses this shape and is right to:
|
|
18
|
+
// `attestedQ` demands the query-side span be corpus-attested, because a
|
|
19
|
+
// SUBSTITUTION asserts equivalence ("these two spans mean the same"), and
|
|
20
|
+
// nothing can corroborate an equivalence claim about bytes the corpus has
|
|
21
|
+
// never seen. A REFERENCE asserts no equivalence. It asserts POSITION —
|
|
22
|
+
// "this is the thing you named, where the corpus keeps one" — and the bytes
|
|
23
|
+
// come from the asker, so voicing them cannot fabricate corpus knowledge.
|
|
24
|
+
// What CAN be fabricated is the relation claimed about them, which is exactly
|
|
25
|
+
// what the licence withholds.
|
|
26
|
+
//
|
|
27
|
+
// The same reasoning is why the bridge is NOT rewired to consult the frame
|
|
28
|
+
// inventory, though it is now free to: the bridge grounds through its
|
|
29
|
+
// candidate's continuation UNSUBSTITUTED, so admitting a slot-gap there would
|
|
30
|
+
// voice the corpus's filler for the asker's referent — the misreference
|
|
31
|
+
// measured live on the trained store ("How do you say 'flurbish' in French?"
|
|
32
|
+
// answered "the way to say hello is \"Bonjour\""). Nor is CAST rewired: its
|
|
33
|
+
// frame gate is WEAVE-local while a slot is COHORT-local, and substituting one
|
|
34
|
+
// population for the other is the error §2.7 names. The notion is made
|
|
35
|
+
// AVAILABLE, never imposed.
|
|
36
|
+
|
|
37
|
+
import type { MindContext } from "../types.js";
|
|
38
|
+
import type { FrameInstance } from "../match.js";
|
|
39
|
+
import { carriesFillers, distinct, follow, substituteAll } from "../match.js";
|
|
40
|
+
import { dominates } from "../../geometry.js";
|
|
41
|
+
import { bytesEqual, indexOf } from "../../bytes.js";
|
|
42
|
+
import { unexplainedLabel } from "../rationale.js";
|
|
43
|
+
import { STEP } from "../graph-search.js";
|
|
44
|
+
import type {
|
|
45
|
+
MechanismResult,
|
|
46
|
+
PipelineMechanism,
|
|
47
|
+
Precomputed,
|
|
48
|
+
} from "../pipeline-mechanism.js";
|
|
49
|
+
import { rItem, rNode, traceFail } from "../trace.js";
|
|
50
|
+
|
|
51
|
+
/** The minimum number of instances that can establish a frame. One instance
|
|
52
|
+
* agrees with nothing, so no carriage is attested — the same "two or no
|
|
53
|
+
* constituent" reading frame-filler's contentRuns applies.
|
|
54
|
+
*
|
|
55
|
+
* THIS IS ALSO THE MECHANISM'S REACH. Evidence comes from the shared top-k
|
|
56
|
+
* resonance, so a frame the corpus instantiates only ONCE within k is not
|
|
57
|
+
* reachable here. Measured on the trained store: `How do you say 'flurbish'
|
|
58
|
+
* in French?` finds one instance of its frame in the top 24 — the rest are
|
|
59
|
+
* `How do you make …`, a different frame — so this abstains and recall's
|
|
60
|
+
* scaffolding-dominated tier answers with the CORPUS's filler. That
|
|
61
|
+
* misreference is recall's, and widening the supply is not the fix: the
|
|
62
|
+
* exhaustive √N list recall's refusal path builds costs hundreds of
|
|
63
|
+
* milliseconds and this runs before it. Abstaining on thin evidence is the
|
|
64
|
+
* honest reading (§2.13). */
|
|
65
|
+
const MIN_INSTANCES = 2;
|
|
66
|
+
|
|
67
|
+
/** THE VOICING GATES — this mechanism's own reading of a pairing, applied here
|
|
68
|
+
* and NOT in the shared matcher.
|
|
69
|
+
*
|
|
70
|
+
* Every one of these is a requirement for SUBSTITUTING AND SPEAKING, not for
|
|
71
|
+
* knowing where a pairing varies, so each belongs to the consumer that
|
|
72
|
+
* speaks. They lived in `frameSlots` first and made the shared reading
|
|
73
|
+
* reference-shaped: three of four real pairings were hidden from every other
|
|
74
|
+
* consumer, including a definite description standing where a noun stands —
|
|
75
|
+
* the shape the frame filler exists for. A shared layer with one usable
|
|
76
|
+
* consumer is private code at a public address.
|
|
77
|
+
*
|
|
78
|
+
* 1. EVERY slot must be a SUBSTITUTION. An insertion or deletion means the
|
|
79
|
+
* query says more or less than the frame does, and there is no occupant to
|
|
80
|
+
* carry. (A consumer that WANTS insertions — "what did the asker add?" —
|
|
81
|
+
* now sees them; this one cannot use them.)
|
|
82
|
+
* 2. EVERY slot must reach one river window on BOTH sides. Below one window
|
|
83
|
+
* byte overlap is chance, not evidence — the floor identityBar, the
|
|
84
|
+
* bridge's attestedQ and recognition's site test all draw.
|
|
85
|
+
* 3. The FRAME MUST DOMINATE the query, or this is a different sentence that
|
|
86
|
+
* happens to align somewhere. It is also what bounds the slot count
|
|
87
|
+
* without a constant: every slot costs at least a window, and the frame
|
|
88
|
+
* must still be more than half the query.
|
|
89
|
+
* 4. FILLERS PAIRWISE DISTINCT. Two slots holding the same bytes make the
|
|
90
|
+
* occurrence → referent mapping ambiguous, and no evidence resolves it. */
|
|
91
|
+
function voiceable(
|
|
92
|
+
inst: FrameInstance,
|
|
93
|
+
W: number,
|
|
94
|
+
queryLen: number,
|
|
95
|
+
): boolean {
|
|
96
|
+
if (inst.slots.length === 0) return false;
|
|
97
|
+
for (const slot of inst.slots) {
|
|
98
|
+
if (slot.kind !== "substitution") return false;
|
|
99
|
+
if (slot.qe - slot.qs < W || slot.ce - slot.cs < W) return false;
|
|
100
|
+
}
|
|
101
|
+
if (!dominates(inst.covered, queryLen)) return false;
|
|
102
|
+
return distinct(inst.slots.map((s) => s.filler));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Elect ONE frame from the inventory: the voiceable instances that place
|
|
106
|
+
* their slots in the same query spans.
|
|
107
|
+
*
|
|
108
|
+
* THE ELECTION IS THIS MECHANISM'S, not the inventory's (see
|
|
109
|
+
* Precomputed.frames). Demanding that everything which aligned agree would
|
|
110
|
+
* be the wrong population: the top-k is ranked by gist, not by frame, so it
|
|
111
|
+
* mixes them — measured, `How do you say 'flurbish' in French?` returns one
|
|
112
|
+
* instance of its own frame and a dozen of `How do you make …`, which align
|
|
113
|
+
* on the shared opening and put their slot somewhere else entirely. One
|
|
114
|
+
* dissenting frame would then veto every binding.
|
|
115
|
+
*
|
|
116
|
+
* Instances of ONE frame put their slots in ONE place, so grouping by the
|
|
117
|
+
* whole slot signature and keeping the modal group IS the frame. The
|
|
118
|
+
* signature is every slot, not one: two candidates agreeing about a file name
|
|
119
|
+
* but disagreeing about whether a second thing was named are instances of two
|
|
120
|
+
* different frames, and mixing them would let a one-slot instance vouch for a
|
|
121
|
+
* two-slot binding it says nothing about. */
|
|
122
|
+
function electFrame(
|
|
123
|
+
inventory: ReadonlyArray<FrameInstance>,
|
|
124
|
+
W: number,
|
|
125
|
+
queryLen: number,
|
|
126
|
+
): FrameInstance[] {
|
|
127
|
+
const bySignature = new Map<string, FrameInstance[]>();
|
|
128
|
+
for (const inst of inventory) {
|
|
129
|
+
if (!voiceable(inst, W, queryLen)) continue;
|
|
130
|
+
const key = inst.slots.map((s) => `${s.qs}:${s.qe}`).join(",");
|
|
131
|
+
const group = bySignature.get(key);
|
|
132
|
+
if (group === undefined) bySignature.set(key, [inst]);
|
|
133
|
+
else group.push(inst);
|
|
134
|
+
}
|
|
135
|
+
let best: FrameInstance[] = [];
|
|
136
|
+
for (const group of bySignature.values()) {
|
|
137
|
+
// Ties keep the FIRST group in insertion order, which is resonance rank —
|
|
138
|
+
// corpus-determined, like every other tie-break here (§2.1).
|
|
139
|
+
if (group.length > best.length) best = group;
|
|
140
|
+
}
|
|
141
|
+
return best;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Voice the query's referents through their frame's own attested carriage, or
|
|
145
|
+
* null when the corpus does not attest one. */
|
|
146
|
+
export async function bindReference(
|
|
147
|
+
ctx: MindContext,
|
|
148
|
+
query: Uint8Array,
|
|
149
|
+
pre: Precomputed,
|
|
150
|
+
): Promise<MechanismResult | null> {
|
|
151
|
+
const t = ctx.trace?.enter("bindReference", [rItem(query, "query")]);
|
|
152
|
+
const fail = traceFail(t);
|
|
153
|
+
|
|
154
|
+
const frame = electFrame(
|
|
155
|
+
await pre.frames(),
|
|
156
|
+
ctx.space.maxGroup,
|
|
157
|
+
query.length,
|
|
158
|
+
);
|
|
159
|
+
if (frame.length < MIN_INSTANCES) {
|
|
160
|
+
return fail(
|
|
161
|
+
`${frame.length} instance(s) of one frame — agreement needs ` +
|
|
162
|
+
`${MIN_INSTANCES}`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
const slots = frame[0].slots.map((s): [number, number] => [s.qs, s.qe]);
|
|
166
|
+
const fillersOf = (inst: FrameInstance) => inst.slots.map((s) => s.filler);
|
|
167
|
+
const referents = slots.map(([s, e]) => query.subarray(s, e));
|
|
168
|
+
|
|
169
|
+
// A referent must be the ASKER's, never the engine's own words. A completed
|
|
170
|
+
// reply stays available to recognition and the climb as context, but quoting
|
|
171
|
+
// it back as a referent launders the engine's own output into evidence — the
|
|
172
|
+
// same rule the weave applies when it aligns only the asker's stream. ANY
|
|
173
|
+
// slot falling inside one refuses the whole binding.
|
|
174
|
+
for (const [rs, re] of slots) {
|
|
175
|
+
for (const [as_, ae] of ctx.answeredSpans) {
|
|
176
|
+
if (rs < ae && as_ < re) {
|
|
177
|
+
return fail(
|
|
178
|
+
"a referent lies inside a completed reply — not the asker's",
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Two slots may not name the same bytes, for the same reason two fillers may
|
|
184
|
+
// not (frameSlots applies it to every instance): the mapping from occurrence
|
|
185
|
+
// to referent would be ambiguous.
|
|
186
|
+
if (!distinct(referents)) {
|
|
187
|
+
return fail("two slots name the same bytes — the binding is ambiguous");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── THE LICENCE ────────────────────────────────────────────────────────
|
|
191
|
+
// Every instance must agree, against the first, that its continuation is its
|
|
192
|
+
// own fillers carried through one fixed form. Unanimity, exactly as the
|
|
193
|
+
// bridge's `unanimous` demands of a frame before it will substitute.
|
|
194
|
+
//
|
|
195
|
+
// Continuations are followed ONE AT A TIME, inside the test, because the
|
|
196
|
+
// overwhelmingly common outcome is refusal and a refusal usually comes from
|
|
197
|
+
// the first comparison — a filler-dependent frame disagrees on instance 2 of
|
|
198
|
+
// 12. Reading all of them up front pays the whole cohort's projections to
|
|
199
|
+
// discard them. Each goes through the shared projection, so an ambiguous
|
|
200
|
+
// instance is disambiguated exactly as it would be anywhere else.
|
|
201
|
+
const leadsNowhere = "an instance of the frame leads nowhere";
|
|
202
|
+
const first = await follow(ctx, frame[0].id, pre.guide);
|
|
203
|
+
if (first === null || first.length === 0) return fail(leadsNowhere);
|
|
204
|
+
for (let i = 1; i < frame.length; i++) {
|
|
205
|
+
const cont = await follow(ctx, frame[i].id, pre.guide);
|
|
206
|
+
if (cont === null || cont.length === 0) return fail(leadsNowhere);
|
|
207
|
+
if (
|
|
208
|
+
!carriesFillers(first, fillersOf(frame[0]), cont, fillersOf(frame[i]))
|
|
209
|
+
) {
|
|
210
|
+
ctx.trace?.step(
|
|
211
|
+
"referenceLicence",
|
|
212
|
+
[rItem(first, "instance"), rNode(ctx, frame[i].id, "against")],
|
|
213
|
+
[rItem(cont, "attested")],
|
|
214
|
+
"refused — the frame's answer carries content that depends on WHICH " +
|
|
215
|
+
"filler, so the corpus cannot supply it for a new one",
|
|
216
|
+
);
|
|
217
|
+
return fail("the frame's answer is not a carriage of its fillers");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// THE BINDING IS A BYTE CONSTRUCTION, NOT A SEARCH, and deliberately so.
|
|
222
|
+
// Articulation splices through ctx.search.cover because WHICH voicing wins is
|
|
223
|
+
// genuinely searched — several candidate substitutions compete. Here the
|
|
224
|
+
// licence has already determined the substitution byte-exactly; a search that
|
|
225
|
+
// can only confirm a determined result is ceremony, not composition. The
|
|
226
|
+
// precedent is frame-filler, which constructs its lookup key the same way.
|
|
227
|
+
const bytes = substituteAll(
|
|
228
|
+
first,
|
|
229
|
+
fillersOf(frame[0]).map((needle, s) => ({ needle, repl: referents[s] })),
|
|
230
|
+
);
|
|
231
|
+
if (bytes.length === 0) return fail("the binding produced nothing");
|
|
232
|
+
// Answering with the question is not answering — the same restated-fragment
|
|
233
|
+
// guard every recall tier applies.
|
|
234
|
+
if (bytes.length < query.length && indexOf(query, bytes, 0) >= 0) {
|
|
235
|
+
return fail("the binding restates part of the question");
|
|
236
|
+
}
|
|
237
|
+
const carried = !bytesEqual(bytes, first);
|
|
238
|
+
|
|
239
|
+
ctx.trace?.step(
|
|
240
|
+
"bindReferent",
|
|
241
|
+
[
|
|
242
|
+
...referents.map((r, s) =>
|
|
243
|
+
rItem(r, `referent ${s + 1}`, undefined, slots[s])
|
|
244
|
+
),
|
|
245
|
+
...frame.map((inst) => rNode(ctx, inst.id, "instance")),
|
|
246
|
+
],
|
|
247
|
+
[rItem(bytes, "bound")],
|
|
248
|
+
carried
|
|
249
|
+
? `carry the asker's ${slots.length} referent(s) through the frame's own ` +
|
|
250
|
+
`answer — ${frame.length} instances attest the carriage byte-exactly`
|
|
251
|
+
: `the frame's answer does not depend on its filler(s) — ` +
|
|
252
|
+
`${frame.length} instances attest the same continuation`,
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
// WHAT THIS EXPLAINS: the frame it matched literally, AND every slot. A slot
|
|
256
|
+
// is not a hole in the explanation — it is an act this mechanism PAID for,
|
|
257
|
+
// one STEP each in `moves` below, and leaving it unaccounted charges the same
|
|
258
|
+
// act twice (once as a move, once at PASS per byte, which is far the larger).
|
|
259
|
+
// The bridge records the same reasoning for its own substitutions.
|
|
260
|
+
const accounted: Array<[number, number]> = [
|
|
261
|
+
...frame[0].matched.filter(([s, e]) => e > s),
|
|
262
|
+
...slots,
|
|
263
|
+
].sort((a, b) => a[0] - b[0]);
|
|
264
|
+
|
|
265
|
+
t?.done(
|
|
266
|
+
[rItem(bytes, "answer")],
|
|
267
|
+
"reference — the asker's referent(s) voiced through a slot the corpus " +
|
|
268
|
+
"attests as a carriage",
|
|
269
|
+
);
|
|
270
|
+
return {
|
|
271
|
+
bytes,
|
|
272
|
+
accounted,
|
|
273
|
+
// The acts: one BINDING per slot plus one edge FOLLOW across the frame's
|
|
274
|
+
// own continuation — the same per-projection price CAST's substitution
|
|
275
|
+
// schema pays for the structurally analogous act. Not CONCEPT: the ladder
|
|
276
|
+
// reserves that for halo-mediated acts, and a reference is decided by byte
|
|
277
|
+
// identity, not distributional company. Per-slot matters: a two-slot
|
|
278
|
+
// binding claims strictly more than a one-slot binding, so where both are
|
|
279
|
+
// licensed the smaller claim wins.
|
|
280
|
+
moves: STEP * slots.length + STEP,
|
|
281
|
+
unexplained: unexplainedLabel(query, accounted),
|
|
282
|
+
// NOT scaffolding. That field counts answer bytes carried through BECAUSE
|
|
283
|
+
// NOTHING EXPLAINED THEM; a referent is carried because the frame's slot
|
|
284
|
+
// explains it, and it is accounted above. Reporting it would make every
|
|
285
|
+
// licensed binding lose its equal-grade tie-breaks by construction.
|
|
286
|
+
//
|
|
287
|
+
// COMPLETE — post-grounding must not extend a binding. The bound answer is
|
|
288
|
+
// a byte string this mechanism CONSTRUCTED; the corpus never said it, so
|
|
289
|
+
// pivoting through it treats the engine's own construction as a trained
|
|
290
|
+
// fact — the same laundering the answeredSpans guard above refuses in the
|
|
291
|
+
// other direction. Measured: with the frame "How do I compile X?" ->
|
|
292
|
+
// "Run gcc X" and a stored "Run gcc main.c" -> "then execute ./a.out", the
|
|
293
|
+
// binding produced "Run gcc main.c" and reason() pivoted straight past it,
|
|
294
|
+
// answering "then execute ./a.out" — the referent gone AND the question
|
|
295
|
+
// unanswered. This is what MechanismResult.complete documents: the query
|
|
296
|
+
// IS a trained context (here, an instance of a trained frame), so its
|
|
297
|
+
// continuation is the whole read-out. Fusion still runs; it is gated on
|
|
298
|
+
// the query REMAINDER, which a binding accounting frame plus slots leaves
|
|
299
|
+
// empty.
|
|
300
|
+
complete: true,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ── Pipeline mechanism ──────────────────────────────────────────────────────
|
|
305
|
+
|
|
306
|
+
export const referenceMechanism: PipelineMechanism = {
|
|
307
|
+
name: "reference",
|
|
308
|
+
provenance: "reference",
|
|
309
|
+
async floor(ctx, query, pre, worthRunning) {
|
|
310
|
+
// The floor is exactly one binding plus one follow — the cheapest shape a
|
|
311
|
+
// result can take. INVESTMENT DISCIPLINE: when that already cannot beat
|
|
312
|
+
// the incumbent, return it UNINVESTED rather than first-touching the shared
|
|
313
|
+
// inventory (cast.ts and extraction.ts are the reference implementations).
|
|
314
|
+
const bound = STEP + STEP;
|
|
315
|
+
if (!worthRunning(bound)) return bound;
|
|
316
|
+
// A frame needs a query long enough to hold one, and a slot needs at least
|
|
317
|
+
// one window of its own beside it.
|
|
318
|
+
if (query.length < 2 * ctx.space.maxGroup) return null;
|
|
319
|
+
// A query the store holds outright is not a reference to anything — its own
|
|
320
|
+
// edges answer it, and binding would re-derive what recall reads directly.
|
|
321
|
+
// O(|query|) probes, already computed for this response.
|
|
322
|
+
if (pre.queryResolved !== null) return null;
|
|
323
|
+
// NO SCAFFOLDING GATE HERE, DELIBERATELY. `allWindowsAreScaffolding` gates
|
|
324
|
+
// recall's scaffolding-dominated tier and looks like the obvious third
|
|
325
|
+
// gate, but it is calibrated for mechanisms grounding THROUGH the query's
|
|
326
|
+
// stored windows, where "every window is a hub" means the query says
|
|
327
|
+
// nothing the corpus can be held to. A reference query's discriminative
|
|
328
|
+
// content is the SLOT — exactly the part the corpus cannot attest — so the
|
|
329
|
+
// predicate reports "all scaffolding" for the purest references there are.
|
|
330
|
+
// Measured on the byte-modality fixture (test/76): the frame's windows sit
|
|
331
|
+
// in all three instances and the referents' in none, so the gate fired and
|
|
332
|
+
// the mechanism abstained on an answer it had already derived correctly.
|
|
333
|
+
//
|
|
334
|
+
// What holds this honest is not window rarity but the licence: instances
|
|
335
|
+
// agreeing on one slot signature, each leading somewhere, and unanimous
|
|
336
|
+
// BYTE-EXACT carriage across all of them.
|
|
337
|
+
return bound;
|
|
338
|
+
},
|
|
339
|
+
async run(ctx, query, pre) {
|
|
340
|
+
const bound = await bindReference(ctx, query, pre);
|
|
341
|
+
return bound === null ? [] : [bound];
|
|
342
|
+
},
|
|
343
|
+
};
|
package/src/mind/mind.ts
CHANGED
|
@@ -304,7 +304,7 @@ export class Mind implements MindContext {
|
|
|
304
304
|
/** Per-response memo of {@link chooseNext} picks — ensures every mechanism
|
|
305
305
|
* of a single response follows the SAME continuation for each ambiguous
|
|
306
306
|
* context node. */
|
|
307
|
-
_edgeChoice = new Map<number, number>();
|
|
307
|
+
_edgeChoice: Map<number, number> = new Map<number, number>();
|
|
308
308
|
|
|
309
309
|
/** Previous deposit's seen node ids for incremental change detection. */
|
|
310
310
|
_prevSeen: Set<number> | null = null;
|
|
@@ -312,7 +312,7 @@ export class Mind implements MindContext {
|
|
|
312
312
|
/** Session cache of node-id → perceived gist for candidate scoring — see
|
|
313
313
|
* {@link MindContext._gistCache}. 32 MB ≈ 8K gists at D=1024; hub
|
|
314
314
|
* candidate sets (√N at most) fit comfortably and recur across queries. */
|
|
315
|
-
_gistCache = new BoundedMap<number, Vec>(
|
|
315
|
+
_gistCache: BoundedMap<number, Vec> = new BoundedMap<number, Vec>(
|
|
316
316
|
32_000_000,
|
|
317
317
|
(v) => v.byteLength,
|
|
318
318
|
);
|
|
@@ -320,12 +320,16 @@ export class Mind implements MindContext {
|
|
|
320
320
|
// bounded: a pyramid costs ~KB per content byte (one D-float gist per
|
|
321
321
|
// interior node), and only the few live conversation chains need to stay
|
|
322
322
|
// warm, so 8 entries is the honest budget.
|
|
323
|
-
_depositTrees
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
323
|
+
_depositTrees: BoundedMap<string, import("./types.js").DepositCacheEntry> =
|
|
324
|
+
new BoundedMap<
|
|
325
|
+
string,
|
|
326
|
+
import("./types.js").DepositCacheEntry
|
|
327
|
+
>(8);
|
|
328
|
+
_depositLens: Set<number> = new Set<number>();
|
|
329
|
+
_internIds: WeakMap<import("../sema.js").Sema, number> = new WeakMap<
|
|
330
|
+
import("../sema.js").Sema,
|
|
331
|
+
number
|
|
332
|
+
>();
|
|
329
333
|
|
|
330
334
|
// ── Conversation state ──────────────────────────────────────────────────
|
|
331
335
|
|