@hviana/sema 0.8.9 → 0.9.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 +7 -7
- package/dist/src/alu/src/index.d.ts +1 -1
- package/dist/src/alu/src/index.js +1 -1
- package/dist/src/alu/src/parser.js +2 -6
- package/dist/src/alu/src/resonance.d.ts +13 -0
- package/dist/src/alu/src/resonance.js +41 -0
- package/dist/src/alu/test/alu.test.js +39 -0
- package/dist/src/bytes.d.ts +6 -2
- package/dist/src/bytes.js +10 -4
- package/dist/src/canon.js +44 -0
- package/dist/src/geometry.d.ts +19 -1
- package/dist/src/geometry.js +125 -141
- package/dist/src/meter.d.ts +27 -0
- package/dist/src/meter.js +28 -1
- package/dist/src/mind/articulation.js +14 -1
- package/dist/src/mind/attention.d.ts +12 -0
- package/dist/src/mind/attention.js +44 -16
- package/dist/src/mind/bridge.js +3 -3
- package/dist/src/mind/derivation.d.ts +40 -0
- package/dist/src/mind/derivation.js +34 -0
- package/dist/src/mind/graph-search.d.ts +89 -15
- package/dist/src/mind/graph-search.js +345 -174
- package/dist/src/mind/learning.js +1 -1
- package/dist/src/mind/mechanisms/cover.d.ts +19 -3
- package/dist/src/mind/mechanisms/cover.js +101 -58
- package/dist/src/mind/mechanisms/recall.js +0 -1
- package/dist/src/mind/mind.js +2 -2
- package/dist/src/mind/pipeline.d.ts +5 -1
- package/dist/src/mind/pipeline.js +175 -87
- package/dist/src/mind/primitives.d.ts +25 -5
- package/dist/src/mind/primitives.js +107 -44
- package/dist/src/mind/reasoning.d.ts +18 -4
- package/dist/src/mind/reasoning.js +445 -321
- package/dist/src/mind/recognition.js +55 -73
- package/dist/src/mind/resonance.js +1 -11
- package/dist/src/mind/traverse.d.ts +3 -3
- package/dist/src/mind/traverse.js +3 -3
- package/dist/src/mind/types.d.ts +7 -1
- package/dist/src/store-sqlite.d.ts +25 -0
- package/dist/src/store-sqlite.js +89 -1
- package/dist/src/store.d.ts +48 -4
- package/dist/src/store.js +86 -6
- package/docs/INDEX.md +18 -18
- package/docs/INVARIANTS.md +16 -16
- package/docs/architecture/bounded-reads.md +1 -1
- package/docs/architecture/caches.md +5 -4
- package/docs/architecture/closure.md +45 -5
- package/docs/architecture/cost-model.md +16 -0
- package/docs/architecture/factored-machinery.md +14 -13
- package/docs/architecture/fold-contract.md +51 -1
- package/docs/architecture/mechanism-market.md +21 -0
- package/docs/architecture/memoization.md +3 -3
- package/docs/architecture/meter.md +2 -1
- package/docs/architecture/saturation.md +12 -0
- package/docs/architecture/store.md +25 -2
- package/docs/failures/tempting-but-wrong.md +13 -2
- package/docs/harness/gates.md +12 -10
- package/docs/mechanisms/cover.md +23 -6
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/alu/README.md +10 -2
- package/src/alu/src/index.ts +1 -0
- package/src/alu/src/parser.ts +6 -6
- package/src/alu/src/resonance.ts +42 -0
- package/src/alu/test/alu.test.ts +40 -0
- package/src/bytes.ts +13 -3
- package/src/canon.ts +40 -0
- package/src/geometry.ts +183 -154
- package/src/meter.ts +28 -1
- package/src/mind/articulation.ts +14 -2
- package/src/mind/attention.ts +47 -25
- package/src/mind/bridge.ts +3 -3
- package/src/mind/derivation.ts +77 -0
- package/src/mind/graph-search.ts +449 -221
- package/src/mind/learning.ts +1 -7
- package/src/mind/match.ts +1 -2
- package/src/mind/mechanisms/cast.ts +1 -2
- package/src/mind/mechanisms/cover.ts +149 -84
- package/src/mind/mechanisms/extraction.ts +1 -2
- package/src/mind/mechanisms/prefix-completion.ts +1 -1
- package/src/mind/mechanisms/recall.ts +1 -3
- package/src/mind/mechanisms/reference.ts +1 -1
- package/src/mind/mind.ts +5 -30
- package/src/mind/pipeline.ts +206 -102
- package/src/mind/primitives.ts +119 -43
- package/src/mind/reasoning.ts +558 -413
- package/src/mind/recognition.ts +49 -65
- package/src/mind/resonance.ts +2 -16
- package/src/mind/trace.ts +1 -1
- package/src/mind/traverse.ts +3 -3
- package/src/mind/types.ts +9 -11
- package/src/store-sqlite.ts +92 -1
- package/src/store.ts +113 -7
- package/test/105-derive-through-reports-its-refusal.test.mjs +8 -5
- package/test/106-the-join-fires.test.mjs +21 -0
- package/test/111-the-cover-assembly-is-counted.test.mjs +8 -5
- package/test/128-the-leads-somewhere-pair-agrees.test.mjs +18 -12
- package/test/136-the-two-named-limits.test.mjs +3 -2
- package/test/137-the-law-lives-once-and-below.test.mjs +21 -0
- package/test/148-exact-shortcuts-agree.test.mjs +188 -0
- package/test/149-the-closure-engine.test.mjs +138 -0
- package/test/150-the-join-is-output-sensitive.test.mjs +66 -0
- package/test/151-the-cover-pays-for-what-it-reaches.test.mjs +142 -0
- package/test/152-the-read-side-names-as-the-write-side.test.mjs +146 -0
- package/test/153-a-cheaper-bound-is-looked-at-first.test.mjs +155 -0
- package/test/24-generalization.test.mjs +32 -0
- package/test/36-bloom.test.mjs +53 -0
- package/test/37-cluster-dispersion-fusion.test.mjs +75 -0
- package/test/48-recognise-turn-connective.test.mjs +3 -2
- package/test/55-cost-meter.test.mjs +4 -4
- package/test/90-connector-read-cap.test.mjs +7 -7
package/src/mind/reasoning.ts
CHANGED
|
@@ -4,26 +4,49 @@
|
|
|
4
4
|
// fuseAttention — fuse independent points of attention (multi-topic)
|
|
5
5
|
import { rItem, rNode } from "./trace.js";
|
|
6
6
|
|
|
7
|
-
import { bytesEqual, indexOf } from "../bytes.js";
|
|
7
|
+
import { bytesEqual, indexOf, latin1 } from "../bytes.js";
|
|
8
8
|
import type { Attention, MindContext } from "./types.js";
|
|
9
|
-
import { resolve } from "./primitives.js";
|
|
10
|
-
import {
|
|
9
|
+
import { read, resolve } from "./primitives.js";
|
|
10
|
+
import { countClusters } from "./attention.js";
|
|
11
|
+
import { hubBound } from "./traverse.js";
|
|
11
12
|
import { containsSpan, follow, haloSiblings, project } from "./match.js";
|
|
12
13
|
import { joinWithBridge, pivotInto } from "./resonance.js";
|
|
13
|
-
import { admissible, advance, type Continuation } from "./derivation.js";
|
|
14
|
-
import type { Precomputed } from "./pipeline-mechanism.js";
|
|
15
|
-
import { type Rationale } from "./rationale.js";
|
|
16
14
|
import {
|
|
17
|
-
|
|
15
|
+
closeOver,
|
|
16
|
+
type ClosureLayer,
|
|
17
|
+
type Continuation,
|
|
18
18
|
type DerivationState,
|
|
19
19
|
type Offer,
|
|
20
20
|
restates,
|
|
21
21
|
type Span,
|
|
22
22
|
unaccountedBytes,
|
|
23
|
+
type Witness,
|
|
23
24
|
} from "./derivation.js";
|
|
25
|
+
import type { Precomputed } from "./pipeline-mechanism.js";
|
|
26
|
+
import type { Rationale } from "./rationale.js";
|
|
24
27
|
import { STEP } from "./graph-search.js";
|
|
25
28
|
|
|
26
|
-
/**
|
|
29
|
+
/** What one ADMITTED transition did to the question, published on the meter:
|
|
30
|
+
* the bytes its witnesses CARRIED and the remainder the move DRAINED. The two
|
|
31
|
+
* transitions that go through the law — the walk's steps and the fusion —
|
|
32
|
+
* report through this one function, so the two counters have one spelling.
|
|
33
|
+
* Write-only (meter.ts contract 1). */
|
|
34
|
+
function meterTransition(
|
|
35
|
+
ctx: MindContext,
|
|
36
|
+
before: DerivationState,
|
|
37
|
+
after: DerivationState,
|
|
38
|
+
witnesses: ReadonlyArray<Witness>,
|
|
39
|
+
): void {
|
|
40
|
+
if (!ctx.meter) return;
|
|
41
|
+
for (const w of witnesses) {
|
|
42
|
+
if (w.window) ctx.meter.reasonCarriedBytes += w.window[1] - w.window[0];
|
|
43
|
+
}
|
|
44
|
+
ctx.meter.closureDrainedBytes += unaccountedBytes(before.remainder) -
|
|
45
|
+
unaccountedBytes(after.remainder);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Extend a grounded answer forward across facts (multi-hop reasoning) — the
|
|
49
|
+
* WALK, run as one layer of the closure engine ({@link walkLayer}).
|
|
27
50
|
* Pivots on the longest unconsumed learnt context each answer contains,
|
|
28
51
|
* then follows the pivot's continuation to the next fact. **The chain ends
|
|
29
52
|
* when it STOPS, never when a count runs out**: every exit is a refusal (no
|
|
@@ -38,7 +61,7 @@ import { STEP } from "./graph-search.js";
|
|
|
38
61
|
* when it declared one — see the pivot's own containment rule. `pre` is the
|
|
39
62
|
* response's shared pre-computation — the post-grounding stages read the
|
|
40
63
|
* same container the mechanisms did. */
|
|
41
|
-
export
|
|
64
|
+
export function reason(
|
|
42
65
|
ctx: MindContext,
|
|
43
66
|
query: Uint8Array,
|
|
44
67
|
d0: DerivationState,
|
|
@@ -46,33 +69,30 @@ export async function reason(
|
|
|
46
69
|
pre: Precomputed,
|
|
47
70
|
voiced: readonly Uint8Array[] = [],
|
|
48
71
|
): Promise<DerivationState> {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const uncovered = d0.remainder;
|
|
54
|
-
// Echo guard: a query that is ITSELF a learnt continuation (some context's
|
|
55
|
-
// answer) is being asked back at the system — hopping forward from it would
|
|
56
|
-
// chain through the very fact that produced it and echo the conversation
|
|
57
|
-
// back. The grounded answer alone is the honest read-out. Deliberately a
|
|
58
|
-
// broad structural gate; pinned by test/31-audit.
|
|
59
|
-
const qId = pre.queryResolved;
|
|
60
|
-
if (qId !== null && ctx.store.prevCount(qId) > 0) {
|
|
61
|
-
// THE ECHO GUARD, NAMED — the audit's point 1 could not attribute two real
|
|
62
|
-
// walks that stopped with no note at all, and this is where one of them went:
|
|
63
|
-
// the query IS a learnt continuation, so the reasoner returns the grounded
|
|
64
|
-
// read-out and never offers anything. Without this note the walk looks like
|
|
65
|
-
// an unexplained stop; with it, "no note" can only mean the offer was never
|
|
66
|
-
// asked. No decision changes: the guard returns exactly where it did.
|
|
67
|
-
ctx.trace?.step(
|
|
68
|
-
"echoGuard",
|
|
69
|
-
[rItem(query, "query")],
|
|
70
|
-
[],
|
|
71
|
-
"the query is itself a learnt continuation — answering the grounded read-out without walking",
|
|
72
|
-
);
|
|
73
|
-
return d0;
|
|
74
|
-
}
|
|
72
|
+
return closeOver(d0, query, ctx.space.maxGroup, [
|
|
73
|
+
walkLayer(ctx, query, preConsumed, pre, voiced),
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
75
76
|
|
|
77
|
+
/** The multi-hop walk as a {@link ClosureLayer}: it OFFERS a forward absorb or a
|
|
78
|
+
* pivot, the law admits. Its preamble — the two guards that say the question
|
|
79
|
+
* was already answered by the query's own position in the graph, and the
|
|
80
|
+
* pre-consumption of what the grounding spoke for — runs on the FIRST offer,
|
|
81
|
+
* against the state the engine hands it; a guard that fires makes that first
|
|
82
|
+
* offer `null`, so the walk ends where it begins and reports nothing more. */
|
|
83
|
+
export function walkLayer(
|
|
84
|
+
ctx: MindContext,
|
|
85
|
+
query: Uint8Array,
|
|
86
|
+
preConsumed: ReadonlySet<number>,
|
|
87
|
+
pre: Precomputed,
|
|
88
|
+
voiced: readonly Uint8Array[] = [],
|
|
89
|
+
): ClosureLayer {
|
|
90
|
+
/** The preamble ran (on the first offer), and whether a guard stopped it. */
|
|
91
|
+
let begun = false;
|
|
92
|
+
let stopped = false;
|
|
93
|
+
let groundedId: number | null = null;
|
|
94
|
+
let groundedPrev: number[] | null = null;
|
|
95
|
+
let startedFrom: Uint8Array = new Uint8Array(0);
|
|
76
96
|
// Consume a node and its neighbours for pivot-cycle prevention — CAPPED at
|
|
77
97
|
// the hub bound, via the store's LIMITed edge reads: a common continuation's
|
|
78
98
|
// reverse fan-in (and a hub context's forward fan-out) is corpus-sized, and
|
|
@@ -81,61 +101,9 @@ export async function reason(
|
|
|
81
101
|
// read order); a pivot suppressed only by a beyond-cap neighbour may now
|
|
82
102
|
// fire — the same visibility trade chooseNext documents.
|
|
83
103
|
const bound = hubBound(ctx);
|
|
84
|
-
|
|
85
|
-
// ANSWERED DIRECTLY — the echo guard's other half, and the same principle:
|
|
86
|
-
// the QUERY's own position in the graph, not the answer's content, says the
|
|
87
|
-
// read-out is complete. Above: the query is itself a learnt CONTINUATION.
|
|
88
|
-
// Here: the query is a learnt CONTEXT and the grounded answer is one of ITS
|
|
89
|
-
// OWN continuations. Either way the question was answered directly and there
|
|
90
|
-
// is nothing left to chain for.
|
|
91
|
-
//
|
|
92
|
-
// Every stopping condition in the loop below judges the ANSWER (`consumed` /
|
|
93
|
-
// the law's `restates` / `bytesEqual`); none asks whether the QUESTION was
|
|
94
|
-
// satisfied. So a single-hop question whose answer happens to name another
|
|
95
|
-
// learnt context extends past a correct answer and REPLACES it:
|
|
96
|
-
//
|
|
97
|
-
// asked "<subj> father"
|
|
98
|
-
// hop 1 "The father of <subj> is Ernest I of Anhalt-Dessau." <- correct
|
|
99
|
-
// pivot "Ernest I of Anhalt-Dessau" <- a learnt context too
|
|
100
|
-
// got "The date of death of Ernest I of Anhalt-Dessau is 12 June 1516."
|
|
101
|
-
//
|
|
102
|
-
// Any store holding a bare-entity context alongside a relation fact has that
|
|
103
|
-
// shape; it is not exotic.
|
|
104
|
-
//
|
|
105
|
-
// Checked ONCE, before the loop, and ahead of BOTH extension branches:
|
|
106
|
-
// `absorbForward` extends the answer too, and nothing about the defect is
|
|
107
|
-
// specific to pivoting, so a guard between them would gate one arbitrary half
|
|
108
|
-
// of the same step. Hop 0 is also the only hop at which the question can be
|
|
109
|
-
// answered directly at all — after a hop, `cur` is no longer the query's own
|
|
110
|
-
// continuation, so re-testing per hop could only cost reads.
|
|
111
|
-
//
|
|
112
|
-
// Read from the ANSWER's side (`prevFirst`) rather than the query's
|
|
113
|
-
// (`nextFirst`). Same relation, but a CONTEXT's fan-out is hub-sized while
|
|
114
|
-
// this is one answer's establishing-context fan-in. Both the resolve and the
|
|
115
|
-
// reverse read are exactly what hop 0 of the loop below would perform, so
|
|
116
|
-
// they are computed ONCE here and handed down (`groundedId`, `groundedPrev`)
|
|
117
|
-
// — the guard then costs nothing when it does not fire. Stated because the
|
|
118
|
-
// naive placement does NOT: `resolve` re-folds the answer bytes on every call
|
|
119
|
-
// (no memo) and `prevFirst` is a direct read (no memo), so a guard that
|
|
120
|
-
// recomputed them would add one fold plus one √N-bounded read per ask.
|
|
121
|
-
// The √N cap carries the file-wide visibility trade, and fails SAFE in the
|
|
122
|
-
// direction that matters: a missed guard costs an over-extended answer, never
|
|
123
|
-
// a suppressed chain.
|
|
124
|
-
//
|
|
125
|
-
// A genuine multi-hop query is not a deposited context at all ("What is the
|
|
126
|
-
// capital of the country of Eiffel Tower?" resolves to nothing), so this can
|
|
127
|
-
// never gate a real chain.
|
|
128
|
-
const groundedId = resolve(ctx, answer);
|
|
129
|
-
const groundedPrev = groundedId === null
|
|
130
|
-
? null
|
|
131
|
-
: ctx.store.prevFirst(groundedId, bound);
|
|
132
|
-
if (qId !== null && groundedPrev !== null && groundedPrev.includes(qId)) {
|
|
133
|
-
return d0;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
104
|
const consumed = new Set<number>();
|
|
137
105
|
/** `prev` lets a caller hand in an already-read reverse-edge list — hop 0
|
|
138
|
-
* reuses the guard's,
|
|
106
|
+
* reuses the guard's, below, instead of re-reading it. */
|
|
139
107
|
const consumeNode = (
|
|
140
108
|
id: number | null,
|
|
141
109
|
prev?: readonly number[],
|
|
@@ -150,27 +118,106 @@ export async function reason(
|
|
|
150
118
|
for (const n of ctx.store.nextFirst(id, bound)) consumed.add(n);
|
|
151
119
|
};
|
|
152
120
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
121
|
+
/** THE PREAMBLE — true when the walk may proceed. */
|
|
122
|
+
const begin = async (d0: DerivationState): Promise<boolean> => {
|
|
123
|
+
const answer = d0.product;
|
|
124
|
+
startedFrom = answer;
|
|
125
|
+
// Echo guard: a query that is ITSELF a learnt continuation (some context's
|
|
126
|
+
// answer) is being asked back at the system — hopping forward from it would
|
|
127
|
+
// chain through the very fact that produced it and echo the conversation
|
|
128
|
+
// back. The grounded answer alone is the honest read-out. Deliberately a
|
|
129
|
+
// broad structural gate; pinned by test/31-audit.
|
|
130
|
+
const qId = pre.queryResolved;
|
|
131
|
+
if (qId !== null && ctx.store.prevCount(qId) > 0) {
|
|
132
|
+
// THE ECHO GUARD, NAMED — the audit's point 1 could not attribute two real
|
|
133
|
+
// walks that stopped with no note at all, and this is where one of them went:
|
|
134
|
+
// the query IS a learnt continuation, so the reasoner returns the grounded
|
|
135
|
+
// read-out and never offers anything. Without this note the walk looks like
|
|
136
|
+
// an unexplained stop; with it, "no note" can only mean the offer was never
|
|
137
|
+
// asked. No decision changes: the guard returns exactly where it did.
|
|
138
|
+
ctx.trace?.step(
|
|
139
|
+
"echoGuard",
|
|
140
|
+
[rItem(query, "query")],
|
|
141
|
+
[],
|
|
142
|
+
"the query is itself a learnt continuation — answering the grounded read-out without walking",
|
|
143
|
+
);
|
|
144
|
+
return false;
|
|
167
145
|
}
|
|
146
|
+
|
|
147
|
+
// ANSWERED DIRECTLY — the echo guard's other half, and the same principle:
|
|
148
|
+
// the QUERY's own position in the graph, not the answer's content, says the
|
|
149
|
+
// read-out is complete. Above: the query is itself a learnt CONTINUATION.
|
|
150
|
+
// Here: the query is a learnt CONTEXT and the grounded answer is one of ITS
|
|
151
|
+
// OWN continuations. Either way the question was answered directly and there
|
|
152
|
+
// is nothing left to chain for.
|
|
153
|
+
//
|
|
154
|
+
// Every stopping condition in the loop below judges the ANSWER (`consumed` /
|
|
155
|
+
// the law's `restates` / `bytesEqual`); none asks whether the QUESTION was
|
|
156
|
+
// satisfied. So a single-hop question whose answer happens to name another
|
|
157
|
+
// learnt context extends past a correct answer and REPLACES it:
|
|
158
|
+
//
|
|
159
|
+
// asked "<subj> father"
|
|
160
|
+
// hop 1 "The father of <subj> is Ernest I of Anhalt-Dessau." <- correct
|
|
161
|
+
// pivot "Ernest I of Anhalt-Dessau" <- a learnt context too
|
|
162
|
+
// got "The date of death of Ernest I of Anhalt-Dessau is 12 June 1516."
|
|
163
|
+
//
|
|
164
|
+
// Any store holding a bare-entity context alongside a relation fact has that
|
|
165
|
+
// shape; it is not exotic.
|
|
166
|
+
//
|
|
167
|
+
// Checked ONCE, before the loop, and ahead of BOTH extension branches:
|
|
168
|
+
// `absorbForward` extends the answer too, and nothing about the defect is
|
|
169
|
+
// specific to pivoting, so a guard between them would gate one arbitrary half
|
|
170
|
+
// of the same step. Hop 0 is also the only hop at which the question can be
|
|
171
|
+
// answered directly at all — after a hop, `cur` is no longer the query's own
|
|
172
|
+
// continuation, so re-testing per hop could only cost reads.
|
|
173
|
+
//
|
|
174
|
+
// Read from the ANSWER's side (`prevFirst`) rather than the query's
|
|
175
|
+
// (`nextFirst`). Same relation, but a CONTEXT's fan-out is hub-sized while
|
|
176
|
+
// this is one answer's establishing-context fan-in. Both the resolve and the
|
|
177
|
+
// reverse read are exactly what hop 0 of the loop below would perform, so
|
|
178
|
+
// they are computed ONCE here and handed down (`groundedId`, `groundedPrev`)
|
|
179
|
+
// — the guard then costs nothing when it does not fire. Stated because the
|
|
180
|
+
// naive placement does NOT: `resolve` re-folds the answer bytes on every call
|
|
181
|
+
// (no memo) and `prevFirst` is a direct read (no memo), so a guard that
|
|
182
|
+
// recomputed them would add one fold plus one √N-bounded read per ask.
|
|
183
|
+
// The √N cap carries the file-wide visibility trade, and fails SAFE in the
|
|
184
|
+
// direction that matters: a missed guard costs an over-extended answer, never
|
|
185
|
+
// a suppressed chain.
|
|
186
|
+
//
|
|
187
|
+
// A genuine multi-hop query is not a deposited context at all ("What is the
|
|
188
|
+
// capital of the country of Eiffel Tower?" resolves to nothing), so this can
|
|
189
|
+
// never gate a real chain.
|
|
190
|
+
groundedId = resolve(ctx, answer);
|
|
191
|
+
groundedPrev = groundedId === null
|
|
192
|
+
? null
|
|
193
|
+
: ctx.store.prevFirst(groundedId, bound);
|
|
194
|
+
if (qId !== null && groundedPrev !== null && groundedPrev.includes(qId)) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Pre-consume whatever the grounding stage already spoke for. The halo
|
|
199
|
+
// sweep is one ANN query per node — cap it at haloQueryK sweeps (cover
|
|
200
|
+
// grounding can pre-consume one node per recognised site, O(query length));
|
|
201
|
+
// nodes past the cap are still consumed directly, they just skip the
|
|
202
|
+
// synonym expansion.
|
|
203
|
+
const preconsume = async () => {
|
|
204
|
+
let haloSweeps = 0;
|
|
205
|
+
for (const id of preConsumed) {
|
|
206
|
+
consumeNode(id);
|
|
207
|
+
if (haloSweeps >= ctx.cfg.haloQueryK) continue;
|
|
208
|
+
const h = ctx.store.halo(id);
|
|
209
|
+
if (!h) continue;
|
|
210
|
+
haloSweeps++;
|
|
211
|
+
for (const sib of await haloSiblings(ctx, id, h)) consumeNode(sib.id);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
if (ctx.meter) {
|
|
215
|
+
await ctx.meter.time("reason.preconsumeHalos", preconsume);
|
|
216
|
+
} else {
|
|
217
|
+
await preconsume();
|
|
218
|
+
}
|
|
219
|
+
return true;
|
|
168
220
|
};
|
|
169
|
-
if (ctx.meter) {
|
|
170
|
-
await ctx.meter.time("reason.preconsumeHalos", preconsume);
|
|
171
|
-
} else {
|
|
172
|
-
await preconsume();
|
|
173
|
-
}
|
|
174
221
|
|
|
175
222
|
// ── THE WALK, AS THE LAW'S ───────────────────────────────────────────
|
|
176
223
|
//
|
|
@@ -194,7 +241,6 @@ export async function reason(
|
|
|
194
241
|
// 30 s); and raising it from 12 to 200 changed neither the answer nor
|
|
195
242
|
// `pivotSteps` on the chain fixtures.
|
|
196
243
|
const qv = pre.guide; // the response-wide guide IS the query's gist
|
|
197
|
-
const W = ctx.space.maxGroup;
|
|
198
244
|
// WHOSE EXTENSION IS THIS? `voiced` is what the mechanism WITHHELD (the
|
|
199
245
|
// pipeline sends the used anchors' CONTINUATIONS, not their bytes), so a
|
|
200
246
|
// non-empty `voiced` means exactly what that note says: the grounding came
|
|
@@ -205,7 +251,6 @@ export async function reason(
|
|
|
205
251
|
// expresses as the identity species of progress — `moves` — declared by the
|
|
206
252
|
// reporter and never inferred from the mechanism's name.
|
|
207
253
|
const producerOwnsShape = voiced.length > 0;
|
|
208
|
-
const startedFrom = answer;
|
|
209
254
|
let hop = 0;
|
|
210
255
|
let t: ReturnType<Rationale["enter"]> | undefined;
|
|
211
256
|
// WHAT THE OFFERED STEP WOULD BE. The law decides, so the accepted step is
|
|
@@ -218,6 +263,13 @@ export async function reason(
|
|
|
218
263
|
| null = null;
|
|
219
264
|
|
|
220
265
|
const offer: Offer = async (d) => {
|
|
266
|
+
if (!begun) {
|
|
267
|
+
begun = true;
|
|
268
|
+
if (!(await begin(d))) {
|
|
269
|
+
stopped = true;
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
221
273
|
if (ctx.meter) ctx.meter.offerRuns += 1;
|
|
222
274
|
const cur = d.product;
|
|
223
275
|
// The first step's `cur` IS the grounding's product, so the guard above
|
|
@@ -364,65 +416,56 @@ export async function reason(
|
|
|
364
416
|
};
|
|
365
417
|
};
|
|
366
418
|
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
(before, after, witnesses)
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
ctx.meter.closureDrainedBytes += unaccountedBytes(before.remainder) -
|
|
379
|
-
unaccountedBytes(after.remainder);
|
|
380
|
-
}
|
|
381
|
-
const p = pending;
|
|
382
|
-
pending = null;
|
|
383
|
-
if (p === null) return;
|
|
384
|
-
t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
|
|
385
|
-
if (p.kind === "absorb") {
|
|
386
|
-
ctx.trace?.step(
|
|
387
|
-
"absorbForward",
|
|
388
|
-
[rItem(p.cur, "answer", p.curId ?? undefined)],
|
|
389
|
-
[rItem(p.fwd, "answer", resolve(ctx, p.fwd) ?? undefined)],
|
|
390
|
-
"the answer is itself a learnt fact — follow its continuation to the fixpoint",
|
|
391
|
-
);
|
|
392
|
-
} else {
|
|
393
|
-
if (ctx.meter) ctx.meter.pivotSteps++;
|
|
394
|
-
ctx.trace?.step(
|
|
395
|
-
"pivotStep",
|
|
396
|
-
[rItem(p.cur, "answer"), rNode(ctx, p.pivot, "pivot")],
|
|
397
|
-
[rItem(p.fc, "answer", resolve(ctx, p.fc) ?? undefined)],
|
|
398
|
-
"pivot on the shared span this answer contains, then step forward across that fact",
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
},
|
|
402
|
-
(at) => {
|
|
403
|
-
// THE LAW REFUSED — counted where it happened, before the bookkeeping
|
|
404
|
-
// below decides whether there is a step to report.
|
|
405
|
-
if (ctx.meter) ctx.meter.lawRejects += 1;
|
|
406
|
-
const p = pending;
|
|
407
|
-
pending = null;
|
|
408
|
-
if (p === null || p.kind !== "pivot") return;
|
|
409
|
-
// THE BRAKE, MADE VISIBLE — and it is now the LAW's refusal, reported
|
|
410
|
-
// where it happened. The reasoner declines a step that carries none of
|
|
411
|
-
// the material the grounding left unaccounted. A refusal that leaves no
|
|
412
|
-
// trace is the kind of silent cut AGENTS §6 forbids: the rationale is
|
|
413
|
-
// where a reader learns that an extension was declined for want of
|
|
414
|
-
// question material, and where the next person sees why the chain stopped
|
|
415
|
-
// here. Measured with the check disabled, test/110 and test/116 fail.
|
|
416
|
-
const left = unaccountedBytes(at.remainder);
|
|
419
|
+
const onTaken = (
|
|
420
|
+
before: DerivationState,
|
|
421
|
+
after: DerivationState,
|
|
422
|
+
witnesses: ReadonlyArray<Witness>,
|
|
423
|
+
): void => {
|
|
424
|
+
meterTransition(ctx, before, after, witnesses);
|
|
425
|
+
const p = pending;
|
|
426
|
+
pending = null;
|
|
427
|
+
if (p === null) return;
|
|
428
|
+
t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
|
|
429
|
+
if (p.kind === "absorb") {
|
|
417
430
|
ctx.trace?.step(
|
|
418
|
-
"
|
|
419
|
-
[rItem(p.cur, "answer"
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
`unaccounted (${left} byte(s) in ${at.remainder.length} span(s)) — refused`,
|
|
431
|
+
"absorbForward",
|
|
432
|
+
[rItem(p.cur, "answer", p.curId ?? undefined)],
|
|
433
|
+
[rItem(p.fwd, "answer", resolve(ctx, p.fwd) ?? undefined)],
|
|
434
|
+
"the answer is itself a learnt fact — follow its continuation to the fixpoint",
|
|
423
435
|
);
|
|
424
|
-
}
|
|
425
|
-
|
|
436
|
+
} else {
|
|
437
|
+
if (ctx.meter) ctx.meter.pivotSteps++;
|
|
438
|
+
ctx.trace?.step(
|
|
439
|
+
"pivotStep",
|
|
440
|
+
[rItem(p.cur, "answer"), rNode(ctx, p.pivot, "pivot")],
|
|
441
|
+
[rItem(p.fc, "answer", resolve(ctx, p.fc) ?? undefined)],
|
|
442
|
+
"pivot on the shared span this answer contains, then step forward across that fact",
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
const onRefused = (at: DerivationState): void => {
|
|
447
|
+
// THE LAW REFUSED — counted where it happened, before the bookkeeping
|
|
448
|
+
// below decides whether there is a step to report.
|
|
449
|
+
if (ctx.meter) ctx.meter.lawRejects += 1;
|
|
450
|
+
const p = pending;
|
|
451
|
+
pending = null;
|
|
452
|
+
if (p === null || p.kind !== "pivot") return;
|
|
453
|
+
// THE BRAKE, MADE VISIBLE — and it is now the LAW's refusal, reported
|
|
454
|
+
// where it happened. The reasoner declines a step that carries none of
|
|
455
|
+
// the material the grounding left unaccounted. A refusal that leaves no
|
|
456
|
+
// trace is the kind of silent cut AGENTS §6 forbids: the rationale is
|
|
457
|
+
// where a reader learns that an extension was declined for want of
|
|
458
|
+
// question material, and where the next person sees why the chain stopped
|
|
459
|
+
// here. Measured with the check disabled, test/110 and test/116 fail.
|
|
460
|
+
const left = unaccountedBytes(at.remainder);
|
|
461
|
+
ctx.trace?.step(
|
|
462
|
+
"pivotRefused",
|
|
463
|
+
[rItem(p.cur, "answer"), rItem(query, "query")],
|
|
464
|
+
at.remainder.map(([a, b]) => rItem(query.subarray(a, b), "uncovered")),
|
|
465
|
+
`the step carries none of the question material the grounding left ` +
|
|
466
|
+
`unaccounted (${left} byte(s) in ${at.remainder.length} span(s)) — refused`,
|
|
467
|
+
);
|
|
468
|
+
};
|
|
426
469
|
|
|
427
470
|
// THE WALK'S OWN ENDING, NAMED — the audit's point 1. Every stop INSIDE the
|
|
428
471
|
// offer now has a note, but a walk can also end without the offer ever being
|
|
@@ -430,68 +473,148 @@ export async function reason(
|
|
|
430
473
|
// ended as "no note at all", indistinguishable from a mechanism that never ran.
|
|
431
474
|
// Two endings cover every remaining case, and together they make a silent stop
|
|
432
475
|
// impossible — which is what the `Offer` contract needs, because `null` is read
|
|
433
|
-
// as exhaustion and exhaustion must be a statement, not an absence.
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
476
|
+
// as exhaustion and exhaustion must be a statement, not an absence. A walk a
|
|
477
|
+
// guard stopped reports nothing here: the guard already said why (or, for the
|
|
478
|
+
// answered-directly guard, the answer IS the read-out), exactly as before the
|
|
479
|
+
// walk became a layer.
|
|
480
|
+
const onEnd = (d0: DerivationState, closed_: DerivationState): void => {
|
|
481
|
+
if (stopped) return;
|
|
482
|
+
t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
|
|
483
|
+
if (closed_.remainder.length === 0) {
|
|
484
|
+
ctx.trace?.step(
|
|
485
|
+
"walkClosed",
|
|
486
|
+
[rItem(closed_.product, "answer")],
|
|
487
|
+
[],
|
|
488
|
+
"the derivation is closed — nothing is left unaccounted for",
|
|
489
|
+
);
|
|
490
|
+
} else {
|
|
491
|
+
ctx.trace?.step(
|
|
492
|
+
"walkEndedWithoutOffer",
|
|
493
|
+
[rItem(closed_.product, "answer")],
|
|
494
|
+
closed_.remainder.map(([a, b]) =>
|
|
495
|
+
rItem(query.subarray(a, b), "uncovered")
|
|
496
|
+
),
|
|
497
|
+
`the walk ended with ${
|
|
498
|
+
unaccountedBytes(closed_.remainder)
|
|
499
|
+
} byte(s) unaccounted and no further offer — no structural continuation`,
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// INSTRUMENTATION ONLY — the extension's two facts, untraced (meter.ts
|
|
504
|
+
// contract 1: a counter never reaches a decision). They are what a caller
|
|
505
|
+
// needs to PRICE the extension instead of taking it unconditionally, and both
|
|
506
|
+
// are now read off the state the law advanced rather than accumulated beside
|
|
507
|
+
// it: the work it did is the cost it accumulated, and the material it carried
|
|
508
|
+
// is the accounting the law's witnesses added.
|
|
509
|
+
const steps = closed_.cost - d0.cost;
|
|
510
|
+
if (ctx.meter) {
|
|
511
|
+
ctx.meter.reasonSteps += steps;
|
|
512
|
+
// The accounting the law's witnesses added, summed by the law's own function
|
|
513
|
+
// over the tail of the state's list — and computed ONLY when a meter is
|
|
514
|
+
// attached: this used to slice and MAP a fresh array on every response, for a
|
|
515
|
+
// counter that usually does not exist. One allocation, under the meter.
|
|
516
|
+
ctx.meter.reasonAccountedBytes += unaccountedBytes(
|
|
517
|
+
closed_.accounted.slice(d0.accounted.length),
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
t?.done(
|
|
521
|
+
[rItem(
|
|
522
|
+
closed_.product,
|
|
523
|
+
"answer",
|
|
524
|
+
resolve(ctx, closed_.product) ?? undefined,
|
|
525
|
+
)],
|
|
526
|
+
// A FIXPOINT: no further step was offered, or the law refused the one that
|
|
527
|
+
// was. There is no allowance to exhaust, so the note is true by
|
|
528
|
+
// construction.
|
|
529
|
+
"the multi-hop chain's fixpoint",
|
|
452
530
|
);
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
return { name: "reason", offer, onTaken, onRefused, onEnd };
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** In how many separate PLACES of the query a stored context shares content at
|
|
537
|
+
* window scale — the query's W-byte windows the context's bytes contain,
|
|
538
|
+
* clustered by {@link countClusters} at the same quantum. Below one window
|
|
539
|
+
* byte identity is chance (identityBar), so only whole windows count.
|
|
540
|
+
*
|
|
541
|
+
* It is the dispersion question asked of the CONTEXT rather than of the votes
|
|
542
|
+
* that happened to land on it. A region votes once, for its top anchor, so
|
|
543
|
+
* where a root's votes come from is a lossy witness of where its context and
|
|
544
|
+
* the query agree — measured on test/24's gap-3.1 fixture: the second topic's
|
|
545
|
+
* second cluster was the region `ace and `, in the OTHER topic's words,
|
|
546
|
+
* voting through a 2-byte sub-window anchor `e ` (attention.ts
|
|
547
|
+
* canonicalChunkId); once that stopped voting, the genuine topic read as one
|
|
548
|
+
* cluster. Its context still shares the system prompt AND its own wording
|
|
549
|
+
* with the query — two places, the two test/37 documents — while a
|
|
550
|
+
* coincidental echo (one closing phrase byte-identical to an unrelated
|
|
551
|
+
* conversation's end, test/37) shares one. */
|
|
552
|
+
function sharedPlaces(
|
|
553
|
+
context: Uint8Array,
|
|
554
|
+
query: Uint8Array,
|
|
555
|
+
quantum: number,
|
|
556
|
+
): number {
|
|
557
|
+
if (context.length < quantum || query.length < quantum) return 0;
|
|
558
|
+
const windows = new Set<string>();
|
|
559
|
+
for (let o = 0; o + quantum <= context.length; o++) {
|
|
560
|
+
windows.add(latin1(context.subarray(o, o + quantum)));
|
|
453
561
|
}
|
|
562
|
+
const spans: Array<[number, number]> = [];
|
|
563
|
+
for (let o = 0; o + quantum <= query.length; o++) {
|
|
564
|
+
if (windows.has(latin1(query.subarray(o, o + quantum)))) {
|
|
565
|
+
spans.push([o, o + quantum]);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return countClusters(spans, quantum);
|
|
569
|
+
}
|
|
454
570
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
);
|
|
571
|
+
/** Whether any query byte lies at least `quantum` away from every span — the
|
|
572
|
+
* only place a root independent of those spans could stand. The spans,
|
|
573
|
+
* widened by the quantum on each side, either cover [0, len) or leave a gap. */
|
|
574
|
+
function roomBeyond(
|
|
575
|
+
len: number,
|
|
576
|
+
spans: ReadonlyArray<Span>,
|
|
577
|
+
quantum: number,
|
|
578
|
+
): boolean {
|
|
579
|
+
if (spans.length === 0) return len > 0;
|
|
580
|
+
const widened = spans
|
|
581
|
+
.map(([s, e]): [number, number] => [s - quantum, e + quantum])
|
|
582
|
+
.sort((a, b) => a[0] - b[0]);
|
|
583
|
+
let reach = 0; // every byte below `reach` is within a quantum of a span
|
|
584
|
+
for (const [s, e] of widened) {
|
|
585
|
+
if (s > reach) return true;
|
|
586
|
+
reach = Math.max(reach, e);
|
|
587
|
+
if (reach >= len) return false;
|
|
471
588
|
}
|
|
472
|
-
|
|
473
|
-
[rItem(
|
|
474
|
-
closed_.product,
|
|
475
|
-
"answer",
|
|
476
|
-
resolve(ctx, closed_.product) ?? undefined,
|
|
477
|
-
)],
|
|
478
|
-
// A FIXPOINT: no further step was offered, or the law refused the one that
|
|
479
|
-
// was. There is no allowance to exhaust, so the note is true by
|
|
480
|
-
// construction.
|
|
481
|
-
"the multi-hop chain's fixpoint",
|
|
482
|
-
);
|
|
483
|
-
return closed_;
|
|
589
|
+
return reach < len;
|
|
484
590
|
}
|
|
485
591
|
|
|
486
592
|
/** Fuse independent points of attention into one answer (multi-topic).
|
|
487
593
|
* When the consensus climb finds more than one dominant point, each
|
|
488
594
|
* independent point grounds its own answer; they are bridged together
|
|
489
595
|
* by any learnt connector the graph holds between them. */
|
|
490
|
-
export
|
|
596
|
+
export function fuseAttention(
|
|
491
597
|
ctx: MindContext,
|
|
492
598
|
query: Uint8Array,
|
|
493
599
|
state: DerivationState,
|
|
494
600
|
pre: Precomputed,
|
|
601
|
+
unclimbed = false,
|
|
602
|
+
primarySpans: ReadonlyArray<Span> = [],
|
|
603
|
+
): Promise<DerivationState> {
|
|
604
|
+
return closeOver(state, query, ctx.space.maxGroup, [
|
|
605
|
+
fusionLayer(ctx, query, pre, unclimbed, primarySpans),
|
|
606
|
+
]);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/** Fusion as a {@link ClosureLayer}: ONE transition, offered once — the fused
|
|
610
|
+
* product of the independent points of attention, or nothing. The law admits
|
|
611
|
+
* it (`reaches`: the fusion composed structure this derivation had not stood
|
|
612
|
+
* on); the layer's own structural gates below decide whether there is a
|
|
613
|
+
* fusion to offer at all. */
|
|
614
|
+
export function fusionLayer(
|
|
615
|
+
ctx: MindContext,
|
|
616
|
+
query: Uint8Array,
|
|
617
|
+
pre: Precomputed,
|
|
495
618
|
/** True when `primary` never touched the consensus climb at all — e.g. a
|
|
496
619
|
* pure ALU computation, which has no anchor of its own. commitVotes
|
|
497
620
|
* ALWAYS admits the dominant root regardless of its vote (attention.ts:
|
|
@@ -506,212 +629,234 @@ export async function fuseAttention(
|
|
|
506
629
|
* fuseAttention just reads a position from it. Empty or absent preserves
|
|
507
630
|
* the original behaviour exactly. */
|
|
508
631
|
primarySpans: ReadonlyArray<Span> = [],
|
|
509
|
-
):
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
632
|
+
): ClosureLayer {
|
|
633
|
+
let offered = false;
|
|
634
|
+
const offer: Offer = async (state) => {
|
|
635
|
+
if (offered) return null;
|
|
636
|
+
offered = true;
|
|
637
|
+
// THE STATE, NOT BARE BYTES: fusion is one more transition of the derivation
|
|
638
|
+
// the walk returned, so it reads that state's product and hands back a state.
|
|
639
|
+
// Its own structural gates stay here — this is the layer that can resolve
|
|
640
|
+
// those witnesses, and the law never re-derives one.
|
|
641
|
+
const primary = state.product;
|
|
642
|
+
// When the answer is structurally drawn from the query itself
|
|
643
|
+
// (extraction), it already spans all the query's pieces — fusion
|
|
644
|
+
// would only add noise from unrelated stored contexts. The gate is
|
|
645
|
+
// STRICT containment (resolved node in the query's tree, or a contiguous
|
|
646
|
+
// byte run): the old sparse-subsequence test was trivially satisfied by
|
|
647
|
+
// short answers over long queries, silently starving multi-topic queries
|
|
648
|
+
// of fusion.
|
|
649
|
+
if (containsSpan(ctx, query, primary)) return null;
|
|
523
650
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
// With no primarySpans (the caller did not resolve them) every span
|
|
551
|
-
// vacuously qualifies, preserving the original behaviour exactly.
|
|
552
|
-
const quantum = ctx.space.maxGroup;
|
|
553
|
-
const independentOfPrimary = (root: Attention): boolean =>
|
|
554
|
-
primarySpans.every(([s, e]) => {
|
|
555
|
-
const gap = root.end <= s
|
|
556
|
-
? s - root.end
|
|
557
|
-
: e <= root.start
|
|
558
|
-
? root.start - e
|
|
559
|
-
: 0;
|
|
560
|
-
return gap >= quantum;
|
|
561
|
-
});
|
|
562
|
-
const lonePromotes = unclimbed && forest.length === 1 &&
|
|
563
|
-
forest[0].breadth > 0.5 && independentOfPrimary(forest[0]);
|
|
564
|
-
if (forest.length === 0 || (forest.length <= 1 && !lonePromotes)) {
|
|
565
|
-
return state;
|
|
566
|
-
}
|
|
651
|
+
// A SECOND POINT OF ATTENTION MUST STAND ON EVIDENCE STRUCTURALLY SEPARATE
|
|
652
|
+
// FROM PRIMARY'S: at least one perceptual quantum of query between them,
|
|
653
|
+
// the same separation countClusters uses to tell independent evidence
|
|
654
|
+
// neighbourhoods apart. Not a score, and not a tuned bar — the fold's own
|
|
655
|
+
// quantum. A root standing on primary's own evidence is primary's topic
|
|
656
|
+
// read again, never a further one, so it is not fused (measured on the
|
|
657
|
+
// 31.7M-node store: an exact dialogue turn grounded whole, `[0,79]`, paid a
|
|
658
|
+
// 3.7 s consensus climb whose only root lay inside that span).
|
|
659
|
+
//
|
|
660
|
+
// With no primarySpans (the caller did not resolve them) every span
|
|
661
|
+
// vacuously qualifies, preserving the original behaviour exactly.
|
|
662
|
+
const quantum = ctx.space.maxGroup;
|
|
663
|
+
const independentOfPrimary = (root: Attention): boolean =>
|
|
664
|
+
primarySpans.every(([s, e]) => {
|
|
665
|
+
const gap = root.end <= s
|
|
666
|
+
? s - root.end
|
|
667
|
+
: e <= root.start
|
|
668
|
+
? root.start - e
|
|
669
|
+
: 0;
|
|
670
|
+
return gap >= quantum;
|
|
671
|
+
});
|
|
672
|
+
// Decided BEFORE the climb when it is already decided: a root spans at
|
|
673
|
+
// least one query byte, so where every byte lies within a quantum of
|
|
674
|
+
// primary's evidence no root can be independent, and there is nothing the
|
|
675
|
+
// climb could hand this layer.
|
|
676
|
+
if (!roomBeyond(query.length, primarySpans, quantum)) return null;
|
|
567
677
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
// primary's own position is the earliest query byte its grounding stands
|
|
583
|
-
// on. `accounted` is the cost-ladder read of that and is authoritative
|
|
584
|
-
// when non-empty; when it is empty the grounding is a pure COMPUTATION,
|
|
585
|
-
// whose evidence is its computed span — the same cost-ladder-vs-coverage
|
|
586
|
-
// distinction think() already draws for the fusion remainder ("`accounted`
|
|
587
|
-
// alone undercounts this ... cover prices its computed spans at near-zero
|
|
588
|
-
// and deliberately leaves them out"), read here for position instead of
|
|
589
|
-
// for coverage. With neither, nothing is known and the old behaviour
|
|
590
|
-
// (forest[0].start) stands.
|
|
591
|
-
const primaryStart = primarySpans.length > 0
|
|
592
|
-
? primarySpans.reduce((m, [s]) => Math.min(m, s), Infinity)
|
|
593
|
-
: forest[0].start;
|
|
594
|
-
const pieces: Array<{ start: number; bytes: Uint8Array }> = [
|
|
595
|
-
{ start: primaryStart, bytes: primary },
|
|
596
|
-
];
|
|
597
|
-
const qv = pre.guide; // once, not per root
|
|
598
|
-
const rest = lonePromotes ? forest : forest.slice(1);
|
|
599
|
-
const t = ctx.trace?.enter("fuseAttention", [
|
|
600
|
-
rItem(primary, "primary"),
|
|
601
|
-
...rest.map((r) => rNode(ctx, r.anchor, "point", r.vote)),
|
|
602
|
-
]);
|
|
603
|
-
for (const root of rest) {
|
|
604
|
-
// DISPERSION: this root's contributing regions are confined to a
|
|
605
|
-
// single cluster (see Attention.clusters) — one local neighbourhood of
|
|
606
|
-
// the query, not several separate places. Raw region count already
|
|
607
|
-
// failed to discriminate a coincidental match from a genuine further
|
|
608
|
-
// topic (test/24 gap 3.1 vs test/35's echo); dispersion is a different
|
|
609
|
-
// question — not how MUCH evidence, but how many separate PLACES in the
|
|
610
|
-
// query corroborate it — and a coincidental match is structurally
|
|
611
|
-
// confined to one cluster no matter how strongly it resonates.
|
|
678
|
+
// The committed points of attention ARE the shared climb's roots (same
|
|
679
|
+
// query, same k, same DF mode) — read them from Precomputed instead of
|
|
680
|
+
// re-climbing, so even a traced response pays for the climb once.
|
|
681
|
+
const forest = (await pre.attention()).roots;
|
|
682
|
+
// A LONE root is ordinarily primary's own source — nothing to fuse. But
|
|
683
|
+
// when primary is unclimbed, the lone root was never checked against
|
|
684
|
+
// anything: it is admitted by commitVotes unconditionally, so it may be
|
|
685
|
+
// genuine consensus (Attention.breadth dominates — most of the query's
|
|
686
|
+
// OWN regions corroborate it) or a coincidental echo (breadth does not
|
|
687
|
+
// dominate — see test/35-attention-confidence). breadth is the SCALE-
|
|
688
|
+
// INVARIANT read of exactly this question: the raw IDF vote cannot serve
|
|
689
|
+
// here, since it is an absolute ln(N)-scaled quantity (a genuine root on
|
|
690
|
+
// a large store can score BELOW its own floor while a coincidental echo
|
|
691
|
+
// on a small one scores comfortably above its own, smaller, floor).
|
|
612
692
|
//
|
|
613
|
-
//
|
|
614
|
-
//
|
|
615
|
-
//
|
|
616
|
-
//
|
|
617
|
-
//
|
|
618
|
-
//
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
// indicates real signal — excluded only when BOTH are weak. Cheap and
|
|
624
|
-
// synchronous — checked before the async already-answered walk below.
|
|
625
|
-
if (root.clusters < 2 && root.breadth <= 0.5) {
|
|
626
|
-
ctx.trace?.step(
|
|
627
|
-
"singleCluster",
|
|
628
|
-
[rNode(ctx, root.anchor, "point", root.vote)],
|
|
629
|
-
[],
|
|
630
|
-
"this point's evidence is confined to one local neighbourhood of the query — not trusted as an independent topic",
|
|
631
|
-
);
|
|
632
|
-
continue;
|
|
693
|
+
// Breadth alone is not enough when primary is a pure COMPUTATION. The ALU
|
|
694
|
+
// answers "2+2 equals what?" with 4, and the store's own arithmetic table
|
|
695
|
+
// then supplies a lone root — an exemplar like "1+2" — whose breadth
|
|
696
|
+
// dominates because it is corroborated by the computation's OWN bytes.
|
|
697
|
+
// Fusing it projected that exemplar's continuation and the bridge voiced
|
|
698
|
+
// "4+3" (test/11 seed 99) — the separation above is what refuses it.
|
|
699
|
+
const lonePromotes = unclimbed && forest.length === 1 &&
|
|
700
|
+
forest[0].breadth > 0.5 && independentOfPrimary(forest[0]);
|
|
701
|
+
if (forest.length === 0 || (forest.length <= 1 && !lonePromotes)) {
|
|
702
|
+
return null;
|
|
633
703
|
}
|
|
634
|
-
|
|
635
|
-
//
|
|
636
|
-
//
|
|
637
|
-
//
|
|
638
|
-
//
|
|
639
|
-
//
|
|
640
|
-
//
|
|
641
|
-
//
|
|
642
|
-
//
|
|
643
|
-
//
|
|
644
|
-
//
|
|
645
|
-
// the
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
)
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
704
|
+
|
|
705
|
+
// WHERE THE QUERY ASKED FOR IT. The sort below orders the fused pieces by
|
|
706
|
+
// query position, which is the whole point of the `start` field: a
|
|
707
|
+
// multi-topic answer should read in the order the question posed its
|
|
708
|
+
// topics. Every ROOT carries its own start. `primary` did not — it was
|
|
709
|
+
// given forest[0].start, the FIRST attention root's position, which is
|
|
710
|
+
// primary's own source only when primary happens to come from that root.
|
|
711
|
+
// When it does not, primary is sorted to a position it never occupied.
|
|
712
|
+
//
|
|
713
|
+
// Observed live: "What is the capital of France? And what is 2 + 2?"
|
|
714
|
+
// answered "4The capital city of France is Paris." — the ALU result, whose
|
|
715
|
+
// evidence is the "2 + 2" span near the END of the query, inherited the
|
|
716
|
+
// France root's start of 0 and sorted ahead of the France answer. Both
|
|
717
|
+
// pieces were right; only the order was.
|
|
718
|
+
//
|
|
719
|
+
// primary's own position is the earliest query byte its grounding stands
|
|
720
|
+
// on. `accounted` is the cost-ladder read of that and is authoritative
|
|
721
|
+
// when non-empty; when it is empty the grounding is a pure COMPUTATION,
|
|
722
|
+
// whose evidence is its computed span — the same cost-ladder-vs-coverage
|
|
723
|
+
// distinction think() already draws for the fusion remainder ("`accounted`
|
|
724
|
+
// alone undercounts this ... cover prices its computed spans at near-zero
|
|
725
|
+
// and deliberately leaves them out"), read here for position instead of
|
|
726
|
+
// for coverage. With neither, nothing is known and the old behaviour
|
|
727
|
+
// (forest[0].start) stands.
|
|
728
|
+
const primaryStart = primarySpans.length > 0
|
|
729
|
+
? primarySpans.reduce((m, [s]) => Math.min(m, s), Infinity)
|
|
730
|
+
: forest[0].start;
|
|
731
|
+
const pieces: Array<{ start: number; bytes: Uint8Array }> = [
|
|
732
|
+
{ start: primaryStart, bytes: primary },
|
|
733
|
+
];
|
|
734
|
+
const qv = pre.guide; // once, not per root
|
|
735
|
+
const rest = (lonePromotes ? forest : forest.slice(1)).filter(
|
|
736
|
+
independentOfPrimary,
|
|
737
|
+
);
|
|
738
|
+
if (rest.length === 0) return null;
|
|
739
|
+
const t = ctx.trace?.enter("fuseAttention", [
|
|
740
|
+
rItem(primary, "primary"),
|
|
741
|
+
...rest.map((r) => rNode(ctx, r.anchor, "point", r.vote)),
|
|
742
|
+
]);
|
|
743
|
+
for (const root of rest) {
|
|
744
|
+
// DISPERSION: this root's contributing regions are confined to a
|
|
745
|
+
// single cluster (see Attention.clusters) — one local neighbourhood of
|
|
746
|
+
// the query, not several separate places. Raw region count already
|
|
747
|
+
// failed to discriminate a coincidental match from a genuine further
|
|
748
|
+
// topic (test/24 gap 3.1 vs test/35's echo); dispersion is a different
|
|
749
|
+
// question — not how MUCH evidence, but how many separate PLACES in the
|
|
750
|
+
// query corroborate it — and a coincidental match is structurally
|
|
751
|
+
// confined to one cluster no matter how strongly it resonates.
|
|
752
|
+
//
|
|
753
|
+
// EXCEPTION: crossRegionVotes' own joint conclusions (a query naming
|
|
754
|
+
// two attributes that were only ever learnt TOGETHER — test/34's own
|
|
755
|
+
// binding corpus) are inherently ONE fused context and are pooled from
|
|
756
|
+
// a single synthetic region, so they always read as one cluster even
|
|
757
|
+
// though they already, by construction, account for both original
|
|
758
|
+
// mentions. `breadth` (dominates — the same > half-the-query bar used
|
|
759
|
+
// everywhere else) still correctly recognises these: a genuine joint
|
|
760
|
+
// binding explains the MAJORITY of the query's regions on its own,
|
|
761
|
+
// which a coincidental echo never does (verified: test/35's echo tops
|
|
762
|
+
// out at 0.40). So a root is trusted when EITHER measure alone
|
|
763
|
+
// indicates real signal — excluded only when BOTH are weak. Cheap and
|
|
764
|
+
// synchronous — checked before the async already-answered walk below.
|
|
765
|
+
if (
|
|
766
|
+
root.clusters < 2 && root.breadth <= 0.5 &&
|
|
767
|
+
sharedPlaces(read(ctx, root.anchor), query, quantum) < 2
|
|
768
|
+
) {
|
|
769
|
+
ctx.trace?.step(
|
|
770
|
+
"singleCluster",
|
|
771
|
+
[rNode(ctx, root.anchor, "point", root.vote)],
|
|
772
|
+
[],
|
|
773
|
+
"this point's evidence is confined to one local neighbourhood of the query — not trusted as an independent topic",
|
|
774
|
+
);
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
// ALREADY ANSWERED: this root's own learnt continuation — the same
|
|
778
|
+
// content-addressed walk reason()'s echo guard already trusts
|
|
779
|
+
// (`ctx.store.prevCount(qId) > 0`), here applied per-candidate instead
|
|
780
|
+
// of to the whole query — is VERBATIM present later in the query. A
|
|
781
|
+
// query that embeds both an exchange's ask and its own already-given
|
|
782
|
+
// reply (a conversation's turn plus its own prior answer, concatenated
|
|
783
|
+
// raw by addTurn — or any caller pasting a transcript into one
|
|
784
|
+
// respond() call; the check is Mind-bookkeeping-free, so it treats both
|
|
785
|
+
// identically) has already spoken this root's answer — fusing it in
|
|
786
|
+
// again would only restate it. Deliberately NOT a magnitude measure:
|
|
787
|
+
// it fires on exact content-addressed recurrence, not on how strongly
|
|
788
|
+
// the root resonates.
|
|
789
|
+
const cont = await follow(ctx, root.anchor, qv);
|
|
790
|
+
if (
|
|
791
|
+
cont !== null && cont.length > 0 &&
|
|
792
|
+
// The law's positional reading: the caller knows the material it is
|
|
793
|
+
// looking for lies AT OR AFTER this root, which is the witness; the law
|
|
794
|
+
// owns the containment test itself. The `cont.length > 0` guard stays
|
|
795
|
+
// here because an EMPTY continuation indexes at every offset — the law
|
|
796
|
+
// has no opinion about whether "nothing" counts as said.
|
|
797
|
+
restates(query, cont, 0, { from: root.end })
|
|
798
|
+
) {
|
|
799
|
+
ctx.trace?.step(
|
|
800
|
+
"alreadyAnswered",
|
|
801
|
+
[rNode(ctx, root.anchor, "point", root.vote)],
|
|
802
|
+
[rItem(cont, "continuation")],
|
|
803
|
+
"this point's own learnt continuation already appears later in the query — already answered, not fused",
|
|
804
|
+
);
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
const g = await project(ctx, root.anchor, qv);
|
|
808
|
+
if (g === null || g.length === 0) continue;
|
|
809
|
+
if (pieces.some((p) => indexOf(p.bytes, g, 0) >= 0)) continue;
|
|
810
|
+
pieces.push({ start: root.start, bytes: g });
|
|
811
|
+
}
|
|
812
|
+
if (pieces.length === 1) {
|
|
813
|
+
t?.done(
|
|
814
|
+
[rItem(primary, "answer")],
|
|
815
|
+
"no further independent point grounded",
|
|
661
816
|
);
|
|
662
|
-
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
pieces.sort((a, b) => a.start - b.start);
|
|
821
|
+
let out = pieces[0].bytes;
|
|
822
|
+
for (let i = 1; i < pieces.length; i++) {
|
|
823
|
+
// An approximate-resonance miss (or a genuinely unlearnt junction) joins
|
|
824
|
+
// the pieces bare — joinWithBridge surfaces it as a bridgeMiss step.
|
|
825
|
+
out = await joinWithBridge(ctx, out, pieces[i].bytes);
|
|
663
826
|
}
|
|
664
|
-
const g = await project(ctx, root.anchor, qv);
|
|
665
|
-
if (g === null || g.length === 0) continue;
|
|
666
|
-
if (pieces.some((p) => indexOf(p.bytes, g, 0) >= 0)) continue;
|
|
667
|
-
pieces.push({ start: root.start, bytes: g });
|
|
668
|
-
}
|
|
669
|
-
if (pieces.length === 1) {
|
|
670
827
|
t?.done(
|
|
671
|
-
[rItem(
|
|
672
|
-
|
|
828
|
+
[rItem(out, "answer", resolve(ctx, out) ?? undefined)],
|
|
829
|
+
`fused ${pieces.length} independent points of attention into one answer`,
|
|
673
830
|
);
|
|
674
|
-
return
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
//
|
|
681
|
-
//
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
831
|
+
// THE FACT IS THE FUSED ANSWER, not the call: every early return above hands
|
|
832
|
+
// back `primary` untouched. Untraced on purpose (meter.ts contract 1).
|
|
833
|
+
if (ctx.meter) ctx.meter.fuseRuns++;
|
|
834
|
+
// A FUSION IS A TRANSITION, and the only one in the engine that can splice the
|
|
835
|
+
// QUESTION'S OWN material into the product. So it is OFFERED to the law rather
|
|
836
|
+
// than built by hand: the law reads the window the fused product holds — the
|
|
837
|
+
// same reading the carries admission uses, one definition — accounts for the
|
|
838
|
+
// span it carried, and lets the question's remainder drain on EVIDENCE. A
|
|
839
|
+
// fusion that carries nothing consumes nothing, because the reading finds no
|
|
840
|
+
// window; and `reaches` is declared only when the fusion composed another root,
|
|
841
|
+
// which is structure this derivation had not stood on.
|
|
842
|
+
//
|
|
843
|
+
// THE LAW'S REFUSAL IS FINAL. The only state it refuses a fusion against is a
|
|
844
|
+
// supplied fixed point (`reaches` is always declared here), and the engine
|
|
845
|
+
// enters no layer for one — so there is no hand-built fallback: a fusion the
|
|
846
|
+
// law refuses is not taken (measured: no response in the suite reaches the
|
|
847
|
+
// refusal).
|
|
848
|
+
const fusedT: Continuation = {
|
|
849
|
+
product: out,
|
|
850
|
+
contains: true,
|
|
851
|
+
reaches: rest.length > 0,
|
|
852
|
+
cost: STEP,
|
|
853
|
+
};
|
|
854
|
+
return fusedT;
|
|
855
|
+
};
|
|
856
|
+
return {
|
|
857
|
+
name: "fuse",
|
|
858
|
+
offer,
|
|
859
|
+
onTaken: (before, after, witnesses) =>
|
|
860
|
+
meterTransition(ctx, before, after, witnesses),
|
|
704
861
|
};
|
|
705
|
-
const fusedWitness = admissible(state, fusedT, query, ctx.space.maxGroup);
|
|
706
|
-
if (fusedWitness === null) return { ...state, product: out };
|
|
707
|
-
const fused = advance(state, fusedT, fusedWitness);
|
|
708
|
-
if (ctx.meter) {
|
|
709
|
-
ctx.meter.reasonCarriedBytes += fusedWitness.reduce(
|
|
710
|
-
(n, w) => n + (w.window ? w.window[1] - w.window[0] : 0),
|
|
711
|
-
0,
|
|
712
|
-
);
|
|
713
|
-
ctx.meter.closureDrainedBytes += unaccountedBytes(state.remainder) -
|
|
714
|
-
unaccountedBytes(fused.remainder);
|
|
715
|
-
}
|
|
716
|
-
return fused;
|
|
717
862
|
}
|