@hviana/sema 0.8.1 → 0.8.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 +29 -29
- package/TRADEMARKS.md +0 -1
- package/dist/src/config.d.ts +28 -0
- package/dist/src/config.js +20 -0
- package/dist/src/geometry.d.ts +21 -0
- package/dist/src/geometry.js +21 -0
- package/dist/src/meter.d.ts +76 -0
- package/dist/src/meter.js +95 -0
- package/dist/src/mind/attention.d.ts +4 -0
- package/dist/src/mind/attention.js +165 -16
- package/dist/src/mind/canonical.d.ts +16 -0
- package/dist/src/mind/canonical.js +41 -0
- package/dist/src/mind/corpus.d.ts +40 -0
- package/dist/src/mind/corpus.js +149 -0
- package/dist/src/mind/graph-search.d.ts +7 -0
- package/dist/src/mind/graph-search.js +254 -24
- package/dist/src/mind/index.d.ts +3 -1
- package/dist/src/mind/index.js +1 -0
- package/dist/src/mind/match.d.ts +9 -4
- package/dist/src/mind/match.js +147 -61
- package/dist/src/mind/mechanisms/cast.js +19 -3
- package/dist/src/mind/mechanisms/confluence.js +24 -0
- package/dist/src/mind/mechanisms/cover.js +6 -0
- package/dist/src/mind/mechanisms/recall.js +32 -4
- package/dist/src/mind/mind.d.ts +57 -0
- package/dist/src/mind/mind.js +72 -1
- package/dist/src/mind/pipeline-mechanism.d.ts +7 -0
- package/dist/src/mind/pipeline.js +66 -20
- package/dist/src/mind/primitives.js +9 -1
- package/dist/src/mind/rationale.d.ts +28 -1
- package/dist/src/mind/rationale.js +22 -1
- package/dist/src/mind/reasoning.d.ts +25 -3
- package/dist/src/mind/reasoning.js +125 -20
- package/dist/src/mind/recognition.js +4 -8
- package/dist/src/mind/resonance.js +20 -1
- package/dist/src/mind/trace.js +1 -0
- package/dist/src/mind/traverse.js +15 -3
- package/dist/src/mind/types.d.ts +49 -4
- package/docs/INVARIANTS.md +2 -2
- package/docs/architecture/bounded-reads.md +1 -1
- package/docs/architecture/commonality.md +2 -2
- package/docs/architecture/cost-model.md +2 -2
- package/docs/architecture/determinism.md +7 -7
- package/docs/architecture/match-project.md +2 -3
- package/docs/architecture/mechanism-market.md +10 -10
- package/docs/architecture/meter.md +5 -5
- package/docs/architecture/store.md +3 -3
- package/docs/failures/tempting-but-wrong.md +34 -6
- package/docs/harness/gates.md +2 -2
- package/docs/mechanisms/cast.md +2 -2
- package/docs/mechanisms/cover.md +2 -3
- package/docs/mechanisms/extraction.md +7 -7
- package/docs/mechanisms/recall.md +8 -9
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/alu/README.md +11 -12
- package/src/config.ts +48 -0
- package/src/geometry.ts +21 -0
- package/src/meter.ts +98 -0
- package/src/mind/attention.ts +167 -16
- package/src/mind/canonical.ts +43 -0
- package/src/mind/corpus.ts +202 -0
- package/src/mind/graph-search.ts +277 -23
- package/src/mind/index.ts +8 -1
- package/src/mind/match.ts +148 -57
- package/src/mind/mechanisms/cast.ts +20 -2
- package/src/mind/mechanisms/confluence.ts +24 -0
- package/src/mind/mechanisms/cover.ts +5 -0
- package/src/mind/mechanisms/recall.ts +32 -4
- package/src/mind/mind.ts +125 -0
- package/src/mind/pipeline-mechanism.ts +7 -0
- package/src/mind/pipeline.ts +79 -22
- package/src/mind/primitives.ts +9 -1
- package/src/mind/rationale.ts +35 -1
- package/src/mind/reasoning.ts +145 -13
- package/src/mind/recognition.ts +4 -8
- package/src/mind/resonance.ts +19 -1
- package/src/mind/trace.ts +1 -0
- package/src/mind/traverse.ts +16 -6
- package/src/mind/types.ts +53 -4
- package/test/100-complete-grounding-trace.test.mjs +109 -0
- package/test/101-alignment-gap-bound.test.mjs +106 -0
- package/test/102-production-composes-at-scale.test.mjs +110 -0
- package/test/103-alignment-gap-budget.test.mjs +89 -0
- package/test/104-composition-is-reported.test.mjs +90 -0
- package/test/105-derive-through-reports-its-refusal.test.mjs +137 -0
- package/test/106-the-join-fires.test.mjs +94 -0
- package/test/107-the-join-is-counted.test.mjs +81 -0
- package/test/108-the-join-chains.test.mjs +78 -0
- package/test/109-the-pivot-is-counted.test.mjs +60 -0
- package/test/110-the-reasoner-stops-when-the-question-is-answered.test.mjs +91 -0
- package/test/111-the-cover-assembly-is-counted.test.mjs +74 -0
- package/test/112-the-exploration-does-not-grow-with-the-hub.test.mjs +89 -0
- package/test/113-the-rationale-payload-is-bounded.test.mjs +84 -0
- package/test/114-alignment-budget-is-per-sweep.test.mjs +93 -0
- package/test/116-the-extension-is-gated-by-the-pipelines-own-remainder.test.mjs +100 -0
- package/test/117-corpus-search.test.mjs +171 -0
- package/test/118-the-join-reaches-a-key-off-the-cut.test.mjs +74 -0
- package/test/119-the-work-does-not-grow-with-the-corpus.test.mjs +122 -0
- package/test/120-composition-is-consequence.test.mjs +132 -0
- package/test/121-the-extension-does-not-grow-with-the-corpus.test.mjs +128 -0
- package/test/122-the-climb-search-does-not-grow-with-the-corpus.test.mjs +117 -0
- package/test/123-the-paired-formulas-agree.test.mjs +90 -0
- package/test/125-the-post-grounding-branch-publishes-its-operand.test.mjs +51 -0
- package/test/126-the-pipeline-does-not-name-mechanisms.test.mjs +42 -0
- package/test/128-the-leads-somewhere-pair-agrees.test.mjs +83 -0
- package/test/129-the-trace-payload-shape.test.mjs +164 -0
- package/test/14-scaling.test.mjs +10 -7
- package/test/32-confluence.test.mjs +68 -0
- package/test/38-reason-restate-guard.test.mjs +8 -2
- package/test/43-cast-analog-seat.test.mjs +10 -0
- package/test/55-cost-meter.test.mjs +859 -0
- package/test/76-reference-binding.test.mjs +6 -1
- package/test/89-completion-recursion.test.mjs +30 -5
|
@@ -12,7 +12,7 @@ import { PASS, STEP } from "./graph-search.js";
|
|
|
12
12
|
import { gistOf, read, resolve } from "./primitives.js";
|
|
13
13
|
import { recognise } from "./recognition.js";
|
|
14
14
|
import { fuseAttention, reason } from "./reasoning.js";
|
|
15
|
-
import { unexplainedSpans } from "./rationale.js";
|
|
15
|
+
import { unaccountedBytes, unexplainedSpans } from "./rationale.js";
|
|
16
16
|
import { rItem } from "./trace.js";
|
|
17
17
|
import { hubBound } from "./traverse.js";
|
|
18
18
|
import { Precomputed } from "./pipeline-mechanism.js";
|
|
@@ -124,8 +124,7 @@ export async function think(ctx, query, mechs) {
|
|
|
124
124
|
// (meter.md); the trace already represents structure.
|
|
125
125
|
const pre = new Precomputed(ctx, query, rec, computed, ctx._edgeGuide);
|
|
126
126
|
const grade = (w) => Math.floor(w / STEP);
|
|
127
|
-
const unaccounted = (spans) => unexplainedSpans(query.length, spans)
|
|
128
|
-
.reduce((sum, [s, e]) => sum + (e - s), 0);
|
|
127
|
+
const unaccounted = (spans) => unaccountedBytes(unexplainedSpans(query.length, spans));
|
|
129
128
|
const weigh = (accounted, moves) => moves + PASS * unaccounted(accounted);
|
|
130
129
|
const candidates = [];
|
|
131
130
|
let best = null;
|
|
@@ -317,13 +316,10 @@ export async function think(ctx, query, mechs) {
|
|
|
317
316
|
}
|
|
318
317
|
const answer = decided.bytes;
|
|
319
318
|
const provenance = decided.provenance;
|
|
320
|
-
const
|
|
319
|
+
const declaredUsed = decided.used;
|
|
321
320
|
// ── Post-grounding, gated by provenance ──────────────────────────────
|
|
322
|
-
const preConsumed =
|
|
323
|
-
|
|
324
|
-
: provenance === "recall" || provenance === "recall-echo"
|
|
325
|
-
? new Set()
|
|
326
|
-
: new Set(recognise(ctx, answer).sites.map((s) => s.payload));
|
|
321
|
+
const preConsumed = declaredUsed ??
|
|
322
|
+
new Set(recognise(ctx, answer).sites.map((s) => s.payload));
|
|
327
323
|
// A grounding that DECLARED itself complete is not extended: the answer is
|
|
328
324
|
// already a trained form's own continuation, reached through an identity
|
|
329
325
|
// claim about the query, so a multi-hop pivot could only chain past the
|
|
@@ -352,12 +348,59 @@ export async function think(ctx, query, mechs) {
|
|
|
352
348
|
// `preConsumed` is derived by re-recognising the answer — "everything in
|
|
353
349
|
// it", not "what it voiced" — and a containment rule over that would
|
|
354
350
|
// suppress every pivot the answer legitimately contains.
|
|
355
|
-
const voiced =
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
351
|
+
const voiced = declaredUsed === undefined ? [] : [...declaredUsed].flatMap((id) => ctx.store.nextFirst(id, hubBound(ctx)).map((n) => read(ctx, n)));
|
|
352
|
+
// WHAT THIS BRANCH READ, published where it was read. Post-grounding decides
|
|
353
|
+
// by `decided.used` and by the provenance NAME; the operands were invisible in
|
|
354
|
+
// the trace, so a change to the branching could not be shown equivalent or
|
|
355
|
+
// otherwise from outside — three separate investigations failed on exactly
|
|
356
|
+
// that gap. A gap in instrumentation is a defect IN the instrumentation
|
|
357
|
+
// (AGENTS.md §6): closed here, once, as counts only — never content.
|
|
358
|
+
ctx.trace?.step("postGrounding", [rItem(answer, provenance)], [], `used=${decided.used !== undefined ? "declared" : "absent"} · ` +
|
|
359
|
+
`preConsumed=${preConsumed.size} · voiced=${voiced.length}`, undefined, {
|
|
360
|
+
version: 1,
|
|
361
|
+
provenance,
|
|
362
|
+
usedDeclared: decided.used !== undefined,
|
|
363
|
+
preConsumed: preConsumed.size,
|
|
364
|
+
voiced: voiced.length,
|
|
365
|
+
});
|
|
366
|
+
// REPORTABLE, NOT SILENT. A declared-complete grounding ends the derivation
|
|
367
|
+
// here, and that decision is part of the derivation's shape: the reader of a
|
|
368
|
+
// rationale must be able to see that the chain stopped because the mechanism
|
|
369
|
+
// claimed the query WAS the context, not because nothing followed. The step
|
|
370
|
+
// carries the claim, not a re-description of the answer — the extension is
|
|
371
|
+
// skipped, so there is no output item to show.
|
|
372
|
+
if (decided.complete) {
|
|
373
|
+
ctx.trace?.step("completeGrounding", [rItem(answer, provenance)], [], "grounding declared complete — the query IS the context, so " +
|
|
374
|
+
"post-grounding extension is skipped");
|
|
375
|
+
}
|
|
376
|
+
// THE REASONER JUDGES ITS OWN EXTENSIONS BY THE PIPELINE'S REMAINDER, not by
|
|
377
|
+
// the ladder's `accounted` — and by the SAME reading the fuse gate below uses,
|
|
378
|
+
// with the same W floor. `accounted` is a COST quantity (measured: a query
|
|
379
|
+
// fully explained by one computed span plus bridged connectors reports
|
|
380
|
+
// `accounted: []` while nothing is unexplained), and a remainder under one
|
|
381
|
+
// river-fold quantum is bridging punctuation, never a second topic — so it
|
|
382
|
+
// licenses no extension and blocks none.
|
|
383
|
+
const explained = [
|
|
384
|
+
...decided.accounted,
|
|
385
|
+
...pre.computed.map((u) => [u.i, u.j]),
|
|
386
|
+
];
|
|
387
|
+
const uncovered = unexplainedSpans(query.length, explained)
|
|
388
|
+
.filter(([a, b]) => b - a >= ctx.space.maxGroup);
|
|
389
|
+
// PUBLISHED, NOT RECOMPUTED: the same `uncovered` the gates below read. A
|
|
390
|
+
// write-only accounting (meter contract 1), so the number that licenses an
|
|
391
|
+
// extension or a fusion stops being invisible.
|
|
392
|
+
if (meter) {
|
|
393
|
+
meter.postGroundingRemainderSpans += uncovered.length;
|
|
394
|
+
meter.postGroundingRemainderBytes += unaccountedBytes(uncovered);
|
|
395
|
+
}
|
|
396
|
+
// The extension is kept as a WHOLE (bytes + what it carried + how many steps),
|
|
397
|
+
// not just its bytes: pricing it — `steps · STEP` against `PASS · unaccounted`
|
|
398
|
+
// — is the caller's job, one comparison away. `reasoned` stays the bytes so
|
|
399
|
+
// everything downstream is untouched.
|
|
400
|
+
const extension = decided.complete ? undefined : meter
|
|
401
|
+
? await meter.time("reason", () => reason(ctx, query, answer, preConsumed, pre, voiced, uncovered))
|
|
402
|
+
: await reason(ctx, query, answer, preConsumed, pre, voiced, uncovered);
|
|
403
|
+
const reasoned = extension?.bytes ?? answer;
|
|
361
404
|
// Fuse only when the query has a genuine REMAINDER no mechanism's
|
|
362
405
|
// structural evidence touched at all. `decided.accounted` alone
|
|
363
406
|
// undercounts this: it is a COST-LADDER quantity (cover.ts prices its
|
|
@@ -374,10 +417,6 @@ export async function think(ctx, query, mechs) {
|
|
|
374
417
|
// observed: a single space between two fully-computed arithmetic spans
|
|
375
418
|
// ("2+2 3+3") registered as "unaccounted" and pulled in an unrelated
|
|
376
419
|
// corpus fact, corrupting "4 6" into "4 63".
|
|
377
|
-
const explained = [
|
|
378
|
-
...decided.accounted,
|
|
379
|
-
...pre.computed.map((u) => [u.i, u.j]),
|
|
380
|
-
];
|
|
381
420
|
const remainder = unaccounted(explained);
|
|
382
421
|
// Whether the winning candidate's entire recognised substance is
|
|
383
422
|
// COMPUTED — every accounted span exactly a pre.computed span, nothing
|
|
@@ -405,6 +444,13 @@ export async function think(ctx, query, mechs) {
|
|
|
405
444
|
: meter
|
|
406
445
|
? await meter.time("fuse", () => fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans))
|
|
407
446
|
: await fuseAttention(ctx, query, reasoned, pre, unclimbed, primarySpans);
|
|
408
|
-
done(fused,
|
|
447
|
+
done(fused,
|
|
448
|
+
// NO CLAIM ABOUT FUSION HERE. `fuseAttention` is entered whenever a
|
|
449
|
+
// remainder ≥ W exists and returns early when there is nothing to bridge, so
|
|
450
|
+
// this note used to assert a fusion that frequently did not happen (measured:
|
|
451
|
+
// "What is the capital of France famous for" fuses 0 times). The fusion is
|
|
452
|
+
// reported by `fuseAttention`'s own `done` when it happens — the layer that
|
|
453
|
+
// did the work is the layer that says so.
|
|
454
|
+
"grounded, reasoned forward");
|
|
409
455
|
return { bytes: fused, provenance };
|
|
410
456
|
}
|
|
@@ -297,7 +297,15 @@ export function canonResolve(ctx, bytes) {
|
|
|
297
297
|
// on exactly the node the canonical-case query would have found.
|
|
298
298
|
const folded = foldTree(ctx, perceive(ctx, bytesOf), 0).node;
|
|
299
299
|
const use = folded ?? id;
|
|
300
|
-
|
|
300
|
+
// THE ADMISSION PREDICATE, by its own pair of probes: `traverse.ts`'s
|
|
301
|
+
// `leadsSomewhere` is edge-or-halo, and `hasHalo` is the one that carries
|
|
302
|
+
// the mass bar (`mass >= minHaloMass`). Asking `haloMass(use) > 0` instead
|
|
303
|
+
// is the same answer only while `minHaloMass <= 1` (its default): raise the
|
|
304
|
+
// bar and this site would rank a node as leading on evidence the law
|
|
305
|
+
// refuses. Calling `leadsSomewhere` here is not possible — `traverse.ts`
|
|
306
|
+
// imports THIS file, so it would be a cycle — which is why the pair is
|
|
307
|
+
// spelled out rather than named.
|
|
308
|
+
const leads = store.hasNext(use) || store.hasHalo(use);
|
|
301
309
|
if (best === null || (leads && !bestLeads) ||
|
|
302
310
|
(leads === bestLeads && use < best)) {
|
|
303
311
|
best = use;
|
|
@@ -26,6 +26,17 @@ export interface RationaleItem {
|
|
|
26
26
|
* caller asked to carry it (off by default — a D-float array per item would
|
|
27
27
|
* bury the reasoning it is meant to explain). */
|
|
28
28
|
v?: Vec;
|
|
29
|
+
/** The element's OWN bytes, attached BY REFERENCE when the step was built from
|
|
30
|
+
* bytes (a `rationale.ts` item made from a node carries none: read it back
|
|
31
|
+
* through `node`). `text` is a RENDERING and cannot stand in for them — it
|
|
32
|
+
* decodes UTF-8 and DROPS NUL bytes, so a key containing one is unrecoverable
|
|
33
|
+
* from it, which is exactly how a join refusal (`deriveThroughMiss`) became
|
|
34
|
+
* impossible to test exactly without re-encoding. Treat as READ-ONLY: the
|
|
35
|
+
* array belongs to the caller (and may be a view into the query).
|
|
36
|
+
*
|
|
37
|
+
* Costs nothing when nothing inspects: items exist only while a rationale
|
|
38
|
+
* sink is attached, and this holds a reference rather than a copy. */
|
|
39
|
+
bytes?: Uint8Array;
|
|
29
40
|
}
|
|
30
41
|
/** A single completed act of inference — one mechanism, run once.
|
|
31
42
|
*
|
|
@@ -72,6 +83,13 @@ export type InspectRationale = (step: RationaleStep) => void;
|
|
|
72
83
|
/** Decode bytes to text for display, dropping the NUL padding the encoder uses
|
|
73
84
|
* (the same cleanup {@link Mind.respondText} does for its result). */
|
|
74
85
|
export declare function decodeText(bytes: Uint8Array): string;
|
|
86
|
+
/** The BYTE COUNT of the complement — what the currency calls `unaccounted`
|
|
87
|
+
* in `weight = moves + PASS·unaccounted`. It lives here, beside the function
|
|
88
|
+
* that produces the gaps, because the price's second term has ONE definition:
|
|
89
|
+
* this was four copies of the same `reduce` (two in reasoning.ts, two in
|
|
90
|
+
* pipeline.ts) before the architecture audit of `../auditoria-arquitectura-sema.md`
|
|
91
|
+
* collapsed them. Same value at every site — the control diff is identical. */
|
|
92
|
+
export declare function unaccountedBytes(spans: ReadonlyArray<readonly [number, number]>): number;
|
|
75
93
|
/** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
|
|
76
94
|
* the same union-of-spans reading think's grounding decider prices at PASS
|
|
77
95
|
* per byte, exposed here so a mechanism can turn it into a human label. */
|
|
@@ -133,7 +151,16 @@ export declare class Rationale {
|
|
|
133
151
|
* now; the matching {@link Scope.done} supplies the outputs when it finishes.
|
|
134
152
|
* `deps` overrides the default data-flow edge (previous sibling / parent). */
|
|
135
153
|
enter(name: string, inputs: RationaleItem[], deps?: number[]): Scope;
|
|
136
|
-
/**
|
|
154
|
+
/** WHY THIS NAME IS A FREE STRING, when the derivation's moves are a closed
|
|
155
|
+
* union: a mechanism name is WRITTEN and DISPLAYED, and it COMPOSES with
|
|
156
|
+
* the nesting — `mechanism` is the whole path (`["respond", "think",
|
|
157
|
+
* "recognise"]`), which no fixed union can express. Nothing branches on it:
|
|
158
|
+
* `nothing here drives the inference; it only WITNESSES it`. A vocabulary
|
|
159
|
+
* that is only witnessed needs no union; one that is read does
|
|
160
|
+
* (`DerivationMove`, in graph-search.ts). The asymmetry is the design, not
|
|
161
|
+
* a drift.
|
|
162
|
+
*
|
|
163
|
+
* Record a mechanism that has no sub-steps — its inputs and outputs are both
|
|
137
164
|
* known at the call site. Returns its index, for a later step to depend on. */
|
|
138
165
|
step(name: string, inputs: RationaleItem[], outputs: RationaleItem[], note?: string, deps?: number[], data?: unknown): number;
|
|
139
166
|
}
|
|
@@ -24,6 +24,18 @@
|
|
|
24
24
|
export function decodeText(bytes) {
|
|
25
25
|
return new TextDecoder().decode(bytes.filter((b) => b !== 0x00));
|
|
26
26
|
}
|
|
27
|
+
/** The BYTE COUNT of the complement — what the currency calls `unaccounted`
|
|
28
|
+
* in `weight = moves + PASS·unaccounted`. It lives here, beside the function
|
|
29
|
+
* that produces the gaps, because the price's second term has ONE definition:
|
|
30
|
+
* this was four copies of the same `reduce` (two in reasoning.ts, two in
|
|
31
|
+
* pipeline.ts) before the architecture audit of `../auditoria-arquitectura-sema.md`
|
|
32
|
+
* collapsed them. Same value at every site — the control diff is identical. */
|
|
33
|
+
export function unaccountedBytes(spans) {
|
|
34
|
+
let total = 0;
|
|
35
|
+
for (const [a, b] of spans)
|
|
36
|
+
total += b - a;
|
|
37
|
+
return total;
|
|
38
|
+
}
|
|
27
39
|
/** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
|
|
28
40
|
* the same union-of-spans reading think's grounding decider prices at PASS
|
|
29
41
|
* per byte, exposed here so a mechanism can turn it into a human label. */
|
|
@@ -151,7 +163,16 @@ export class Rationale {
|
|
|
151
163
|
},
|
|
152
164
|
};
|
|
153
165
|
}
|
|
154
|
-
/**
|
|
166
|
+
/** WHY THIS NAME IS A FREE STRING, when the derivation's moves are a closed
|
|
167
|
+
* union: a mechanism name is WRITTEN and DISPLAYED, and it COMPOSES with
|
|
168
|
+
* the nesting — `mechanism` is the whole path (`["respond", "think",
|
|
169
|
+
* "recognise"]`), which no fixed union can express. Nothing branches on it:
|
|
170
|
+
* `nothing here drives the inference; it only WITNESSES it`. A vocabulary
|
|
171
|
+
* that is only witnessed needs no union; one that is read does
|
|
172
|
+
* (`DerivationMove`, in graph-search.ts). The asymmetry is the design, not
|
|
173
|
+
* a drift.
|
|
174
|
+
*
|
|
175
|
+
* Record a mechanism that has no sub-steps — its inputs and outputs are both
|
|
155
176
|
* known at the call site. Returns its index, for a later step to depend on. */
|
|
156
177
|
step(name, inputs, outputs, note, deps, data) {
|
|
157
178
|
const mechanism = this.path(name);
|
|
@@ -13,14 +13,36 @@ import type { Precomputed } from "./pipeline-mechanism.js";
|
|
|
13
13
|
export declare function restatesQuery(query: Uint8Array, bytes: Uint8Array): boolean;
|
|
14
14
|
/** Extend a grounded answer forward across facts (multi-hop reasoning).
|
|
15
15
|
* Pivots on the longest unconsumed learnt context each answer contains,
|
|
16
|
-
* then follows the pivot's continuation to the next fact.
|
|
17
|
-
*
|
|
16
|
+
* then follows the pivot's continuation to the next fact. **The chain ends
|
|
17
|
+
* when it STOPS, never when a count runs out**: every exit is a refusal (no
|
|
18
|
+
* pivot, no forward step, no question material carried) and the walk is bounded
|
|
19
|
+
* by the material and the graph — `consumed` refuses to revisit a node. There
|
|
20
|
+
* is no hop allowance, so this doc deliberately names no `cfg` capacity: the
|
|
21
|
+
* cover prices every hop at `STEP` and lets the search decide the depth, and a
|
|
22
|
+
* second count here would be a second decision about the same thing.
|
|
23
|
+
* `preConsumed` carries node ids already
|
|
18
24
|
* spoken for by the grounding stage (cover/extract/CAST). `voiced` carries
|
|
19
25
|
* the BYTES of the anchors a mechanism declared it voiced (its `used` set),
|
|
20
26
|
* when it declared one — see the pivot's own containment rule. `pre` is the
|
|
21
27
|
* response's shared pre-computation — the post-grounding stages read the
|
|
22
28
|
* same container the mechanisms did. */
|
|
23
|
-
|
|
29
|
+
/** What the multi-hop extension produced, and what it cost: the bytes (the
|
|
30
|
+
* answer), the spans of the grounding's UNCOVERED material that each step was
|
|
31
|
+
* justified by, and how many steps were taken. The last two exist so the
|
|
32
|
+
* caller can price the extension in the ladder's own currency — `steps · STEP`
|
|
33
|
+
* against `PASS · unaccounted` — instead of taking it unconditionally. Both
|
|
34
|
+
* are FACTS, not verdicts: nothing here says whether the extension was worth
|
|
35
|
+
* it; that is the comparison's job, one layer up. */
|
|
36
|
+
export interface ReasonedAnswer {
|
|
37
|
+
bytes: Uint8Array;
|
|
38
|
+
carried: Array<[number, number]>;
|
|
39
|
+
steps: number;
|
|
40
|
+
}
|
|
41
|
+
export declare function reason(ctx: MindContext, query: Uint8Array, answer: Uint8Array, preConsumed: ReadonlySet<number>, pre: Precomputed, voiced?: readonly Uint8Array[],
|
|
42
|
+
/** The query material the GROUNDING left uncovered — the cost ladder's own
|
|
43
|
+
* `unaccounted` spans. Only the reasoner's OWN extensions are judged
|
|
44
|
+
* against it; a mechanism carrying its own `used` set owns its shape. */
|
|
45
|
+
uncovered?: readonly (readonly [number, number])[]): Promise<ReasonedAnswer>;
|
|
24
46
|
/** Fuse independent points of attention into one answer (multi-topic).
|
|
25
47
|
* When the consensus climb finds more than one dominant point, each
|
|
26
48
|
* independent point grounds its own answer; they are bridged together
|
|
@@ -6,8 +6,9 @@ import { rItem, rNode } from "./trace.js";
|
|
|
6
6
|
import { bytesEqual, indexOf } from "../bytes.js";
|
|
7
7
|
import { resolve } from "./primitives.js";
|
|
8
8
|
import { hubBound } from "./traverse.js";
|
|
9
|
-
import { follow, haloSiblings, project } from "./match.js";
|
|
9
|
+
import { containsSpan, follow, haloSiblings, project } from "./match.js";
|
|
10
10
|
import { joinWithBridge, pivotInto } from "./resonance.js";
|
|
11
|
+
import { unaccountedBytes } from "./rationale.js";
|
|
11
12
|
/** Whether `bytes` is a proper byte-subspan of `query` — already present in
|
|
12
13
|
* the question, so voicing it back only restates part of what was asked,
|
|
13
14
|
* never answers it. The exact guard recallByResonance already applies to
|
|
@@ -21,24 +22,20 @@ import { joinWithBridge, pivotInto } from "./resonance.js";
|
|
|
21
22
|
export function restatesQuery(query, bytes) {
|
|
22
23
|
return bytes.length < query.length && indexOf(query, bytes, 0) >= 0;
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
* the BYTES of the anchors a mechanism declared it voiced (its `used` set),
|
|
30
|
-
* when it declared one — see the pivot's own containment rule. `pre` is the
|
|
31
|
-
* response's shared pre-computation — the post-grounding stages read the
|
|
32
|
-
* same container the mechanisms did. */
|
|
33
|
-
export async function reason(ctx, query, answer, preConsumed, pre, voiced = []) {
|
|
25
|
+
export async function reason(ctx, query, answer, preConsumed, pre, voiced = [],
|
|
26
|
+
/** The query material the GROUNDING left uncovered — the cost ladder's own
|
|
27
|
+
* `unaccounted` spans. Only the reasoner's OWN extensions are judged
|
|
28
|
+
* against it; a mechanism carrying its own `used` set owns its shape. */
|
|
29
|
+
uncovered = []) {
|
|
34
30
|
// Echo guard: a query that is ITSELF a learnt continuation (some context's
|
|
35
31
|
// answer) is being asked back at the system — hopping forward from it would
|
|
36
32
|
// chain through the very fact that produced it and echo the conversation
|
|
37
33
|
// back. The grounded answer alone is the honest read-out. Deliberately a
|
|
38
34
|
// broad structural gate; pinned by test/31-audit.
|
|
39
35
|
const qId = pre.queryResolved;
|
|
40
|
-
if (qId !== null && ctx.store.prevCount(qId) > 0)
|
|
41
|
-
return answer;
|
|
36
|
+
if (qId !== null && ctx.store.prevCount(qId) > 0) {
|
|
37
|
+
return { bytes: answer, carried: [], steps: 0 };
|
|
38
|
+
}
|
|
42
39
|
// Consume a node and its neighbours for pivot-cycle prevention — CAPPED at
|
|
43
40
|
// the hub bound, via the store's LIMITed edge reads: a common continuation's
|
|
44
41
|
// reverse fan-in (and a hub context's forward fan-out) is corpus-sized, and
|
|
@@ -95,7 +92,7 @@ export async function reason(ctx, query, answer, preConsumed, pre, voiced = [])
|
|
|
95
92
|
? null
|
|
96
93
|
: ctx.store.prevFirst(groundedId, bound);
|
|
97
94
|
if (qId !== null && groundedPrev !== null && groundedPrev.includes(qId)) {
|
|
98
|
-
return answer;
|
|
95
|
+
return { bytes: answer, carried: [], steps: 0 };
|
|
99
96
|
}
|
|
100
97
|
const consumed = new Set();
|
|
101
98
|
/** `prev` lets a caller hand in an already-read reverse-edge list — hop 0
|
|
@@ -143,7 +140,27 @@ export async function reason(ctx, query, answer, preConsumed, pre, voiced = [])
|
|
|
143
140
|
const qv = pre.guide; // the response-wide guide IS the query's gist
|
|
144
141
|
let t;
|
|
145
142
|
const startedFrom = answer;
|
|
146
|
-
|
|
143
|
+
// INSTRUMENTATION ONLY — the two facts the extension's own decision already
|
|
144
|
+
// used and threw away: the spans of uncovered material each step was
|
|
145
|
+
// JUSTIFIED by (the gate below computes which span carries it and kept only a
|
|
146
|
+
// boolean), and how many steps were taken. Nothing here decides anything:
|
|
147
|
+
// both are read after the loop, to bump counters and to let the caller compare
|
|
148
|
+
// the extension's cost against what it explains, in the ladder's own currency.
|
|
149
|
+
const carried = [];
|
|
150
|
+
let steps = 0;
|
|
151
|
+
// NO ALLOWANCE: THE CHAIN ENDS WHEN IT STOPS. Every exit below is the law —
|
|
152
|
+
// no pivot, no forward step, no question material carried — and the walk is
|
|
153
|
+
// bounded by the material and the graph rather than by a count: each taken
|
|
154
|
+
// step must carry a W-window of the uncovered material (finite), and
|
|
155
|
+
// `consumed` refuses to revisit a node. `recallQueryK` no longer bounds the
|
|
156
|
+
// reasoner here; it keeps its other roles (the bridge's candidate reads, the
|
|
157
|
+
// pivot's probe budget, the resonance limits).
|
|
158
|
+
//
|
|
159
|
+
// Measured before removing it: test/89 — the corpus-cost guard, the heaviest
|
|
160
|
+
// case in the suite — is green and no slower without the allowance (27 s
|
|
161
|
+
// against 30 s); and raising it from 12 to 200 changed neither the answer nor
|
|
162
|
+
// `pivotSteps` on the chain fixtures.
|
|
163
|
+
for (let hop = 0;; hop++) {
|
|
147
164
|
// Hop 0's `cur` IS `answer`, so the guard above already resolved it and
|
|
148
165
|
// read its reverse edges — reuse both rather than repeat them.
|
|
149
166
|
const curId = hop === 0 ? groundedId : resolve(ctx, cur);
|
|
@@ -166,6 +183,7 @@ export async function reason(ctx, query, answer, preConsumed, pre, voiced = [])
|
|
|
166
183
|
]);
|
|
167
184
|
ctx.trace?.step("absorbForward", [rItem(cur, "answer", curId)], [rItem(fwd, "answer", resolve(ctx, fwd) ?? undefined)], "the answer is itself a learnt fact — follow its continuation to the fixpoint");
|
|
168
185
|
cur = fwd;
|
|
186
|
+
steps++;
|
|
169
187
|
continue;
|
|
170
188
|
}
|
|
171
189
|
}
|
|
@@ -178,12 +196,98 @@ export async function reason(ctx, query, answer, preConsumed, pre, voiced = [])
|
|
|
178
196
|
consumeAll(pivot);
|
|
179
197
|
if (fc === null || bytesEqual(fc, cur) || restatesQuery(query, fc))
|
|
180
198
|
break;
|
|
199
|
+
// WHOSE EXTENSION IS THIS?
|
|
200
|
+
//
|
|
201
|
+
// `voiced` is what the mechanism WITHHELD (the pipeline sends the used
|
|
202
|
+
// anchors' CONTINUATIONS, not their bytes — see pipeline's own note), so a
|
|
203
|
+
// non-empty `voiced` means exactly what that note says: the grounding came
|
|
204
|
+
// from a mechanism that carries its own short `used` set (cast/join) and
|
|
205
|
+
// therefore owns the shape of its answer. The further terms inside such a
|
|
206
|
+
// seat are legitimately followable — test/29 C3's `Mona Lisa` lives inside
|
|
207
|
+
// the voiced seat and leads on to a fact about neither analog.
|
|
208
|
+
//
|
|
209
|
+
// Every other grounding is ordinary, and an extension of it is the
|
|
210
|
+
// reasoner's own inference: it is taken only while question material the
|
|
211
|
+
// grounding left uncovered remains AND the step carries some of it, judged
|
|
212
|
+
// by the mind's own line between chance and evidence — one W-byte window,
|
|
213
|
+
// no word notion, no character class, no threshold. Measured: the drift's
|
|
214
|
+
// second step (`the Eiffel Tower is in Paris` after `Paris is famous for
|
|
215
|
+
// the Eiffel Tower`) carries no window of `" famous for"` and is refused,
|
|
216
|
+
// while the first carries it. Terminates by a real argument: the uncovered
|
|
217
|
+
// material is finite and each taken extension must carry some of it.
|
|
218
|
+
const producerOwnsShape = voiced.length > 0;
|
|
219
|
+
if (!producerOwnsShape && uncovered.length > 0) {
|
|
220
|
+
const W = ctx.space.maxGroup;
|
|
221
|
+
let progress = false;
|
|
222
|
+
let justified;
|
|
223
|
+
for (const [a, b] of uncovered) {
|
|
224
|
+
for (let i = a; i + W <= b && !progress; i++) {
|
|
225
|
+
if (indexOf(fc, query.subarray(i, i + W), 0) >= 0) {
|
|
226
|
+
progress = true;
|
|
227
|
+
justified = [a, b];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (progress)
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
if (progress && justified !== undefined)
|
|
234
|
+
carried.push(justified);
|
|
235
|
+
if (!progress) {
|
|
236
|
+
// THE BRAKE, MADE VISIBLE. The reasoner declines a step that carries
|
|
237
|
+
// none of the material the grounding left uncovered — the drift the
|
|
238
|
+
// extension tests pin. A refusal that leaves no trace is the kind of
|
|
239
|
+
// silent cut AGENTS §6 forbids: the rationale is where a reader learns
|
|
240
|
+
// that an extension was declined for want of question material, and
|
|
241
|
+
// where the next person sees why the chain stopped here. Measured with
|
|
242
|
+
// the check disabled, test/110 and test/116 fail — so this brake is the
|
|
243
|
+
// only thing keeping the extension honest until the pivot reports its
|
|
244
|
+
// own accounted spans and the ladder can judge it instead.
|
|
245
|
+
//
|
|
246
|
+
// THE PROMISE IS NOW KEPT, AND THE BRAKE TURNS OUT TO BE THE LADDER'S
|
|
247
|
+
// OWN CONSEQUENCE. The extension reports what it carried (`carried`,
|
|
248
|
+
// the span each step was justified by) and what it cost (`steps`), both
|
|
249
|
+
// counted in the meter (`reasonCarriedBytes`, `reasonSteps`), so the
|
|
250
|
+
// ladder CAN judge it: it accepts while
|
|
251
|
+
//
|
|
252
|
+
// steps · STEP < PASS · carried
|
|
253
|
+
//
|
|
254
|
+
// and this brake accepts whenever the step carries a `W`-window, i.e.
|
|
255
|
+
// whenever `carried ≥ W ≥ 1`. With `PASS/STEP = 1000` the two therefore
|
|
256
|
+
// agree on every extension with `steps ≤ 1000 · carried` — and every
|
|
257
|
+
// extension this repository produces takes 0 or 1 steps (measured on
|
|
258
|
+
// chains of 3, 8, 20 and 40 links). Above that bound the ladder would
|
|
259
|
+
// refuse what this brake accepts, which is the corner named in the
|
|
260
|
+
// closure report's limits: a chain of thousands of links explaining a
|
|
261
|
+
// handful of bytes. No guard is added for it — a limit without a
|
|
262
|
+
// derivation is exactly what the brake must not become.
|
|
263
|
+
const left = unaccountedBytes(uncovered);
|
|
264
|
+
ctx.trace?.step("pivotRefused", [rItem(cur, "answer"), rItem(query, "query")], uncovered.map(([a, b]) => rItem(query.subarray(a, b), "uncovered")), `the step carries none of the question material the grounding left ` +
|
|
265
|
+
`uncovered (${left} byte(s) in ${uncovered.length} span(s)) — refused`);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (ctx.meter)
|
|
270
|
+
ctx.meter.pivotSteps++;
|
|
181
271
|
t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
|
|
182
272
|
ctx.trace?.step("pivotStep", [rItem(cur, "answer"), rNode(ctx, pivot, "pivot")], [rItem(fc, "answer", resolve(ctx, fc) ?? undefined)], "pivot on the shared span this answer contains, then step forward across that fact");
|
|
183
273
|
cur = fc;
|
|
274
|
+
steps++;
|
|
275
|
+
}
|
|
276
|
+
// INSTRUMENTATION ONLY — the extension's two facts, untraced (meter.ts
|
|
277
|
+
// contract 1: a counter never reaches a decision). They are what a caller
|
|
278
|
+
// needs to PRICE the extension instead of taking it unconditionally: the work
|
|
279
|
+
// it did (`steps · STEP`) and the uncovered material it carried.
|
|
280
|
+
if (ctx.meter) {
|
|
281
|
+
ctx.meter.reasonSteps += steps;
|
|
282
|
+
ctx.meter.reasonCarriedBytes += unaccountedBytes(carried);
|
|
184
283
|
}
|
|
185
|
-
t?.done([rItem(cur, "answer", resolve(ctx, cur) ?? undefined)],
|
|
186
|
-
|
|
284
|
+
t?.done([rItem(cur, "answer", resolve(ctx, cur) ?? undefined)],
|
|
285
|
+
// A FIXPOINT: no further step was possible. This note used to also cover an
|
|
286
|
+
// exhausted hop allowance — a different fact, and the reason F1 added a
|
|
287
|
+
// counter for it. The allowance is gone, so the only way out of the loop is
|
|
288
|
+
// a refusal, and the note is true again by construction.
|
|
289
|
+
"the multi-hop chain's fixpoint");
|
|
290
|
+
return { bytes: cur, carried, steps };
|
|
187
291
|
}
|
|
188
292
|
/** Fuse independent points of attention into one answer (multi-topic).
|
|
189
293
|
* When the consensus climb finds more than one dominant point, each
|
|
@@ -352,8 +456,9 @@ primarySpans = []) {
|
|
|
352
456
|
out = await joinWithBridge(ctx, out, pieces[i].bytes);
|
|
353
457
|
}
|
|
354
458
|
t?.done([rItem(out, "answer", resolve(ctx, out) ?? undefined)], `fused ${pieces.length} independent points of attention into one answer`);
|
|
459
|
+
// THE FACT IS THE FUSED ANSWER, not the call: every early return above hands
|
|
460
|
+
// back `primary` untouched. Untraced on purpose (meter.ts contract 1).
|
|
461
|
+
if (ctx.meter)
|
|
462
|
+
ctx.meter.fuseRuns++;
|
|
355
463
|
return out;
|
|
356
464
|
}
|
|
357
|
-
// (resonance.js is already a static dependency above — `bridge` — so the old
|
|
358
|
-
// dynamic import of pivotInto guarded against a cycle that does not exist.)
|
|
359
|
-
import { containsSpan } from "./match.js";
|
|
@@ -519,8 +519,11 @@ function recogniseImpl(ctx, bytes) {
|
|
|
519
519
|
if (end - start < W)
|
|
520
520
|
return false;
|
|
521
521
|
if (flatProbe(start, end) === null) {
|
|
522
|
-
if (!canonBudget)
|
|
522
|
+
if (!canonBudget) {
|
|
523
|
+
if (ctx.meter)
|
|
524
|
+
ctx.meter.canonProbesDenied++;
|
|
523
525
|
return false;
|
|
526
|
+
}
|
|
524
527
|
if (!canonAdmits(start, end))
|
|
525
528
|
return false;
|
|
526
529
|
}
|
|
@@ -543,13 +546,6 @@ function recogniseImpl(ctx, bytes) {
|
|
|
543
546
|
// endpoints in order and running out partway along the query. A form
|
|
544
547
|
// longer than that is out of this tier's reach — but so is a form the
|
|
545
548
|
// chain cannot span, and that is exactly the trade the budget prices.
|
|
546
|
-
// The factor is chainReach(W), the same W² scale the chain already
|
|
547
|
-
// trusts; no new constant.
|
|
548
|
-
// The factor is chainReach(W) — the same W² scale the chain itself
|
|
549
|
-
// trusts — so the cap is derived from the fold's geometry, never tuned.
|
|
550
|
-
// (It was briefly an environment variable while the cost was being
|
|
551
|
-
// measured; an env-read here would make inference non-reproducible,
|
|
552
|
-
// which the determinism contract forbids outright.)
|
|
553
549
|
// The factor is chainReach(W) — the same W² scale the chain itself
|
|
554
550
|
// trusts — so the cap is derived from the fold's geometry, never tuned.
|
|
555
551
|
// (It was briefly an environment variable while the cost was being
|
|
@@ -249,7 +249,14 @@ export async function joinWithBridge(ctx, left, right) {
|
|
|
249
249
|
* overlap, so C3's genuine further hop — "Mona Lisa", a term inside the seat
|
|
250
250
|
* sentence but part of NEITHER analog — still fires. */
|
|
251
251
|
export async function pivotInto(ctx, answer, consumed, voiced = []) {
|
|
252
|
-
|
|
252
|
+
// The pivot's OWN shortlist capacity — not `recallQueryK`: they are different
|
|
253
|
+
// quantities (a mechanical sweep's probe budget vs the bridge's candidate-read
|
|
254
|
+
// allowance), and one number serving both means tightening either silently
|
|
255
|
+
// starves the other. The reason is the duplication, NOT a measured strangle:
|
|
256
|
+
// an earlier version of this comment claimed "at recallQueryK 1 the pivot finds
|
|
257
|
+
// no pivot", and that was probed and is FALSE on test/23's fixture — the
|
|
258
|
+
// strangle is fixture-specific, so it is not the evidence for this split.
|
|
259
|
+
const k = ctx.cfg.pivotProbeK;
|
|
253
260
|
// ONE perception of the answer, shared by the probe budget and the walk —
|
|
254
261
|
// this used to fold the same bytes twice, back to back, on every hop.
|
|
255
262
|
const tree = perceive(ctx, answer);
|
|
@@ -288,6 +295,18 @@ export async function pivotInto(ctx, answer, consumed, voiced = []) {
|
|
|
288
295
|
for (const c of n.kids)
|
|
289
296
|
queue.push(c); // breadth-first: larger regions first
|
|
290
297
|
}
|
|
298
|
+
// The sweep's two FACTS, untraced (meter.ts contract 1: a counter never
|
|
299
|
+
// reaches a decision). `probes` is the work done; the shortfall is the
|
|
300
|
+
// capacity the cap withheld — the branches the breadth-first order never got
|
|
301
|
+
// to. Whether that withheld anything that mattered is NOT said here: the
|
|
302
|
+
// order spends the largest regions first, and recognition below still
|
|
303
|
+
// contributes every exact containment candidate regardless of the budget.
|
|
304
|
+
if (ctx.meter) {
|
|
305
|
+
ctx.meter.pivotProbes += probes;
|
|
306
|
+
const unprobed = branchCount - probeCap;
|
|
307
|
+
if (unprobed > 0)
|
|
308
|
+
ctx.meter.pivotBranchesUnprobed += unprobed;
|
|
309
|
+
}
|
|
291
310
|
// THE FULL recognition, memo-shared with every other reader of these bytes.
|
|
292
311
|
// A "skip the edge trims here" variant was refuted (see recognise's own
|
|
293
312
|
// note): those trims are what find a WHOLE trained form embedded at an
|
package/dist/src/mind/trace.js
CHANGED
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
import { cosine } from "../vec.js";
|
|
9
9
|
import { gistOf, read } from "./primitives.js";
|
|
10
10
|
import { canonicalWindows, leafIdPrefix, leafIdRun } from "./canonical.js";
|
|
11
|
+
// Imported at the TOP, where every other import is. They used to sit 800 lines
|
|
12
|
+
// down under a note claiming the position mattered ("before trace module is
|
|
13
|
+
// loaded") — it does not: an ES module's static imports are HOISTED, so the
|
|
14
|
+
// file's line order never decides load order. The note described an intention
|
|
15
|
+
// the runtime does not honour; the imports move and the claim goes.
|
|
16
|
+
import { decodeText } from "./rationale.js";
|
|
11
17
|
//
|
|
12
18
|
// Budgeted on the same terms as the reach memo below (caches.md): these three
|
|
13
19
|
// maps are cleared on every write, but a long read-only session over a large
|
|
@@ -664,7 +670,15 @@ export function chooseNext(ctx, id, guide) {
|
|
|
664
670
|
// for the prevCount calls in the loop above, never for extra rItemShort
|
|
665
671
|
// byte-reads.
|
|
666
672
|
if (ctx.trace) {
|
|
667
|
-
|
|
673
|
+
// A BOUNDED SAMPLE, AND THE COUNT. The step used to carry EVERY candidate
|
|
674
|
+
// it weighed — measured on the trained store, 1559 out-items in one step
|
|
675
|
+
// (hubBound's own size) and 1082 in another (the hub's degree). The
|
|
676
|
+
// rationale's job is to explain the CHOICE, and the count is what says how
|
|
677
|
+
// wide the field was; the declared candidate budget (`recallQueryK`) is what
|
|
678
|
+
// bounds the sample, so no number is invented here.
|
|
679
|
+
const others = capped
|
|
680
|
+
.filter((c) => c !== best)
|
|
681
|
+
.slice(0, ctx.cfg.rationaleSampleK);
|
|
668
682
|
ctx.trace.step("disambiguate", [rItemShort(ctx, best, "halo-evidence", bestSupport)], others.map((c) => rItemShort(ctx, c, "candidate", ctx.store.prevCount(c))), `${capped.length} continuations — distributional evidence selects ` +
|
|
669
683
|
`the most corroborated (distinct contexts ${bestSupport}, ` +
|
|
670
684
|
`poured mass ${bestMass})`);
|
|
@@ -696,8 +710,6 @@ export function chooseAmong(ctx, candidates, guide) {
|
|
|
696
710
|
? { id: found.item, score: found.score }
|
|
697
711
|
: { id: candidates[0], score: -Infinity };
|
|
698
712
|
}
|
|
699
|
-
// ── Trace shim (used by chooseNext before trace module is loaded) ────────
|
|
700
|
-
import { decodeText } from "./rationale.js";
|
|
701
713
|
function rItemShort(ctx, id, role, score) {
|
|
702
714
|
return {
|
|
703
715
|
text: decodeText(read(ctx, id)),
|