@hviana/sema 0.2.9 → 0.4.0
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 +71 -5
- package/dist/src/derive/src/deduction.d.ts +12 -1
- package/dist/src/derive/src/deduction.js +5 -1
- package/dist/src/derive/src/index.d.ts +1 -0
- package/dist/src/geometry.d.ts +3 -30
- package/dist/src/geometry.js +330 -82
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/meter.d.ts +171 -0
- package/dist/src/meter.js +269 -0
- package/dist/src/mind/attention.d.ts +5 -4
- package/dist/src/mind/attention.js +254 -23
- package/dist/src/mind/bridge.d.ts +10 -1
- package/dist/src/mind/bridge.js +179 -10
- package/dist/src/mind/canonical.d.ts +6 -1
- package/dist/src/mind/canonical.js +6 -1
- package/dist/src/mind/graph-search.d.ts +9 -0
- package/dist/src/mind/graph-search.js +59 -19
- package/dist/src/mind/index.d.ts +2 -0
- package/dist/src/mind/junction.d.ts +10 -0
- package/dist/src/mind/junction.js +14 -0
- package/dist/src/mind/learning.d.ts +32 -4
- package/dist/src/mind/learning.js +26 -4
- package/dist/src/mind/match.d.ts +40 -0
- package/dist/src/mind/match.js +125 -1
- package/dist/src/mind/mechanisms/cast.js +63 -6
- package/dist/src/mind/mechanisms/extraction.d.ts +0 -34
- package/dist/src/mind/mechanisms/extraction.js +1 -88
- package/dist/src/mind/mechanisms/recall.d.ts +3 -0
- package/dist/src/mind/mechanisms/recall.js +77 -14
- package/dist/src/mind/mind.d.ts +59 -5
- package/dist/src/mind/mind.js +115 -93
- package/dist/src/mind/pipeline-mechanism.d.ts +33 -3
- package/dist/src/mind/pipeline-mechanism.js +179 -10
- package/dist/src/mind/pipeline.d.ts +29 -0
- package/dist/src/mind/pipeline.js +79 -21
- package/dist/src/mind/primitives.d.ts +11 -15
- package/dist/src/mind/primitives.js +47 -28
- package/dist/src/mind/reasoning.d.ts +7 -1
- package/dist/src/mind/reasoning.js +40 -8
- package/dist/src/mind/recognition.js +93 -20
- package/dist/src/mind/traverse.d.ts +11 -0
- package/dist/src/mind/traverse.js +88 -7
- package/dist/src/mind/types.d.ts +39 -5
- package/dist/src/store.d.ts +15 -0
- package/dist/src/store.js +91 -6
- package/package.json +1 -1
- package/src/derive/src/deduction.ts +15 -0
- package/src/derive/src/index.ts +1 -0
- package/src/geometry.ts +350 -122
- package/src/index.ts +1 -0
- package/src/meter.ts +333 -0
- package/src/mind/attention.ts +276 -31
- package/src/mind/bridge.ts +187 -10
- package/src/mind/canonical.ts +6 -1
- package/src/mind/graph-search.ts +60 -21
- package/src/mind/index.ts +6 -0
- package/src/mind/junction.ts +12 -0
- package/src/mind/learning.ts +46 -5
- package/src/mind/match.ts +146 -1
- package/src/mind/mechanisms/cast.ts +62 -6
- package/src/mind/mechanisms/extraction.ts +2 -103
- package/src/mind/mechanisms/recall.ts +84 -17
- package/src/mind/mind.ts +144 -99
- package/src/mind/pipeline-mechanism.ts +203 -13
- package/src/mind/pipeline.ts +144 -36
- package/src/mind/primitives.ts +49 -33
- package/src/mind/reasoning.ts +39 -7
- package/src/mind/recognition.ts +89 -19
- package/src/mind/traverse.ts +89 -8
- package/src/mind/types.ts +39 -5
- package/src/store.ts +75 -6
- package/test/14-scaling.test.mjs +17 -7
- package/test/31-audit.test.mjs +4 -1
- package/test/33-multi-candidate.test.mjs +13 -1
- package/test/36-already-answered-fusion.test.mjs +10 -3
- package/test/46-recognise-multibyte-edge.test.mjs +3 -3
- package/test/53-cross-region-probe-instrumentation.test.mjs +36 -6
- package/test/54-evidence-k-instrumentation.test.mjs +175 -0
- package/test/55-cost-meter.test.mjs +284 -0
- package/test/56-bridge-identity-admission.test.mjs +209 -0
- package/test/57-fusion-order.test.mjs +104 -0
- package/test/58-subquantum-sites.test.mjs +112 -0
- package/test/59-fold-invariance.test.mjs +226 -0
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
// 3. VISIBLE BUDGET — every mechanism carries its own caps internally (√N, k).
|
|
13
13
|
// 4. TRAVELING EVIDENCE — run() returns MechanismResult with accounted, moves,
|
|
14
14
|
// and unexplained. The pipeline computes the weight.
|
|
15
|
+
import { indexOf } from "../bytes.js";
|
|
16
|
+
import { dominates } from "../geometry.js";
|
|
15
17
|
import { windowIds } from "./canonical.js";
|
|
16
18
|
import { read, resolve } from "./primitives.js";
|
|
17
|
-
import { alignGraded } from "./match.js";
|
|
19
|
+
import { alignGraded, skillExemplar } from "./match.js";
|
|
18
20
|
import { climbAttentionAll } from "./attention.js";
|
|
19
|
-
import {
|
|
21
|
+
import { sharedReachMemo } from "./traverse.js";
|
|
20
22
|
// ── Precomputed ──────────────────────────────────────────────────────────────
|
|
21
23
|
//
|
|
22
24
|
// Precomputed is a LAZY container for structural analyses of the query — the
|
|
@@ -88,29 +90,46 @@ export class Precomputed {
|
|
|
88
90
|
return w;
|
|
89
91
|
}
|
|
90
92
|
/** Shared memo for {@link reachOf} (structural-IDF reads): a window's
|
|
91
|
-
* ancestor reach is a pure function of the read-only store, so one
|
|
92
|
-
*
|
|
93
|
-
|
|
93
|
+
* ancestor reach is a pure function of the read-only store, so one memo
|
|
94
|
+
* serves every mechanism that prices commonality — AND the consensus
|
|
95
|
+
* climb, which is the largest consumer and used to build its own. The
|
|
96
|
+
* ONE definition of its lifetime lives in traverse.ts
|
|
97
|
+
* ({@link sharedReachMemo}): response-scoped for respond(),
|
|
98
|
+
* conversation-scoped across turns, always cold under a trace. */
|
|
99
|
+
_reach;
|
|
100
|
+
get reachMemo() {
|
|
101
|
+
return this._reach ??= sharedReachMemo(this.ctx);
|
|
102
|
+
}
|
|
94
103
|
// ── Expensive lazy analyses ───────────────────────────────────────────
|
|
95
104
|
//
|
|
96
105
|
// Async, cached-by-promise: the first caller starts the computation, every
|
|
97
106
|
// later caller (any mechanism, any phase) awaits the same promise. A
|
|
98
107
|
// mechanism MUST check its cheap floor gates and the pipeline's
|
|
99
108
|
// `worthRunning` predicate before first-touching one of these.
|
|
109
|
+
/** Charge a lazily-shared analysis to its OWN phase rather than to the
|
|
110
|
+
* mechanism that happened to first-touch it. Without this the profile
|
|
111
|
+
* reads as "cast.floor costs 2 s" when what actually cost 2 s is the
|
|
112
|
+
* consensus climb — which cast merely paid for on everyone's behalf, and
|
|
113
|
+
* which every later consumer then got free. Attribution must follow the
|
|
114
|
+
* work, not the caller. */
|
|
115
|
+
shared(phase, fn) {
|
|
116
|
+
const meter = this.ctx.meter;
|
|
117
|
+
return meter ? meter.time(phase, fn) : fn();
|
|
118
|
+
}
|
|
100
119
|
_attention;
|
|
101
120
|
/** The full consensus climb (roots + ranked anchors) — the query-level
|
|
102
121
|
* evidence CAST, confluence, extraction, recall's scaffolding tier, and
|
|
103
122
|
* fusion all share. Computed on first access; a query no mechanism
|
|
104
123
|
* climbs for (e.g. one an extension decided outright) never pays for it. */
|
|
105
124
|
attention() {
|
|
106
|
-
return this._attention ??= climbAttentionAll(this.ctx, this.query, this.k);
|
|
125
|
+
return this._attention ??= this.shared("attention", () => climbAttentionAll(this.ctx, this.query, this.k));
|
|
107
126
|
}
|
|
108
127
|
_weave;
|
|
109
128
|
/** Result of {@link alignGraded} for the first k ranked anchors —
|
|
110
129
|
* O(k · |query| · |ctx|). Consumed by CAST; reusable by any future
|
|
111
130
|
* mechanism doing analogical transfer. */
|
|
112
131
|
weave() {
|
|
113
|
-
return this._weave ??= this.attention().then((climb) => computeWeave(this.ctx, this.query, this, climb));
|
|
132
|
+
return this._weave ??= this.attention().then((climb) => this.shared("weave", async () => computeWeave(this.ctx, this.query, this, climb)));
|
|
114
133
|
}
|
|
115
134
|
/** Span-shaped classification of one ranked anchor, memoised per anchor id
|
|
116
135
|
* so repeated calls (extraction's own early-exit scan, any future
|
|
@@ -124,7 +143,7 @@ export class Precomputed {
|
|
|
124
143
|
spanShapedOf(anchor) {
|
|
125
144
|
let p = this._spanShaped.get(anchor);
|
|
126
145
|
if (p === undefined) {
|
|
127
|
-
p = skillExemplar(this.ctx, anchor, this.guide);
|
|
146
|
+
p = this.shared("spanShaped", () => skillExemplar(this.ctx, anchor, this.guide));
|
|
128
147
|
this._spanShaped.set(anchor, p);
|
|
129
148
|
}
|
|
130
149
|
return p;
|
|
@@ -152,6 +171,7 @@ function computeWeave(ctx, query, pre, climb) {
|
|
|
152
171
|
const rankedCapped = ranked.length > pre.k ? ranked.slice(0, pre.k) : ranked;
|
|
153
172
|
const depth = new Float64Array(query.length);
|
|
154
173
|
const points = [];
|
|
174
|
+
const byAnchor = new Map();
|
|
155
175
|
// WEAVE-SCALE anchors only: CAST transfers structure between things the
|
|
156
176
|
// QUERY weaves together — query-scale structures. A context an order of
|
|
157
177
|
// magnitude beyond the query is not woven BY the query (the query can at
|
|
@@ -166,6 +186,34 @@ function computeWeave(ctx, query, pre, climb) {
|
|
|
166
186
|
// uncapped weaves spent 5–8s per query recognising conversation-length
|
|
167
187
|
// anchors that could never form a weave point.
|
|
168
188
|
const capBytes = query.length * quantum;
|
|
189
|
+
// EXCLUSIVITY IS ARBITRATED BY THE CLIMB'S VOTE ORDER, DELIBERATELY. A query
|
|
190
|
+
// byte can only be independent evidence for ONE point, so points are built in
|
|
191
|
+
// ranked order and each new point's runs are trimmed against every point
|
|
192
|
+
// already accepted; a point left with no run of a full quantum drops out of
|
|
193
|
+
// the weave.
|
|
194
|
+
//
|
|
195
|
+
// That reads like first-come-wins — a point that merely ranked higher taking
|
|
196
|
+
// a span from the point that actually explains it — and arbitrating by LOCAL
|
|
197
|
+
// evidence instead (ownership of each byte to the longest covering run, then
|
|
198
|
+
// the heavier weight, then rank) was implemented and MEASURED: test/29 went
|
|
199
|
+
// 9/2 to 7/4, and the new failures name the reason. CAST requires the weave
|
|
200
|
+
// to touch a COMMITTED point of attention ("2 aligned structure(s), but none
|
|
201
|
+
// is one of the climb's 1 committed root(s)"), and it was precisely the vote
|
|
202
|
+
// order that kept the committed root's own point alive in the weave. Local
|
|
203
|
+
// run length knows nothing about what the climb settled on, so it evicted the
|
|
204
|
+
// root's evidence and left CAST refusing on its own consistency check.
|
|
205
|
+
//
|
|
206
|
+
// So the vote order here is not an accident of construction — it is what
|
|
207
|
+
// holds the weave and the climb to the same conclusion. Weave-local
|
|
208
|
+
// measures decide what is FRAME inside the weave (see the frame gates in
|
|
209
|
+
// cast.ts); which structures are in the weave at all stays the climb's call.
|
|
210
|
+
//
|
|
211
|
+
// TWO PASSES. `depth` — how much of the weave agrees on each query byte, and
|
|
212
|
+
// therefore what counts as FRAME — must be the whole weave's, not "whatever
|
|
213
|
+
// has been processed so far": read in one pass it made a candidate's own
|
|
214
|
+
// frame reading depend on its rank, and the proposed-run gate below needs the
|
|
215
|
+
// real thing.
|
|
216
|
+
const cands = [];
|
|
169
217
|
for (const cand of rankedCapped) {
|
|
170
218
|
const ctxBytes = read(ctx, cand.anchor, capBytes + 1);
|
|
171
219
|
if (ctxBytes.length === 0 || ctxBytes.length > capBytes)
|
|
@@ -177,6 +225,9 @@ function computeWeave(ctx, query, pre, climb) {
|
|
|
177
225
|
for (let i = r.qs; i < r.qe; i++)
|
|
178
226
|
depth[i] += r.weight;
|
|
179
227
|
}
|
|
228
|
+
cands.push({ cand, ctxBytes, raw });
|
|
229
|
+
}
|
|
230
|
+
for (const { cand, ctxBytes, raw } of cands) {
|
|
180
231
|
const free = [];
|
|
181
232
|
for (const r of raw) {
|
|
182
233
|
let { qs, qe, cs, weight } = r;
|
|
@@ -201,12 +252,130 @@ function computeWeave(ctx, query, pre, climb) {
|
|
|
201
252
|
}
|
|
202
253
|
}
|
|
203
254
|
if (free.length > 0) {
|
|
204
|
-
|
|
255
|
+
const pt = {
|
|
205
256
|
anchor: cand.anchor,
|
|
206
257
|
vote: cand.vote,
|
|
207
258
|
ctx: ctxBytes,
|
|
208
259
|
runs: free,
|
|
209
|
-
}
|
|
260
|
+
};
|
|
261
|
+
byAnchor.set(cand.anchor, pt);
|
|
262
|
+
points.push(pt);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// A byte is FRAME when more than half the weave shares it, and a SPAN is
|
|
266
|
+
// frame when more than half its bytes are — the same two-level
|
|
267
|
+
// half-dominance reading cast.ts's own frame gate uses, over the same
|
|
268
|
+
// `depth`. Read against the accepted POINTS (as cast.ts does), so it is
|
|
269
|
+
// only meaningful once phase 1 has run.
|
|
270
|
+
const framed = (from, to) => {
|
|
271
|
+
let n = 0;
|
|
272
|
+
for (let i = from; i < to; i++)
|
|
273
|
+
if (dominates(depth[i], points.length))
|
|
274
|
+
n++;
|
|
275
|
+
return dominates(n, to - from);
|
|
276
|
+
};
|
|
277
|
+
// PHASE 2 — THE CLIMB'S OWN CONCLUSION IS AN ALIGNMENT THE LITERAL MATCHER
|
|
278
|
+
// CANNOT SEE. `alignRuns` seeds on W-grams, so two forms differing by a
|
|
279
|
+
// single byte share no run at all: on `How is ice like steel?` against a
|
|
280
|
+
// store holding `Ice is cold`, the query's `ice` and the stored `Ice` agree
|
|
281
|
+
// on only `ce ` — three bytes, never seeded — so that structure entered the
|
|
282
|
+
// weave carrying nothing but the ` is ` scaffolding every exemplar shares,
|
|
283
|
+
// lost it to the first point that claimed it, and vanished. The climb had
|
|
284
|
+
// ALREADY identified it: its resonance elected `Ice is cold` from the query
|
|
285
|
+
// span `ce l` and `Steel is hard` from `stee`, two disjoint spans each naming
|
|
286
|
+
// its own structure, weighed through the region's contrastive margin and its
|
|
287
|
+
// IDF — gates the aligner has no equivalent of.
|
|
288
|
+
//
|
|
289
|
+
// So the climb PROPOSES the pairing (which structure, which query span) and
|
|
290
|
+
// bytes DECIDE its terms (§2.3). Three gates, each one measured:
|
|
291
|
+
//
|
|
292
|
+
// • it may only take query bytes NO literal run claimed. Run inline with
|
|
293
|
+
// phase 1 this did the opposite of "exact decides" — a higher-ranked
|
|
294
|
+
// candidate's proposal trimmed a lower-ranked candidate's byte-for-byte
|
|
295
|
+
// match out of existence (`he W`, proposed for `a nickname meaning the
|
|
296
|
+
// divine one`, cut the literal `The ` out of `The Starry Night was
|
|
297
|
+
// painted by Vincent van Gogh.` and CAST's redirection lost its
|
|
298
|
+
// dominant — test/29 C4). Hence a second pass, after every literal run
|
|
299
|
+
// is placed.
|
|
300
|
+
// • the literal agreement must DOMINATE the span. A climb vote is not by
|
|
301
|
+
// itself an alignment: on `The Persistence of Memory was painted by
|
|
302
|
+
// Salvador Dali.` the climb elects `The Starry Night…` from the span
|
|
303
|
+
// ` Dali.`, which shares barely a byte with it — the resonance was
|
|
304
|
+
// carried by the frame those exemplars share. Admitting it let CAST
|
|
305
|
+
// weave points out of pure scaffolding and out-account the correct
|
|
306
|
+
// extraction (test/00, test/24). Where the proposal is real the
|
|
307
|
+
// agreement is overwhelming: both C1 spans agree on three of four bytes.
|
|
308
|
+
// • and the span must not be FRAME. Literal dominance alone is too weak
|
|
309
|
+
// at this scale — a 4-byte span agrees three-of-four with half the
|
|
310
|
+
// corpus by accident (`he W` against `a nickname meaning the divine
|
|
311
|
+
// one`). Frame is the weave-local measure of exactly that.
|
|
312
|
+
const claimed = new Uint8Array(query.length);
|
|
313
|
+
for (const p of points) {
|
|
314
|
+
for (const r of p.runs)
|
|
315
|
+
claimed.fill(1, r.qs, r.qe);
|
|
316
|
+
}
|
|
317
|
+
for (const { cand, ctxBytes } of cands) {
|
|
318
|
+
if (cand.end > cand.start) {
|
|
319
|
+
let qs = cand.start;
|
|
320
|
+
let qe = cand.end;
|
|
321
|
+
while (qs < qe && claimed[qs])
|
|
322
|
+
qs++;
|
|
323
|
+
while (qe > qs && claimed[qe - 1])
|
|
324
|
+
qe--;
|
|
325
|
+
let clear = true;
|
|
326
|
+
for (let i = qs; i < qe; i++)
|
|
327
|
+
if (claimed[i])
|
|
328
|
+
clear = false;
|
|
329
|
+
if (clear && qe - qs >= Math.min(quantum, ctxBytes.length)) {
|
|
330
|
+
// The gate only asks whether the agreement DOMINATES the span, so
|
|
331
|
+
// search DOWNWARD from the whole span and stop at the first hit: the
|
|
332
|
+
// first length found is both the longest agreement and, by
|
|
333
|
+
// construction, already past the dominance bar. At most O(W²) bounded
|
|
334
|
+
// substring probes — a span is one segment (≤ 2W) — where a full
|
|
335
|
+
// longest-common-substring scan would be O(|span|² · |ctx|) against a
|
|
336
|
+
// context that may be W× the query.
|
|
337
|
+
const span = query.subarray(qs, qe);
|
|
338
|
+
const bar = Math.floor(span.length / 2) + 1; // dominates(bar, length)
|
|
339
|
+
let bestLen = 0;
|
|
340
|
+
let bestCs = 0;
|
|
341
|
+
for (let len = span.length; len >= bar && bestLen === 0; len--) {
|
|
342
|
+
for (let off = 0; off + len <= span.length; off++) {
|
|
343
|
+
const at = indexOf(ctxBytes, span.subarray(off, off + len), 0);
|
|
344
|
+
if (at < 0)
|
|
345
|
+
continue;
|
|
346
|
+
bestLen = len;
|
|
347
|
+
// Where the span's FIRST byte lands, so `cs` means the same thing
|
|
348
|
+
// it does for a literal run: the context offset the run starts at.
|
|
349
|
+
bestCs = Math.max(0, at - off);
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (bestLen > 0 && !framed(qs, qe)) {
|
|
354
|
+
const run = {
|
|
355
|
+
qs,
|
|
356
|
+
qe,
|
|
357
|
+
cs: bestCs,
|
|
358
|
+
weight: bestLen / (qe - qs),
|
|
359
|
+
proposed: true,
|
|
360
|
+
};
|
|
361
|
+
claimed.fill(1, qs, qe);
|
|
362
|
+
const pt = byAnchor.get(cand.anchor);
|
|
363
|
+
if (!pt) {
|
|
364
|
+
const made = {
|
|
365
|
+
anchor: cand.anchor,
|
|
366
|
+
vote: cand.vote,
|
|
367
|
+
ctx: ctxBytes,
|
|
368
|
+
runs: [run],
|
|
369
|
+
};
|
|
370
|
+
byAnchor.set(cand.anchor, made);
|
|
371
|
+
points.push(made);
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
pt.runs.push(run);
|
|
375
|
+
pt.runs.sort((x, y) => x.qs - y.qs);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
210
379
|
}
|
|
211
380
|
}
|
|
212
381
|
return { points, depth };
|
|
@@ -8,6 +8,35 @@ export interface Thought {
|
|
|
8
8
|
bytes: Uint8Array;
|
|
9
9
|
provenance: Provenance;
|
|
10
10
|
}
|
|
11
|
+
/** Structured payload of the "decideGrounding" rationale step — the same
|
|
12
|
+
* numbers the human-readable candidate labels already carry, exposed as
|
|
13
|
+
* data so a downstream tool need not parse free text. Purely additive
|
|
14
|
+
* instrumentation: built only under `ctx.trace?.` (optional chaining
|
|
15
|
+
* short-circuits its arguments), never read by inference. */
|
|
16
|
+
export interface DecideGroundingData {
|
|
17
|
+
version: 1;
|
|
18
|
+
/** Every grounding candidate weighed, in consideration order. */
|
|
19
|
+
candidates: Array<{
|
|
20
|
+
provenance: string;
|
|
21
|
+
/** The candidate's exact weight in the one cost ladder. */
|
|
22
|
+
weight: number;
|
|
23
|
+
/** The DISCRETE grade the decision actually compares (floor(weight/STEP)). */
|
|
24
|
+
grade: number;
|
|
25
|
+
/** Query bytes the candidate's accounted spans leave unexplained. */
|
|
26
|
+
unexplainedBytes: number;
|
|
27
|
+
/** Whether this candidate won the decision. */
|
|
28
|
+
decided: boolean;
|
|
29
|
+
}>;
|
|
30
|
+
/** Grade margin between the winner and the runner-up, when both exist —
|
|
31
|
+
* the same quantity the "narrowDecision" step reports as narrow when
|
|
32
|
+
* ≤ 1. Absent for a single-candidate decision. */
|
|
33
|
+
runnerUpMargin?: number;
|
|
34
|
+
}
|
|
35
|
+
/** Structured payload of the "narrowDecision" rationale step. */
|
|
36
|
+
export interface NarrowDecisionData {
|
|
37
|
+
version: 1;
|
|
38
|
+
margin: number;
|
|
39
|
+
}
|
|
11
40
|
/** Think: a single lightest-derivation exploration of the Sema graph.
|
|
12
41
|
*
|
|
13
42
|
* Every answer travels the same path:
|
|
@@ -25,11 +25,16 @@ import { recallMechanism } from "./mechanisms/recall.js";
|
|
|
25
25
|
export { resolveConcepts, resolveConnectors } from "./mechanisms/cover.js";
|
|
26
26
|
export { aluToMechanism } from "./mechanisms/alu.js";
|
|
27
27
|
// ── Extension dispatch (pre-loop parse) ─────────────────────────────────────
|
|
28
|
-
async function collectComputed(mechanisms, query) {
|
|
28
|
+
async function collectComputed(ctx, mechanisms, query) {
|
|
29
29
|
const out = [];
|
|
30
|
+
const meter = ctx.meter;
|
|
30
31
|
for (const m of mechanisms) {
|
|
31
|
-
if (m.parse)
|
|
32
|
-
|
|
32
|
+
if (!m.parse)
|
|
33
|
+
continue;
|
|
34
|
+
const spans = meter
|
|
35
|
+
? await meter.time(`${m.name}.parse`, () => m.parse(query))
|
|
36
|
+
: await m.parse(query);
|
|
37
|
+
out.push(...spans);
|
|
33
38
|
}
|
|
34
39
|
return out;
|
|
35
40
|
}
|
|
@@ -76,7 +81,7 @@ export async function think(ctx, query, mechs) {
|
|
|
76
81
|
const mechanisms = mechs ?? defaultMechanisms;
|
|
77
82
|
const rec = recognise(ctx, query);
|
|
78
83
|
// Phase 1: collect computed spans from mechanisms that implement parse()
|
|
79
|
-
const computed = await collectComputed(mechanisms, query);
|
|
84
|
+
const computed = await collectComputed(ctx, mechanisms, query);
|
|
80
85
|
if (computed.length > 0) {
|
|
81
86
|
ctx.trace?.step("computeExtensions", [rItem(query, "query")], computed.map((u) => rItem(query.subarray(u.i, u.j), "operand", undefined, [u.i, u.j])), `extensions recognised and evaluated ${computed.length} computation(s)`);
|
|
82
87
|
for (const u of computed) {
|
|
@@ -99,14 +104,28 @@ export async function think(ctx, query, mechs) {
|
|
|
99
104
|
const consider = (c) => {
|
|
100
105
|
if (c.bytes.length === 0)
|
|
101
106
|
return;
|
|
107
|
+
if (ctx.meter)
|
|
108
|
+
ctx.meter.candidates++;
|
|
102
109
|
candidates.push(c);
|
|
103
110
|
if (best === null || grade(c.weight) < grade(best.weight))
|
|
104
111
|
best = c;
|
|
105
112
|
};
|
|
106
113
|
const worthRunning = (floor) => best === null || grade(floor) < grade(best.weight);
|
|
107
114
|
// Phase 3: grounding loop
|
|
115
|
+
// Per-mechanism accounting (src/meter.ts). The market's whole premise is
|
|
116
|
+
// that mechanisms compete on one cost scale — so the profiling read-out is
|
|
117
|
+
// also per-mechanism, uniformly: the loop never asks which one it holds.
|
|
118
|
+
const meter = ctx.meter;
|
|
108
119
|
for (const mech of mechanisms) {
|
|
109
|
-
const floor =
|
|
120
|
+
const floor = meter
|
|
121
|
+
? await meter.time(`${mech.name}.floor`, () => mech.floor(ctx, query, pre, worthRunning))
|
|
122
|
+
: await mech.floor(ctx, query, pre, worthRunning);
|
|
123
|
+
if (meter) {
|
|
124
|
+
if (floor === null)
|
|
125
|
+
meter.mechanismSkips++;
|
|
126
|
+
else
|
|
127
|
+
meter.mechanismFloors++;
|
|
128
|
+
}
|
|
110
129
|
if (floor === null) {
|
|
111
130
|
ctx.trace?.step("skipMechanism", [], [], `${mech.name} skipped — structural precondition failed`);
|
|
112
131
|
continue;
|
|
@@ -115,7 +134,11 @@ export async function think(ctx, query, mechs) {
|
|
|
115
134
|
ctx.trace?.step("skipMechanism", [], [], `${mech.name} skipped — floor ${floor} cannot beat incumbent (grade ${grade(best.weight)})`);
|
|
116
135
|
continue;
|
|
117
136
|
}
|
|
118
|
-
|
|
137
|
+
if (meter)
|
|
138
|
+
meter.mechanismRuns++;
|
|
139
|
+
const results = meter
|
|
140
|
+
? await meter.time(`${mech.name}.run`, () => mech.run(ctx, query, pre))
|
|
141
|
+
: await mech.run(ctx, query, pre);
|
|
119
142
|
for (const r of results) {
|
|
120
143
|
const weight = r.weight ?? weigh(r.accounted, r.moves);
|
|
121
144
|
consider({
|
|
@@ -125,6 +148,7 @@ export async function think(ctx, query, mechs) {
|
|
|
125
148
|
used: r.used,
|
|
126
149
|
accounted: r.accounted,
|
|
127
150
|
unexplained: r.unexplained,
|
|
151
|
+
complete: r.complete,
|
|
128
152
|
});
|
|
129
153
|
}
|
|
130
154
|
}
|
|
@@ -132,9 +156,12 @@ export async function think(ctx, query, mechs) {
|
|
|
132
156
|
// initial null, so the read-back needs the assertion.)
|
|
133
157
|
const decided = best;
|
|
134
158
|
if (candidates.length > 1) {
|
|
135
|
-
|
|
159
|
+
// The runner-up is computed BEFORE the decideGrounding step so its grade
|
|
160
|
+
// margin can ride along in the step's structured data payload; the
|
|
161
|
+
// computation itself is pure and was always unconditional — only its
|
|
162
|
+
// position moved.
|
|
163
|
+
let runnerUp = null;
|
|
136
164
|
if (decided !== null) {
|
|
137
|
-
let runnerUp = null;
|
|
138
165
|
for (const c of candidates) {
|
|
139
166
|
if (c === decided)
|
|
140
167
|
continue;
|
|
@@ -142,15 +169,28 @@ export async function think(ctx, query, mechs) {
|
|
|
142
169
|
runnerUp = c;
|
|
143
170
|
}
|
|
144
171
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
172
|
+
}
|
|
173
|
+
const margin = decided !== null && runnerUp !== null
|
|
174
|
+
? grade(runnerUp.weight) - grade(decided.weight)
|
|
175
|
+
: null;
|
|
176
|
+
ctx.trace?.step("decideGrounding", candidates.map((c) => rItem(c.bytes, `${c.provenance} (weight ${c.weight.toFixed(3)}${c.unexplained ? `, unexplained: "${c.unexplained}"` : ""})`)), decided ? [rItem(decided.bytes, decided.provenance)] : [], "the lightest grounding derivation wins — every mechanism weighed in the one cost ladder", undefined, {
|
|
177
|
+
version: 1,
|
|
178
|
+
candidates: candidates.map((c) => ({
|
|
179
|
+
provenance: c.provenance,
|
|
180
|
+
weight: c.weight,
|
|
181
|
+
grade: grade(c.weight),
|
|
182
|
+
unexplainedBytes: unaccounted(c.accounted),
|
|
183
|
+
decided: c === decided,
|
|
184
|
+
})),
|
|
185
|
+
...(margin !== null ? { runnerUpMargin: margin } : {}),
|
|
186
|
+
});
|
|
187
|
+
if (decided !== null && runnerUp !== null && margin !== null) {
|
|
188
|
+
if (margin <= 1) {
|
|
189
|
+
ctx.trace?.step("narrowDecision", [
|
|
190
|
+
rItem(decided.bytes, `${decided.provenance} (weight ${decided.weight.toFixed(3)})`),
|
|
191
|
+
], [
|
|
192
|
+
rItem(runnerUp.bytes, `${runnerUp.provenance} (weight ${runnerUp.weight.toFixed(3)})`),
|
|
193
|
+
], `margin ${margin} grade-unit(s) — the decision could change with one more training fact`, undefined, { version: 1, margin });
|
|
154
194
|
}
|
|
155
195
|
}
|
|
156
196
|
}
|
|
@@ -176,7 +216,13 @@ export async function think(ctx, query, mechs) {
|
|
|
176
216
|
: provenance === "recall" || provenance === "recall-echo"
|
|
177
217
|
? new Set()
|
|
178
218
|
: new Set(recognise(ctx, answer).sites.map((s) => s.payload));
|
|
179
|
-
|
|
219
|
+
// A grounding that DECLARED itself complete is not extended: the answer is
|
|
220
|
+
// already a trained form's own continuation, reached through an identity
|
|
221
|
+
// claim about the query, so a multi-hop pivot could only chain past the
|
|
222
|
+
// fact that produced it (see MechanismResult.complete).
|
|
223
|
+
const reasoned = decided.complete ? answer : meter
|
|
224
|
+
? await meter.time("reason", () => reason(ctx, query, answer, preConsumed, pre))
|
|
225
|
+
: await reason(ctx, query, answer, preConsumed, pre);
|
|
180
226
|
// Fuse only when the query has a genuine REMAINDER no mechanism's
|
|
181
227
|
// structural evidence touched at all. `decided.accounted` alone
|
|
182
228
|
// undercounts this: it is a COST-LADDER quantity (cover.ts prices its
|
|
@@ -209,9 +255,21 @@ export async function think(ctx, query, mechs) {
|
|
|
209
255
|
// rejected.
|
|
210
256
|
const unclimbed = decided.accounted.length > 0 &&
|
|
211
257
|
decided.accounted.every(([i, j]) => pre.computed.some((u) => u.i === i && u.j === j));
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
258
|
+
// Where the winning grounding stands in the query — fusion places primary
|
|
259
|
+
// by it (see fuseAttention's `primarySpans`). `accounted` is the
|
|
260
|
+
// cost-ladder read and is authoritative when non-empty; when it is empty
|
|
261
|
+
// the grounding is a pure COMPUTATION, whose evidence is its computed span.
|
|
262
|
+
// Exactly the cost-ladder-vs-coverage distinction `explained` above draws,
|
|
263
|
+
// read here for POSITION instead of for coverage — and resolved here, where
|
|
264
|
+
// both readings are in hand, rather than inside fuseAttention.
|
|
265
|
+
const primarySpans = decided.accounted.length > 0
|
|
266
|
+
? decided.accounted
|
|
267
|
+
: pre.computed.map((u) => [u.i, u.j]);
|
|
268
|
+
const fused = remainder < ctx.space.maxGroup
|
|
269
|
+
? reasoned
|
|
270
|
+
: meter
|
|
271
|
+
? await meter.time("fuse", () => fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans))
|
|
272
|
+
: await fuseAttention(ctx, query, reasoned, pre, unclimbed, decided.accounted);
|
|
215
273
|
done(fused, "grounded, reasoned forward, fused across points of attention");
|
|
216
274
|
return { bytes: fused, provenance };
|
|
217
275
|
}
|
|
@@ -15,21 +15,17 @@ export declare function latin1Key(bytes: Uint8Array): string;
|
|
|
15
15
|
* CALLER — who assembled the multi-turn context — knows where those
|
|
16
16
|
* boundaries are; the geometry never guesses them from the bytes. */
|
|
17
17
|
export declare function perceive(ctx: MindContext, input: Input, leafAt?: (i: number) => number | null, lookup?: (ids: number[]) => number | null, boundaries?: readonly number[]): Sema;
|
|
18
|
-
/** The DEPOSIT-shaped perceive.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* reuse across deposits ({@link stablePrefixFoldIncremental}) — O(turn)
|
|
30
|
-
* instead of O(context) per turn. The fold state is purely a cache; the
|
|
31
|
-
* boundary accumulation is what an evicted chain loses (falling back to
|
|
32
|
-
* the plain fold, the pre-boundary shape — a warm replay restores it). */
|
|
18
|
+
/** The DEPOSIT-shaped perceive. Folds over the stream's own content cuts —
|
|
19
|
+
* bit-identical to what inference computes for the same bytes, and that
|
|
20
|
+
* train/inference agreement is load-bearing for exact recall. An input that
|
|
21
|
+
* EXTENDS a previously deposited one is a conversation context grown by one
|
|
22
|
+
* turn; the cached prefix length IS the turn boundary (derived from the deposit
|
|
23
|
+
* sequence itself, never from a content convention) and joins the cut set, so
|
|
24
|
+
* the trained context node and the query's context subtree are the SAME node.
|
|
25
|
+
* Segment folds reuse across deposits ({@link stablePrefixFoldIncremental}) —
|
|
26
|
+
* O(turn) instead of O(context) per turn. All of it is purely a cache: an
|
|
27
|
+
* evicted chain loses only the turn boundaries, and since the content cuts do
|
|
28
|
+
* not depend on the cache, the segments themselves are unchanged. */
|
|
33
29
|
export declare function perceiveDeposit(ctx: MindContext, bytes: Uint8Array, conversational?: boolean): Sema;
|
|
34
30
|
/** The raw bytes of an input — modality-neutral conversion. */
|
|
35
31
|
export declare function inputBytes(ctx: MindContext, input: Input): Uint8Array;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Address — bytes → node (perceive, foldTree, resolve)
|
|
4
4
|
// Read — node → bytes (read)
|
|
5
|
-
import { bytesToTree,
|
|
5
|
+
import { bytesToTree, gridToTree, hilbertBytes, stablePrefixFoldIncremental, stackGrids, } from "../geometry.js";
|
|
6
6
|
import { canonHash } from "../canon.js";
|
|
7
7
|
import { bytesEqual } from "../bytes.js";
|
|
8
8
|
import { ALL } from "./types.js";
|
|
@@ -43,12 +43,23 @@ export function perceive(ctx, input, leafAt, lookup, boundaries) {
|
|
|
43
43
|
if (memo) {
|
|
44
44
|
const key = latin1Key(bytes);
|
|
45
45
|
const hit = memo.get(key);
|
|
46
|
-
if (hit !== undefined)
|
|
46
|
+
if (hit !== undefined) {
|
|
47
|
+
if (ctx.meter)
|
|
48
|
+
ctx.meter.perceiveHits++;
|
|
47
49
|
return hit;
|
|
50
|
+
}
|
|
51
|
+
if (ctx.meter) {
|
|
52
|
+
ctx.meter.perceptions++;
|
|
53
|
+
ctx.meter.perceivedBytes += bytes.length;
|
|
54
|
+
}
|
|
48
55
|
const tree = bytesToTree(ctx.space, ctx.alphabet, bytes, undefined, undefined, boundaries);
|
|
49
56
|
memo.set(key, tree);
|
|
50
57
|
return tree;
|
|
51
58
|
}
|
|
59
|
+
if (ctx.meter) {
|
|
60
|
+
ctx.meter.perceptions++;
|
|
61
|
+
ctx.meter.perceivedBytes += bytes.length;
|
|
62
|
+
}
|
|
52
63
|
return bytesToTree(ctx.space, ctx.alphabet, bytes, undefined, undefined, boundaries);
|
|
53
64
|
}
|
|
54
65
|
return bytesToTree(ctx.space, ctx.alphabet, bytes, leafAt, lookup);
|
|
@@ -58,21 +69,17 @@ export function perceive(ctx, input, leafAt, lookup, boundaries) {
|
|
|
58
69
|
}
|
|
59
70
|
return gridToTree(ctx.space, ctx.alphabet, input);
|
|
60
71
|
}
|
|
61
|
-
/** The DEPOSIT-shaped perceive.
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* reuse across deposits ({@link stablePrefixFoldIncremental}) — O(turn)
|
|
73
|
-
* instead of O(context) per turn. The fold state is purely a cache; the
|
|
74
|
-
* boundary accumulation is what an evicted chain loses (falling back to
|
|
75
|
-
* the plain fold, the pre-boundary shape — a warm replay restores it). */
|
|
72
|
+
/** The DEPOSIT-shaped perceive. Folds over the stream's own content cuts —
|
|
73
|
+
* bit-identical to what inference computes for the same bytes, and that
|
|
74
|
+
* train/inference agreement is load-bearing for exact recall. An input that
|
|
75
|
+
* EXTENDS a previously deposited one is a conversation context grown by one
|
|
76
|
+
* turn; the cached prefix length IS the turn boundary (derived from the deposit
|
|
77
|
+
* sequence itself, never from a content convention) and joins the cut set, so
|
|
78
|
+
* the trained context node and the query's context subtree are the SAME node.
|
|
79
|
+
* Segment folds reuse across deposits ({@link stablePrefixFoldIncremental}) —
|
|
80
|
+
* O(turn) instead of O(context) per turn. All of it is purely a cache: an
|
|
81
|
+
* evicted chain loses only the turn boundaries, and since the content cuts do
|
|
82
|
+
* not depend on the cache, the segments themselves are unchanged. */
|
|
76
83
|
export function perceiveDeposit(ctx, bytes, conversational = false) {
|
|
77
84
|
let prev;
|
|
78
85
|
let prefixLen = 0;
|
|
@@ -99,19 +106,27 @@ export function perceiveDeposit(ctx, bytes, conversational = false) {
|
|
|
99
106
|
}
|
|
100
107
|
}
|
|
101
108
|
}
|
|
102
|
-
|
|
103
|
-
|
|
109
|
+
// ONLY turn boundaries belong here. The stream's own content cuts are NOT
|
|
110
|
+
// passed in: `bytesToTree` and `stablePrefixFoldIncremental` both derive them
|
|
111
|
+
// per span, at every level, and handing the level-0 cuts in as stable-prefix
|
|
112
|
+
// boundaries instead produces a LEFT-NESTED join of flat segments — a
|
|
113
|
+
// different tree from the one inference builds for the same bytes. When that
|
|
114
|
+
// happened, a deposit's context root and `resolve(question)` were different
|
|
115
|
+
// nodes, so the trained edge hung off a node inference never reached and
|
|
116
|
+
// recall went silent (test/44 caught it as a site that could not be emitted
|
|
117
|
+
// because the resolved node led nowhere). Train and infer must fold
|
|
118
|
+
// identically; the way to guarantee that is to give this function nothing
|
|
119
|
+
// extra to say.
|
|
120
|
+
const cuts = new Set();
|
|
104
121
|
if (prev !== undefined) {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
entry = { boundaries, stable: folded.fold };
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
const plain = bytesToTreePyramid(ctx.space, ctx.alphabet, bytes);
|
|
112
|
-
tree = plain.tree;
|
|
113
|
-
entry = { boundaries: [], pyramid: plain.pyramid };
|
|
122
|
+
for (const b of prev.boundaries)
|
|
123
|
+
cuts.add(b);
|
|
124
|
+
cuts.add(prefixLen);
|
|
114
125
|
}
|
|
126
|
+
const boundaries = [...cuts].sort((a, b) => a - b);
|
|
127
|
+
const folded = stablePrefixFoldIncremental(ctx.space, ctx.alphabet, bytes, boundaries, prev?.stable);
|
|
128
|
+
const tree = folded.tree;
|
|
129
|
+
const entry = { boundaries, stable: folded.fold };
|
|
115
130
|
// Only a conversational deposit writes the cache too — otherwise a bare
|
|
116
131
|
// fact's plain fold could later be misread as a conversation's turn-zero
|
|
117
132
|
// boundary by an unrelated conversational deposit that happens to extend
|
|
@@ -193,6 +208,8 @@ export function foldTree(ctx, n, start, visit) {
|
|
|
193
208
|
export function resolve(ctx, bytes) {
|
|
194
209
|
if (bytes.length === 0)
|
|
195
210
|
return null;
|
|
211
|
+
if (ctx.meter)
|
|
212
|
+
ctx.meter.resolves++;
|
|
196
213
|
const exact = foldTree(ctx, perceive(ctx, bytes), 0).node;
|
|
197
214
|
if (exact !== null)
|
|
198
215
|
return exact;
|
|
@@ -235,6 +252,8 @@ export function canonResolve(ctx, bytes) {
|
|
|
235
252
|
if (direct !== null)
|
|
236
253
|
return set(direct);
|
|
237
254
|
}
|
|
255
|
+
if (ctx.meter)
|
|
256
|
+
ctx.meter.canonLookups++;
|
|
238
257
|
const candidates = store.canonFind(canonHash(key));
|
|
239
258
|
if (candidates.length === 0)
|
|
240
259
|
return set(null);
|
|
@@ -31,4 +31,10 @@ export declare function fuseAttention(ctx: MindContext, query: Uint8Array, prima
|
|
|
31
31
|
* primary's own source; that assumption is exactly backwards when
|
|
32
32
|
* primary is unclimbed. Absent or false preserves the original
|
|
33
33
|
* behaviour exactly. */
|
|
34
|
-
unclimbed?: boolean
|
|
34
|
+
unclimbed?: boolean,
|
|
35
|
+
/** The query spans `primary`'s own grounding stands on — used ONLY to place
|
|
36
|
+
* primary in the fused reading order (see below). Resolved by the caller,
|
|
37
|
+
* which is the layer that knows how a given grounding records its evidence;
|
|
38
|
+
* fuseAttention just reads a position from it. Empty or absent preserves
|
|
39
|
+
* the original behaviour exactly. */
|
|
40
|
+
primarySpans?: ReadonlyArray<readonly [number, number]>): Promise<Uint8Array>;
|