@hviana/sema 0.3.0 → 0.4.1
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/CONTRIBUTING.md +92 -10
- package/LICENSE.md +2 -2
- 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 +0 -4
- package/dist/src/mind/attention.js +251 -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/junction.d.ts +10 -0
- package/dist/src/mind/junction.js +14 -0
- 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 +55 -4
- package/dist/src/mind/mind.js +110 -91
- package/dist/src/mind/pipeline-mechanism.d.ts +33 -3
- package/dist/src/mind/pipeline-mechanism.js +179 -10
- package/dist/src/mind/pipeline.js +52 -10
- 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 +58 -5
- package/dist/src/mind/types.d.ts +28 -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 +268 -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/junction.ts +12 -0
- 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 +139 -98
- package/src/mind/pipeline-mechanism.ts +203 -13
- package/src/mind/pipeline.ts +65 -8
- 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 +59 -5
- package/src/mind/types.ts +28 -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/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
package/dist/src/mind/match.js
CHANGED
|
@@ -28,7 +28,7 @@ import { cosine } from "../vec.js";
|
|
|
28
28
|
import { conceptThreshold, identityBar, significanceBar } from "../geometry.js";
|
|
29
29
|
import { indexOf } from "../bytes.js";
|
|
30
30
|
import { leafIdRun } from "./canonical.js";
|
|
31
|
-
import { gistOf, read, resolve } from "./primitives.js";
|
|
31
|
+
import { foldTree, gistOf, perceive, read, resolve } from "./primitives.js";
|
|
32
32
|
import { argmaxCosine, chooseAmong, chooseNext, guidedFirst, hubBound, hubCap, } from "./traverse.js";
|
|
33
33
|
import { recognise, segment } from "./recognition.js";
|
|
34
34
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -84,6 +84,25 @@ export function locate(ctx, haystack, needle, fromPos, sites) {
|
|
|
84
84
|
* matcher CAST detects a woven query with. Returns non-overlapping runs
|
|
85
85
|
* sorted by query position. */
|
|
86
86
|
export function alignRuns(ctx, query, ct) {
|
|
87
|
+
if (ctx.meter) {
|
|
88
|
+
ctx.meter.alignments++;
|
|
89
|
+
// The alignment family's honest unit: the seed index is O(|query|) but
|
|
90
|
+
// the run extension is O(|query|·|ct|) in the worst case, and a weave
|
|
91
|
+
// that starts scanning conversation-length contexts shows up HERE long
|
|
92
|
+
// before it shows up in a call count.
|
|
93
|
+
ctx.meter.alignCells += query.length * ct.length;
|
|
94
|
+
}
|
|
95
|
+
// MEASURED AND REFUTED — seeding at the write side's unit floor W−1 instead
|
|
96
|
+
// of W. `canonicalWindows` interns both lengths precisely so a form
|
|
97
|
+
// straddling a group boundary is reachable from either cut, and the runs
|
|
98
|
+
// found here are exactly such remnants: test/29 C1's query shares only `ce `
|
|
99
|
+
// with `Ice is cold` — three bytes, never seeded at W, so that structure
|
|
100
|
+
// enters the weave carrying nothing but the scaffolding run every exemplar
|
|
101
|
+
// shares. But this is a byte MATCHER between two streams, not an index, and
|
|
102
|
+
// at W−1 the corpus is dense with spurious 3-byte agreements: the extra runs
|
|
103
|
+
// reshuffle which point claims which span, and test/29 A2 loses its analog.
|
|
104
|
+
// C1 does not pass either way. The store's unit floor does not transfer to
|
|
105
|
+
// the aligner's seed length.
|
|
87
106
|
const quantum = Math.min(ctx.space.maxGroup, ct.length);
|
|
88
107
|
if (quantum < 1 || query.length < quantum)
|
|
89
108
|
return [];
|
|
@@ -439,3 +458,108 @@ export async function project(ctx, id, guide) {
|
|
|
439
458
|
return fc;
|
|
440
459
|
return reverseContext(ctx, id, guide);
|
|
441
460
|
}
|
|
461
|
+
// ── The span-shape family ───────────────────────────────────────────────────
|
|
462
|
+
//
|
|
463
|
+
// "Is this answer drawn from this context?" has TWO formally distinct
|
|
464
|
+
// readings, and the pair plus the anchor classifier built on them are SHARED
|
|
465
|
+
// machinery — extraction proposes span-shaped exemplars with them, the
|
|
466
|
+
// shared `Precomputed.spanShapedOf` container computes them, and fusion
|
|
467
|
+
// (reasoning.ts) gates on the strict one. They lived inside
|
|
468
|
+
// mechanisms/extraction.ts, so `pipeline-mechanism.ts` and `reasoning.ts`
|
|
469
|
+
// both had to import back OUT of a specific mechanism — an inversion the
|
|
470
|
+
// mechanism market forbids (AGENTS §2.6: the shared contract may not depend
|
|
471
|
+
// on any one mechanism; §2.5: a shared matcher belongs to this family, never
|
|
472
|
+
// to a mechanism's private helpers). Deleting extraction must not break the
|
|
473
|
+
// shared container, so they live here.
|
|
474
|
+
//
|
|
475
|
+
// • isSpanShaped — the OPEN reading (sparse in-order embedding).
|
|
476
|
+
// • containsSpan — the STRICT reading (contiguous run or resolved node).
|
|
477
|
+
// • skillExemplar — classify one anchor into (context, answer) using them.
|
|
478
|
+
//
|
|
479
|
+
// The two readings are NOT interchangeable; AGENTS §2.5 pins the distinction
|
|
480
|
+
// and each function's own doc states what breaks if it is substituted.
|
|
481
|
+
/** Check whether an anchor is a span-shaped skill exemplar: it represents a
|
|
482
|
+
* fact whose context and answer together form a span-in-context pattern.
|
|
483
|
+
* If the anchor has a nextOf continuation, that is the answer and the anchor
|
|
484
|
+
* itself is the context. Otherwise the anchor's prevOf parents provide
|
|
485
|
+
* candidate contexts, and the longest one whose span is span-shaped wins. */
|
|
486
|
+
export async function skillExemplar(ctx, anchor, guide) {
|
|
487
|
+
if (ctx.store.hasNext(anchor)) {
|
|
488
|
+
const contextBytes = read(ctx, anchor);
|
|
489
|
+
const answerBytes = await follow(ctx, anchor, guide);
|
|
490
|
+
if (answerBytes !== null && isSpanShaped(ctx, contextBytes, answerBytes)) {
|
|
491
|
+
return { contextBytes, answerBytes };
|
|
492
|
+
}
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
const answerBytes = read(ctx, anchor);
|
|
496
|
+
// Candidate contexts, capped at the hub bound (a common answer's reverse
|
|
497
|
+
// fan-in is corpus-sized).
|
|
498
|
+
const capped = ctx.store.prevFirst(anchor, hubBound(ctx));
|
|
499
|
+
const spanShaped = [];
|
|
500
|
+
for (const p of capped) {
|
|
501
|
+
const ctxB = read(ctx, p);
|
|
502
|
+
if (ctxB.length > 0 && isSpanShaped(ctx, ctxB, answerBytes)) {
|
|
503
|
+
spanShaped.push({ id: p, bytes: ctxB });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (spanShaped.length === 0)
|
|
507
|
+
return null;
|
|
508
|
+
// Among span-shaped contexts, the longest wins (the smallest spanning frame
|
|
509
|
+
// heuristic's dual: more frame to locate in the query); the query gist,
|
|
510
|
+
// when given, breaks LENGTH TIES via chooseAmong — the same reverse-regime
|
|
511
|
+
// disambiguator every context pick uses, whose gist cache spares the
|
|
512
|
+
// re-fold this block once paid per tied candidate. Same strict first-seen
|
|
513
|
+
// tie-break as the hand loop it replaces.
|
|
514
|
+
const maxLen = Math.max(...spanShaped.map((s) => s.bytes.length));
|
|
515
|
+
const longest = spanShaped.filter((s) => s.bytes.length === maxLen);
|
|
516
|
+
let contextBytes = longest[0].bytes;
|
|
517
|
+
if (guide && longest.length > 1) {
|
|
518
|
+
const pick = chooseAmong(ctx, longest.map((s) => s.id), guide).id;
|
|
519
|
+
contextBytes = longest.find((s) => s.id === pick).bytes;
|
|
520
|
+
}
|
|
521
|
+
return { contextBytes, answerBytes };
|
|
522
|
+
}
|
|
523
|
+
/** Whether the answer is a SPARSE subsequence of the context (bytes in
|
|
524
|
+
* order, arbitrary gaps) — the OPEN span-shape reading (see the section
|
|
525
|
+
* note above). This is what lets extraction validate a MULTI-PIECE
|
|
526
|
+
* exemplar whose answer is stitched from several context runs — but it is
|
|
527
|
+
* deliberately permissive, so it must never be used as evidence that one
|
|
528
|
+
* span was "drawn from" another (see {@link containsSpan} for that).
|
|
529
|
+
*
|
|
530
|
+
* There is deliberately NO containsSpan pre-check here: strict containment
|
|
531
|
+
* IMPLIES the subsequence embedding (a contiguous run, or a resolved node —
|
|
532
|
+
* whose content-addressed identity means its bytes occur contiguously — is
|
|
533
|
+
* an in-order embedding with zero gaps), so the scan below decides alone,
|
|
534
|
+
* with the same truth value. The old pre-check re-perceived the context
|
|
535
|
+
* (a full river fold) per CANDIDATE in skillExemplar's √N-capped loop —
|
|
536
|
+
* pure cost, no discrimination. */
|
|
537
|
+
export function isSpanShaped(_ctx, context, answer) {
|
|
538
|
+
let ai = 0;
|
|
539
|
+
for (let ci = 0; ci < context.length && ai < answer.length; ci++) {
|
|
540
|
+
if (context[ci] === answer[ai])
|
|
541
|
+
ai++;
|
|
542
|
+
}
|
|
543
|
+
return ai === answer.length;
|
|
544
|
+
}
|
|
545
|
+
/** STRICT containment: the answer's resolved node appears in the context's
|
|
546
|
+
* folded tree, or the answer occurs as one CONTIGUOUS byte run of the
|
|
547
|
+
* context. This is real evidence the answer was drawn from the context.
|
|
548
|
+
* Fusion gates on this — the sparse-subsequence reading of
|
|
549
|
+
* {@link isSpanShaped} is trivially satisfied by short answers over long
|
|
550
|
+
* queries ("cold" is a gap-tolerant subsequence of most sentences holding
|
|
551
|
+
* c…o…l…d in order), and gating fusion on it silently starved multi-topic
|
|
552
|
+
* queries of their further points of attention. */
|
|
553
|
+
export function containsSpan(ctx, context, answer) {
|
|
554
|
+
const ansId = resolve(ctx, answer);
|
|
555
|
+
if (ansId !== null) {
|
|
556
|
+
let found = false;
|
|
557
|
+
foldTree(ctx, perceive(ctx, context), 0, (_n, _s, _e, node) => {
|
|
558
|
+
if (node === ansId)
|
|
559
|
+
found = true;
|
|
560
|
+
});
|
|
561
|
+
if (found)
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
return indexOf(context, answer, 0) >= 0;
|
|
565
|
+
}
|
|
@@ -273,9 +273,22 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
273
273
|
? s.ctx.subarray(0, r.cs + (r.qe - r.qs))
|
|
274
274
|
: query.subarray(r.qs, r.qe);
|
|
275
275
|
};
|
|
276
|
-
|
|
276
|
+
// The subject is the closest structure whose FILLER RUN precedes the seat.
|
|
277
|
+
// The gate is on `runs[0]` — the run `fillerOf` actually reads — not on the
|
|
278
|
+
// point's LAST run: requiring the subject's whole alignment to end before
|
|
279
|
+
// the seat disqualifies any structure the query mentions on BOTH sides of
|
|
280
|
+
// it, which is the shape CAST exists for. Measured on
|
|
281
|
+
// `steel is frigid so steel is ???` against `steel is hard so steel is
|
|
282
|
+
// strong` (runs "steel is " at 0-9 and "d so steel is " at 14-28) and
|
|
283
|
+
// `water is frigid so water is freezing` (seat "frigi" at 9-14): the
|
|
284
|
+
// subject's filler run sits squarely before the seat, but its second run —
|
|
285
|
+
// the recurrence AFTER it, the very thing that makes the sentence an
|
|
286
|
+
// analogy — pushed lastRun past the seat and no substitution fired at all.
|
|
287
|
+
// The ordering key follows the gate to the same run, so "closest preceding"
|
|
288
|
+
// still means closest by the evidence actually used.
|
|
289
|
+
const beforeOf = (p, r) => argmaxBy(points.filter((s) => s !== p && s.runs[0].qe <= r.qs &&
|
|
277
290
|
s.runs[0].cs < quantum &&
|
|
278
|
-
usable(s.runs[0].qs, s.runs[0].qe)), (s) =>
|
|
291
|
+
usable(s.runs[0].qs, s.runs[0].qe)), (s) => s.runs[0].qs, -Infinity, true)?.item;
|
|
279
292
|
const displacement = points
|
|
280
293
|
.map((p) => {
|
|
281
294
|
const r = p.runs[0];
|
|
@@ -287,6 +300,19 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
287
300
|
return null;
|
|
288
301
|
if (r.cs > fillerOf(before).length + quantum)
|
|
289
302
|
return null;
|
|
303
|
+
// SUBSTITUTION MUST ACTUALLY DISPLACE. The schema's premise is that the
|
|
304
|
+
// displaced structure's seat is held by something ELSE, which the
|
|
305
|
+
// subject then replaces. When the subject's filler already occurs in
|
|
306
|
+
// that structure, there is nothing to displace — the "transfer" restates
|
|
307
|
+
// the structure with its own occupant put back, and the answer is a
|
|
308
|
+
// tautology. Measured on `Michelangelo is to sculpture as who is to
|
|
309
|
+
// literature?`: the weave aligned the concept `Michelangelo` and the
|
|
310
|
+
// exemplar `The David was sculpted by Michelangelo.`, and substitution
|
|
311
|
+
// produced `Michelangelo sculpted by Michelangelo.` — then outbid every
|
|
312
|
+
// honest candidate with it (test/29 A2). Byte containment, the same
|
|
313
|
+
// primitive the self-evidence and contradiction guards use.
|
|
314
|
+
if (indexOf(p.ctx, fillerOf(before), 0) >= 0)
|
|
315
|
+
return null;
|
|
290
316
|
return { p, before, depth: p.ctx.length - r.cs };
|
|
291
317
|
})
|
|
292
318
|
.filter((c) => c !== null);
|
|
@@ -358,13 +384,27 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
358
384
|
// overlap trimming.
|
|
359
385
|
// The seat that establishes a candidate's role — see {@link seatOfNode}.
|
|
360
386
|
const seatOf = (p, allowForward = true) => seatOfNode(ctx, p.anchor, qv, p.ctx, allowForward);
|
|
387
|
+
// QUERY-SCALE — "this learnt context is the same size as the question", the
|
|
388
|
+
// bound comparison holds its dominant and its analogs to. A byte-exact
|
|
389
|
+
// `<= query.length` made that judgement turn on a difference the
|
|
390
|
+
// architecture cannot perceive: on `The Weeping Woman was painted by Pablo
|
|
391
|
+
// Picasso.` (47 bytes) the weave's dominant was `The Night Watch was painted
|
|
392
|
+
// by Rembrandt van Rijn.` (50) — three bytes over, so comparison refused
|
|
393
|
+
// outright, while the interchangeable `The Mona Lisa was painted by Leonardo
|
|
394
|
+
// da Vinci.` (47) would have passed. WHICH exemplar becomes dominant is
|
|
395
|
+
// settled by run-claiming order among equals, so a 3-byte difference was
|
|
396
|
+
// deciding whether the schema fires at all (test/33 1b). W is the smallest
|
|
397
|
+
// distinction perception can make — the same quantum countClusters separates
|
|
398
|
+
// neighbourhoods by — so a context within one quantum of the query's length
|
|
399
|
+
// carries no independently perceivable unit beyond it and is the same scale.
|
|
400
|
+
const queryScale = (n) => n - query.length < quantum;
|
|
361
401
|
const analogs = [];
|
|
362
402
|
for (const p of points) {
|
|
363
403
|
if (p === dominant)
|
|
364
404
|
continue;
|
|
365
405
|
// Push the point's own anchor only when its context fits within
|
|
366
406
|
// the query (the seat sentence must not dominate the comparison).
|
|
367
|
-
if (p.ctx.length
|
|
407
|
+
if (queryScale(p.ctx.length) &&
|
|
368
408
|
indexOf(dominant.ctx, p.ctx, 0) < 0 &&
|
|
369
409
|
indexOf(p.ctx, dominant.ctx, 0) < 0 &&
|
|
370
410
|
indexOf(query, p.ctx, 0) < 0) {
|
|
@@ -381,7 +421,7 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
381
421
|
// chooseNext caps by) are reachable as analogs.
|
|
382
422
|
for (const nid of ctx.store.nextFirst(p.anchor, hubBound(ctx))) {
|
|
383
423
|
const nctx = read(ctx, nid);
|
|
384
|
-
if (nctx.length
|
|
424
|
+
if (!queryScale(nctx.length) ||
|
|
385
425
|
indexOf(dominant.ctx, nctx, 0) >= 0 ||
|
|
386
426
|
indexOf(nctx, dominant.ctx, 0) >= 0 ||
|
|
387
427
|
indexOf(query, nctx, 0) >= 0)
|
|
@@ -389,6 +429,23 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
389
429
|
analogs.push({ anchor: nid, point: null, src: p });
|
|
390
430
|
}
|
|
391
431
|
}
|
|
432
|
+
// MEASURED AND REFUTED — proposing analogs from the dominant's halo when the
|
|
433
|
+
// query-local generator finds none. Both loops above are query-local (an
|
|
434
|
+
// aligned point, or one forward hop off one), while the gate that judges
|
|
435
|
+
// candidates — analogyStrength's halo tier — is cross-domain by construction
|
|
436
|
+
// and is licence enough on its own (`bestHalo` exempts it from the naming and
|
|
437
|
+
// trusted-root bars). So the gate reads as strictly more capable than the
|
|
438
|
+
// generator, and closing that asymmetry looks like the fix for test/29 A2
|
|
439
|
+
// (`Michelangelo is to sculpture as who is to literature?`, whose only stored
|
|
440
|
+
// content is `Michelangelo`: the weave aligns the concept and its own
|
|
441
|
+
// exemplar, each contains the other, so every candidate is excluded and
|
|
442
|
+
// comparison checks zero).
|
|
443
|
+
//
|
|
444
|
+
// It proposes nothing. Measured on A2's own 13-pair corpus: `Michelangelo`
|
|
445
|
+
// HAS a halo, and `haloSiblings` returns not one sibling above
|
|
446
|
+
// significanceBar — the distributional company that would make Shakespeare a
|
|
447
|
+
// cross-domain analog was never trained. The asymmetry is real but it is not
|
|
448
|
+
// what stops A2; the corpus is.
|
|
392
449
|
let bestAnalog = null;
|
|
393
450
|
let bestSim = 0;
|
|
394
451
|
let bestHalo = false;
|
|
@@ -614,7 +671,7 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
614
671
|
if (bestAnalog !== null &&
|
|
615
672
|
(bestHalo || analogNamed || rootTrusted) &&
|
|
616
673
|
!cmpDismisses &&
|
|
617
|
-
dominant.ctx.length
|
|
674
|
+
queryScale(dominant.ctx.length) &&
|
|
618
675
|
roots.length <= 1 &&
|
|
619
676
|
!dominates(cmpMaxGap, query.length) &&
|
|
620
677
|
cmpMaxGap < dominant.ctx.length) {
|
|
@@ -650,7 +707,7 @@ export async function counterfactualTransfer(ctx, query, pre) {
|
|
|
650
707
|
cmpAccounted);
|
|
651
708
|
}
|
|
652
709
|
else if (bestAnalog !== null &&
|
|
653
|
-
dominant.ctx.length
|
|
710
|
+
queryScale(dominant.ctx.length) &&
|
|
654
711
|
roots.length <= 1) {
|
|
655
712
|
ctx.trace?.step("validateAnalogy", [
|
|
656
713
|
rNode(ctx, dominant.anchor, "analog", bestSim),
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Vec } from "../../vec.js";
|
|
2
1
|
import type { MindContext } from "../types.js";
|
|
3
2
|
import type { PipelineMechanism, Precomputed } from "../pipeline-mechanism.js";
|
|
4
3
|
/** Find the first span-shaped skill exemplar among the ranked anchors from
|
|
@@ -31,37 +30,4 @@ export declare function answerRunsInContext(_ctx: MindContext, context: Uint8Arr
|
|
|
31
30
|
end: number;
|
|
32
31
|
ansLen: number;
|
|
33
32
|
}> | null;
|
|
34
|
-
/** Check whether an anchor is a span-shaped skill exemplar: it represents a
|
|
35
|
-
* fact whose context and answer together form a span-in-context pattern.
|
|
36
|
-
* If the anchor has a nextOf continuation, that is the answer and the anchor
|
|
37
|
-
* itself is the context. Otherwise the anchor's prevOf parents provide
|
|
38
|
-
* candidate contexts, and the longest one whose span is span-shaped wins. */
|
|
39
|
-
export declare function skillExemplar(ctx: MindContext, anchor: number, guide?: Vec | null): Promise<{
|
|
40
|
-
contextBytes: Uint8Array;
|
|
41
|
-
answerBytes: Uint8Array;
|
|
42
|
-
} | null>;
|
|
43
|
-
/** Whether the answer is a SPARSE subsequence of the context (bytes in
|
|
44
|
-
* order, arbitrary gaps) — the OPEN span-shape reading (see the section
|
|
45
|
-
* note above). This is what lets extraction validate a MULTI-PIECE
|
|
46
|
-
* exemplar whose answer is stitched from several context runs — but it is
|
|
47
|
-
* deliberately permissive, so it must never be used as evidence that one
|
|
48
|
-
* span was "drawn from" another (see {@link containsSpan} for that).
|
|
49
|
-
*
|
|
50
|
-
* There is deliberately NO containsSpan pre-check here: strict containment
|
|
51
|
-
* IMPLIES the subsequence embedding (a contiguous run, or a resolved node —
|
|
52
|
-
* whose content-addressed identity means its bytes occur contiguously — is
|
|
53
|
-
* an in-order embedding with zero gaps), so the scan below decides alone,
|
|
54
|
-
* with the same truth value. The old pre-check re-perceived the context
|
|
55
|
-
* (a full river fold) per CANDIDATE in skillExemplar's √N-capped loop —
|
|
56
|
-
* pure cost, no discrimination. */
|
|
57
|
-
export declare function isSpanShaped(_ctx: MindContext, context: Uint8Array, answer: Uint8Array): boolean;
|
|
58
|
-
/** STRICT containment: the answer's resolved node appears in the context's
|
|
59
|
-
* folded tree, or the answer occurs as one CONTIGUOUS byte run of the
|
|
60
|
-
* context. This is real evidence the answer was drawn from the context.
|
|
61
|
-
* Fusion gates on this — the sparse-subsequence reading of
|
|
62
|
-
* {@link isSpanShaped} is trivially satisfied by short answers over long
|
|
63
|
-
* queries ("cold" is a gap-tolerant subsequence of most sentences holding
|
|
64
|
-
* c…o…l…d in order), and gating fusion on it silently starved multi-topic
|
|
65
|
-
* queries of their further points of attention. */
|
|
66
|
-
export declare function containsSpan(ctx: MindContext, context: Uint8Array, answer: Uint8Array): boolean;
|
|
67
33
|
export declare const extractionMechanism: PipelineMechanism;
|
|
@@ -6,9 +6,7 @@
|
|
|
6
6
|
// form a span-in-context pattern: the answer is a subsequence of the context
|
|
7
7
|
// (or one of its pieces is), and the context is the smallest spanning frame
|
|
8
8
|
// that contains it.
|
|
9
|
-
import {
|
|
10
|
-
import { follow, locate } from "../match.js";
|
|
11
|
-
import { chooseAmong, hubBound } from "../traverse.js";
|
|
9
|
+
import { locate } from "../match.js";
|
|
12
10
|
import { concatBytes, indexOf } from "../../bytes.js";
|
|
13
11
|
import { decodeText, unexplainedLabel } from "../rationale.js";
|
|
14
12
|
import { CONCEPT, STEP } from "../graph-search.js";
|
|
@@ -273,91 +271,6 @@ export function answerRunsInContext(_ctx, context, answer) {
|
|
|
273
271
|
}
|
|
274
272
|
return merged.length > 0 ? merged : null;
|
|
275
273
|
}
|
|
276
|
-
/** Check whether an anchor is a span-shaped skill exemplar: it represents a
|
|
277
|
-
* fact whose context and answer together form a span-in-context pattern.
|
|
278
|
-
* If the anchor has a nextOf continuation, that is the answer and the anchor
|
|
279
|
-
* itself is the context. Otherwise the anchor's prevOf parents provide
|
|
280
|
-
* candidate contexts, and the longest one whose span is span-shaped wins. */
|
|
281
|
-
export async function skillExemplar(ctx, anchor, guide) {
|
|
282
|
-
if (ctx.store.hasNext(anchor)) {
|
|
283
|
-
const contextBytes = read(ctx, anchor);
|
|
284
|
-
const answerBytes = await follow(ctx, anchor, guide);
|
|
285
|
-
if (answerBytes !== null && isSpanShaped(ctx, contextBytes, answerBytes)) {
|
|
286
|
-
return { contextBytes, answerBytes };
|
|
287
|
-
}
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
const answerBytes = read(ctx, anchor);
|
|
291
|
-
// Candidate contexts, capped at the hub bound (a common answer's reverse
|
|
292
|
-
// fan-in is corpus-sized).
|
|
293
|
-
const capped = ctx.store.prevFirst(anchor, hubBound(ctx));
|
|
294
|
-
const spanShaped = [];
|
|
295
|
-
for (const p of capped) {
|
|
296
|
-
const ctxB = read(ctx, p);
|
|
297
|
-
if (ctxB.length > 0 && isSpanShaped(ctx, ctxB, answerBytes)) {
|
|
298
|
-
spanShaped.push({ id: p, bytes: ctxB });
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
if (spanShaped.length === 0)
|
|
302
|
-
return null;
|
|
303
|
-
// Among span-shaped contexts, the longest wins (the smallest spanning frame
|
|
304
|
-
// heuristic's dual: more frame to locate in the query); the query gist,
|
|
305
|
-
// when given, breaks LENGTH TIES via chooseAmong — the same reverse-regime
|
|
306
|
-
// disambiguator every context pick uses, whose gist cache spares the
|
|
307
|
-
// re-fold this block once paid per tied candidate. Same strict first-seen
|
|
308
|
-
// tie-break as the hand loop it replaces.
|
|
309
|
-
const maxLen = Math.max(...spanShaped.map((s) => s.bytes.length));
|
|
310
|
-
const longest = spanShaped.filter((s) => s.bytes.length === maxLen);
|
|
311
|
-
let contextBytes = longest[0].bytes;
|
|
312
|
-
if (guide && longest.length > 1) {
|
|
313
|
-
const pick = chooseAmong(ctx, longest.map((s) => s.id), guide).id;
|
|
314
|
-
contextBytes = longest.find((s) => s.id === pick).bytes;
|
|
315
|
-
}
|
|
316
|
-
return { contextBytes, answerBytes };
|
|
317
|
-
}
|
|
318
|
-
/** Whether the answer is a SPARSE subsequence of the context (bytes in
|
|
319
|
-
* order, arbitrary gaps) — the OPEN span-shape reading (see the section
|
|
320
|
-
* note above). This is what lets extraction validate a MULTI-PIECE
|
|
321
|
-
* exemplar whose answer is stitched from several context runs — but it is
|
|
322
|
-
* deliberately permissive, so it must never be used as evidence that one
|
|
323
|
-
* span was "drawn from" another (see {@link containsSpan} for that).
|
|
324
|
-
*
|
|
325
|
-
* There is deliberately NO containsSpan pre-check here: strict containment
|
|
326
|
-
* IMPLIES the subsequence embedding (a contiguous run, or a resolved node —
|
|
327
|
-
* whose content-addressed identity means its bytes occur contiguously — is
|
|
328
|
-
* an in-order embedding with zero gaps), so the scan below decides alone,
|
|
329
|
-
* with the same truth value. The old pre-check re-perceived the context
|
|
330
|
-
* (a full river fold) per CANDIDATE in skillExemplar's √N-capped loop —
|
|
331
|
-
* pure cost, no discrimination. */
|
|
332
|
-
export function isSpanShaped(_ctx, context, answer) {
|
|
333
|
-
let ai = 0;
|
|
334
|
-
for (let ci = 0; ci < context.length && ai < answer.length; ci++) {
|
|
335
|
-
if (context[ci] === answer[ai])
|
|
336
|
-
ai++;
|
|
337
|
-
}
|
|
338
|
-
return ai === answer.length;
|
|
339
|
-
}
|
|
340
|
-
/** STRICT containment: the answer's resolved node appears in the context's
|
|
341
|
-
* folded tree, or the answer occurs as one CONTIGUOUS byte run of the
|
|
342
|
-
* context. This is real evidence the answer was drawn from the context.
|
|
343
|
-
* Fusion gates on this — the sparse-subsequence reading of
|
|
344
|
-
* {@link isSpanShaped} is trivially satisfied by short answers over long
|
|
345
|
-
* queries ("cold" is a gap-tolerant subsequence of most sentences holding
|
|
346
|
-
* c…o…l…d in order), and gating fusion on it silently starved multi-topic
|
|
347
|
-
* queries of their further points of attention. */
|
|
348
|
-
export function containsSpan(ctx, context, answer) {
|
|
349
|
-
const ansId = resolve(ctx, answer);
|
|
350
|
-
if (ansId !== null) {
|
|
351
|
-
let found = false;
|
|
352
|
-
foldTree(ctx, perceive(ctx, context), 0, (_n, _s, _e, node) => {
|
|
353
|
-
if (node === ansId)
|
|
354
|
-
found = true;
|
|
355
|
-
});
|
|
356
|
-
if (found)
|
|
357
|
-
return true;
|
|
358
|
-
}
|
|
359
|
-
return indexOf(context, answer, 0) >= 0;
|
|
360
|
-
}
|
|
361
274
|
// ── Pipeline mechanism ──────────────────────────────────────────────────────
|
|
362
275
|
export const extractionMechanism = {
|
|
363
276
|
name: "extraction",
|
|
@@ -7,6 +7,9 @@ export interface RecallResult {
|
|
|
7
7
|
accounted: Array<[number, number]>;
|
|
8
8
|
moves: number;
|
|
9
9
|
unexplained: string;
|
|
10
|
+
/** See {@link import("../pipeline-mechanism.js").MechanismResult.complete}
|
|
11
|
+
* — set by the IDENTITY-bridge tier alone. */
|
|
12
|
+
complete?: boolean;
|
|
10
13
|
}
|
|
11
14
|
/** Recall the answer by resonating the whole query against the content index. */
|
|
12
15
|
export declare function recallByResonance(ctx: MindContext, query: Uint8Array, pre: Precomputed): Promise<RecallResult | null>;
|
|
@@ -20,7 +20,7 @@ export async function recallByResonance(ctx, query, pre) {
|
|
|
20
20
|
]);
|
|
21
21
|
const whole_ = [[0, query.length]];
|
|
22
22
|
const nothing = [];
|
|
23
|
-
const ground = (bytes, note, accounted, moves, echoed = false) => {
|
|
23
|
+
const ground = (bytes, note, accounted, moves, echoed = false, complete = false) => {
|
|
24
24
|
t?.done(bytes === null
|
|
25
25
|
? []
|
|
26
26
|
: [rItem(bytes, "answer", resolve(ctx, bytes) ?? undefined)], note);
|
|
@@ -30,6 +30,7 @@ export async function recallByResonance(ctx, query, pre) {
|
|
|
30
30
|
accounted,
|
|
31
31
|
moves,
|
|
32
32
|
unexplained: unexplainedLabel(query, accounted),
|
|
33
|
+
...(complete ? { complete } : {}),
|
|
33
34
|
};
|
|
34
35
|
};
|
|
35
36
|
const k = pre.k;
|
|
@@ -245,10 +246,32 @@ export async function recallByResonance(ctx, query, pre) {
|
|
|
245
246
|
// natural, tuning-free ceiling (probe every cluster) for a call that
|
|
246
247
|
// is ALREADY refusal-path-only and must not miss a candidate hiding
|
|
247
248
|
// behind an unlucky structural distance.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
249
|
+
//
|
|
250
|
+
// MEASURED COST, AND WHY IT STAYS (17.9M vectors / 325K contexts):
|
|
251
|
+
// this one call is ~570 ms and ~45% of all inference time on a refusing
|
|
252
|
+
// query. The cost is entirely `exhaustive` (nprobe = every cluster),
|
|
253
|
+
// NOT the widened k — timed on that store: k=571 exhaustive 632 ms,
|
|
254
|
+
// k=24 exhaustive 536 ms, k=571 NON-exhaustive 12 ms. So narrowing k
|
|
255
|
+
// buys nothing and the 50x is the whole-index scan itself.
|
|
256
|
+
// It is load-bearing: over an 18-query battery the bridge produced a
|
|
257
|
+
// winner 4 times, and ALL FOUR winners came from this proposal channel
|
|
258
|
+
// — the anchor-climb channel won nothing on its own. Reordering the
|
|
259
|
+
// channels (climb first, resonate only on failure) would therefore pay
|
|
260
|
+
// the climb, fail, and pay this anyway. Do not weaken it without
|
|
261
|
+
// re-running that measurement.
|
|
262
|
+
// Handed to the bridge as a THUNK: this exhaustive whole-index probe is
|
|
263
|
+
// the most expensive single act on the refusal path, and the bridge's
|
|
264
|
+
// own cheap gates (query length, the O(|query|) stored-window anchor
|
|
265
|
+
// scan) can refuse without any proposal at all. See substitutionBridge.
|
|
266
|
+
const wideIds = async () => {
|
|
267
|
+
const wide = k >= hubBound(ctx)
|
|
268
|
+
? whole
|
|
269
|
+
: ctx.meter
|
|
270
|
+
? await ctx.meter.time("recall.exhaustiveResonate", () => ctx.store.resonate(queryGist, hubBound(ctx), true))
|
|
271
|
+
: await ctx.store.resonate(queryGist, hubBound(ctx), true);
|
|
272
|
+
return wide.map((h) => h.id);
|
|
273
|
+
};
|
|
274
|
+
const bridged = await substitutionBridge(ctx, query, wideIds);
|
|
252
275
|
if (bridged !== null) {
|
|
253
276
|
const g = await project(ctx, bridged.id, queryGist);
|
|
254
277
|
// A projection contained in a substituted candidate-side span is the
|
|
@@ -263,15 +286,54 @@ export async function recallByResonance(ctx, query, pre) {
|
|
|
263
286
|
bridged.subs.some((s) => indexOf(cBytes.subarray(s.cs, s.ce), g, 0) >= 0);
|
|
264
287
|
if (g !== null && g.length > 0 && !restates(g) && !manufactured &&
|
|
265
288
|
!(g.length < query.length && indexOf(query, g, 0) >= 0)) {
|
|
266
|
-
return ground(g,
|
|
267
|
-
`up to
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
|
|
289
|
+
return ground(g, bridged.subs.length === 0
|
|
290
|
+
? `identity bridge — a trained context IS this query, up to ` +
|
|
291
|
+
`scaffolding the corpus itself treats as filler`
|
|
292
|
+
: `substitution bridge — a trained context accounts for the ` +
|
|
293
|
+
`query up to ${bridged.subs.length} corroborated ` +
|
|
294
|
+
`substitution(s)`,
|
|
295
|
+
// WHAT THIS GROUNDING EXPLAINS — the spans its alignment covers,
|
|
296
|
+
// matched AND substituted, for BOTH tiers.
|
|
297
|
+
//
|
|
298
|
+
// A corroborated substitution is not a gap in the explanation; it is
|
|
299
|
+
// an explanation the mechanism PAID for, one CONCEPT each in `moves`
|
|
300
|
+
// just below. Leaving its span unaccounted charges the same act
|
|
301
|
+
// twice — once as a move, once as PASS-per-unexplained-byte — and the
|
|
302
|
+
// second charge is far the larger, so a bridge that matched 28 of 29
|
|
303
|
+
// bytes declared the whole query unexplained and lost to any
|
|
304
|
+
// mechanism with a smaller honest claim. Measured on test/49's
|
|
305
|
+
// paraphrase: it FOUND the trained fact through two corroborated case
|
|
306
|
+
// substitutions and was outbid 29011 to 1012 by a CAST comparison
|
|
307
|
+
// that voiced the wrong country.
|
|
308
|
+
//
|
|
309
|
+
// This tier DID once report `[]` for the substituted case, against
|
|
310
|
+
// the observation that "pricing the aligned spans outweighed
|
|
311
|
+
// extraction's correct answer in the grounding decider". That is no
|
|
312
|
+
// longer so and the suite is the witness: with the fold's regions
|
|
313
|
+
// content-defined and the junction tiers no longer consuming each
|
|
314
|
+
// other's candidates, full accounting here passes every test that
|
|
315
|
+
// refutation was recorded for. Reporting only the LITERALLY matched
|
|
316
|
+
// spans (accounted minus the substituted ones) was also implemented
|
|
317
|
+
// and is a strictly worse reading of the same ladder — it still
|
|
318
|
+
// double-charges, just less.
|
|
319
|
+
//
|
|
320
|
+
// An IDENTITY bridge (zero substitutions) substituted nothing, so
|
|
321
|
+
// there is nothing to be humble about: every accounted byte is a
|
|
322
|
+
// LITERAL match against a trained form, and the query is that form
|
|
323
|
+
// up to scaffolding. Reporting `[]` for it was actively wrong in
|
|
324
|
+
// two ways — it priced a full explanation at PASS-per-byte so junk
|
|
325
|
+
// outweighed it, and, because the honest-remainder test in think()
|
|
326
|
+
// reads the same spans, it left the whole query "unaccounted" and
|
|
327
|
+
// forced the multi-topic fusion gate open. Observed live: the
|
|
328
|
+
// correct "What is the process of photosynthesis?" grounding was
|
|
329
|
+
// fused away into an unrelated "Hello! How can I assist you
|
|
330
|
+
// today?" point of attention.
|
|
331
|
+
[...bridged.accounted], CONCEPT * bridged.subs.length + STEP, false,
|
|
332
|
+
// COMPLETE only for the identity tier: the query IS this trained
|
|
333
|
+
// context, so `g` is that context's own continuation — the whole
|
|
334
|
+
// read-out. A SUBSTITUTED bridge makes no such claim (it stood a
|
|
335
|
+
// different word in the query's place), so it stays extendable.
|
|
336
|
+
bridged.subs.length === 0);
|
|
275
337
|
}
|
|
276
338
|
}
|
|
277
339
|
}
|
|
@@ -320,6 +382,7 @@ export const recallMechanism = {
|
|
|
320
382
|
moves: r.moves,
|
|
321
383
|
unexplained: r.unexplained,
|
|
322
384
|
provenance: r.echoed ? "recall-echo" : "recall",
|
|
385
|
+
...(r.complete ? { complete: true } : {}),
|
|
323
386
|
}];
|
|
324
387
|
},
|
|
325
388
|
};
|
package/dist/src/mind/mind.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export interface Conversation {
|
|
|
44
44
|
import type { AttentionRead, MindContext, Recognition } from "./types.js";
|
|
45
45
|
export type { AnchorRejectionReason, ClimbConsensusData, ConsensusAnchorTrace, ConsensusReachTrace, ConsensusRegionTrace, CrossRegionTier, JunctionVoteTrace, RegionOutcome, } from "./attention.js";
|
|
46
46
|
export type { AncestorReach, SaturationReason, SaturationStop, } from "./types.js";
|
|
47
|
+
import { type CostReport, Meter } from "../meter.js";
|
|
47
48
|
export interface MindOptions {
|
|
48
49
|
seed?: number;
|
|
49
50
|
recallQueryK?: number;
|
|
@@ -58,6 +59,15 @@ export interface MindOptions {
|
|
|
58
59
|
mechanisms?: import("./pipeline-mechanism.js").PipelineMechanism[];
|
|
59
60
|
/** Factories that receive the {@link ExtensionHost} and return mechanisms. */
|
|
60
61
|
mechanismFactories?: ((host: import("../extension.js").ExtensionHost) => import("./pipeline-mechanism.js").PipelineMechanism)[];
|
|
62
|
+
/** Measure the computational usage of every inference call — see
|
|
63
|
+
* src/meter.ts. Off by default and free when off (one null check per
|
|
64
|
+
* store read); on, each `respond`/`respondTurn` leaves a {@link
|
|
65
|
+
* Mind.lastCost} report behind. Counters are deterministic, so two runs
|
|
66
|
+
* of the same query on the same store are diffable; the millisecond
|
|
67
|
+
* fields are not. Profiling NEVER changes an answer — but note that
|
|
68
|
+
* attaching a RATIONALE does (traced responses bypass the ctx memos,
|
|
69
|
+
* AGENTS §2.11), so profile without a trace. */
|
|
70
|
+
profile?: boolean;
|
|
61
71
|
/** Content canonicalizer applied to EVERY response (any modality) for
|
|
62
72
|
* equivalence-class resolution — see src/canon.ts. Text entry points
|
|
63
73
|
* ({@link Mind.respondText}, {@link Mind.respondTurnText}) inject the
|
|
@@ -86,6 +96,19 @@ export declare class Mind implements MindContext {
|
|
|
86
96
|
* `false` to disable canonical resolution, or null to let each entry
|
|
87
97
|
* point decide (text entry points inject {@link textCanon}). */
|
|
88
98
|
private _canonOpt;
|
|
99
|
+
/** The work accumulator for the inference call in flight — see
|
|
100
|
+
* {@link MindContext.meter}. Non-null only between beginResponse and
|
|
101
|
+
* endResponse, and only when the Mind was constructed with
|
|
102
|
+
* `{ profile: true }`. */
|
|
103
|
+
meter: Meter | null;
|
|
104
|
+
/** Whether {@link MindOptions.profile} was set. */
|
|
105
|
+
private _profile;
|
|
106
|
+
/** The computational-usage report of the LAST completed inference call, or
|
|
107
|
+
* null when profiling is off (or nothing has been asked yet). Overwritten
|
|
108
|
+
* by every `respond`/`respondTurn`; copy it if you are aggregating. See
|
|
109
|
+
* {@link import("../meter.js").CostReport} and `sumReports`/`formatReport`
|
|
110
|
+
* for battery-level aggregation. */
|
|
111
|
+
lastCost: CostReport | null;
|
|
89
112
|
/** Memo of the consensus climb — content-keyed. See {@link MindContext.climbMemo}. */
|
|
90
113
|
climbMemo: Map<string, Map<string, AttentionRead>> | null;
|
|
91
114
|
/** Memo of recognise() — content-keyed. See {@link MindContext.recogniseMemo}. */
|
|
@@ -146,21 +169,49 @@ export declare class Mind implements MindContext {
|
|
|
146
169
|
/** Perceive input into a content-defined tree. Deterministic — identical
|
|
147
170
|
* bytes always produce an identical tree. Public for ingest-cache. */
|
|
148
171
|
perceive(input: Input, leafAt?: (i: number) => number | null, lookup?: (ids: number[]) => number | null): Sema;
|
|
149
|
-
/** Open one response's transient state — the tracer
|
|
150
|
-
* memos
|
|
151
|
-
*
|
|
172
|
+
/** Open one response's transient state — the tracer, the per-response
|
|
173
|
+
* memos, the work meter. The ONE place this state is created, and it
|
|
174
|
+
* serves BOTH entry points: `respond` takes fresh per-response memos,
|
|
175
|
+
* `respondTurn` passes its conversation, whose memos persist across turns
|
|
176
|
+
* (content-keyed, so the previous turn's results are found by this turn's
|
|
177
|
+
* sub-span calls) and whose `resolvedSubtrees` makes foldTree O(suffix)
|
|
178
|
+
* instead of O(context). respondTurn used to inline its own copy of this
|
|
179
|
+
* and of {@link endResponse}; the two drifted (a memo added to one was
|
|
180
|
+
* silently absent from the other), so there is exactly one pair now. */
|
|
152
181
|
private beginResponse;
|
|
182
|
+
/** Open (or leave closed) the response's work accumulator. Separate from
|
|
183
|
+
* {@link beginResponse} because {@link respondTurn} keeps its own
|
|
184
|
+
* conversation-scoped lifecycle and must not create fresh per-response
|
|
185
|
+
* memos — but it DOES meter, through this same pair. */
|
|
186
|
+
private _beginMeter;
|
|
187
|
+
/** Close the accumulator and publish its report. Detaching from the store
|
|
188
|
+
* matters: a Mind that shares a store with another Mind must not keep
|
|
189
|
+
* charging that store's reads to a finished response. */
|
|
190
|
+
private _endMeter;
|
|
153
191
|
/** The canonicalizer a response should carry: the Mind-level option when
|
|
154
192
|
* set (or none when explicitly disabled), else the entry point's own
|
|
155
193
|
* default — text entry points pass {@link textCanon}, binary ones null. */
|
|
156
194
|
private _canonFor;
|
|
157
195
|
/** Close one response's transient state — every per-response field, incl.
|
|
158
|
-
* the edge guide/choices `think` sets mid-flight.
|
|
196
|
+
* the edge guide/choices `think` sets mid-flight, and the meter's report.
|
|
197
|
+
*
|
|
198
|
+
* A conversation's memo MAPS were mutated in place, so `data.*` still
|
|
199
|
+
* points at them and there is nothing to save back. Clearing the Mind's
|
|
200
|
+
* references is what matters: a concurrently-started `respond()` swaps its
|
|
201
|
+
* own fresh maps into these pointers, and copying back from them here
|
|
202
|
+
* would inject a foreign response's memos into the conversation. */
|
|
159
203
|
private endResponse;
|
|
160
204
|
/** Shared response core — the one path from bytes to voiced answer.
|
|
161
205
|
* `respond` calls this directly; `respondTurn` has its own path
|
|
162
206
|
* with conversation-persistent memos and incremental perception. */
|
|
163
207
|
private _respondImpl;
|
|
208
|
+
/** The ONE path from query bytes to a voiced answer: ground (think), then
|
|
209
|
+
* re-voice in the asker's words (articulate). Both entry points run
|
|
210
|
+
* exactly this — they differ only in the LIFECYCLE around it (fresh
|
|
211
|
+
* per-response memos vs. a conversation's persistent ones) and in what
|
|
212
|
+
* they do with the answer afterwards. It must be called between
|
|
213
|
+
* {@link beginResponse} and {@link endResponse}. */
|
|
214
|
+
private _groundAndVoice;
|
|
164
215
|
respond(input: Input, inspectRationale?: InspectRationale): Promise<Response>;
|
|
165
216
|
/** Text view of {@link respond}. NUL bytes (0x00) are stripped before
|
|
166
217
|
* decoding — they are structural padding in text answers. LOSSY for a
|