@hviana/sema 0.6.0 → 0.7.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/.github/workflows/release.yml +80 -0
- package/AGENTS.md +53 -9
- package/dist/src/meter.d.ts +1 -4
- package/dist/src/meter.js +0 -3
- package/dist/src/mind/attention.js +22 -20
- package/dist/src/mind/graph-search.d.ts +43 -9
- package/dist/src/mind/graph-search.js +82 -15
- package/dist/src/mind/junction.d.ts +13 -0
- package/dist/src/mind/junction.js +13 -0
- package/dist/src/mind/mechanisms/cover.js +23 -2
- package/dist/src/mind/mechanisms/prefix-completion.js +13 -11
- package/dist/src/mind/mechanisms/recall.js +8 -4
- package/dist/src/mind/pipeline-mechanism.d.ts +0 -24
- package/dist/src/mind/pipeline-mechanism.js +13 -36
- package/dist/src/mind/pipeline.d.ts +23 -0
- package/dist/src/mind/pipeline.js +51 -3
- package/dist/src/mind/recognition.d.ts +6 -1
- package/dist/src/mind/recognition.js +11 -6
- package/dist/src/mind/resonance.js +48 -13
- package/dist/src/store.js +22 -1
- package/jsr.json +1 -1
- package/package.json +7 -2
- package/src/meter.ts +1 -4
- package/src/mind/attention.ts +22 -19
- package/src/mind/graph-search.ts +93 -16
- package/src/mind/junction.ts +13 -0
- package/src/mind/mechanisms/cover.ts +23 -4
- package/src/mind/mechanisms/prefix-completion.ts +13 -11
- package/src/mind/mechanisms/recall.ts +8 -4
- package/src/mind/pipeline-mechanism.ts +13 -42
- package/src/mind/pipeline.ts +87 -3
- package/src/mind/recognition.ts +19 -6
- package/src/mind/resonance.ts +79 -50
- package/src/store.ts +21 -1
- package/test/89-completion-recursion.test.mjs +230 -0
- package/test/90-connector-read-cap.test.mjs +130 -0
- package/test/91-branch-bytes-cache.test.mjs +152 -0
- package/test/93-regime-prediction.test.mjs +148 -0
- package/test/94-cross-region-budget.test.mjs +67 -0
- package/test/95-wide-resonance-removed.test.mjs +109 -0
|
@@ -306,10 +306,14 @@ export async function recallByResonance(ctx, query, pre) {
|
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
// 3b. Corroborated-substitution bridge — refusal-path only (bridge.ts).
|
|
309
|
-
// The
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
|
|
309
|
+
// The bridge's proposal source is the response's ONE top-k read — the same
|
|
310
|
+
// list recall already ranked above — never an exhaustive √N scan. The
|
|
311
|
+
// bridge's own candidate cap is 2·recallQueryK, so top-k proposals are
|
|
312
|
+
// exactly the budget it can consume, and every proposal is byte-verified
|
|
313
|
+
// downstream (§2.3). Reuse the memoised `resonance()`; scanning every IVF
|
|
314
|
+
// cluster here once made every honest refusal cost hundreds of ms regardless
|
|
315
|
+
// of k.
|
|
316
|
+
const wideIds = async () => (await pre.resonance()).map((h) => h.id);
|
|
313
317
|
// Every gist-based tier has failed; before refusing, align the query
|
|
314
318
|
// byte-for-byte against the trained contexts its own stored windows
|
|
315
319
|
// anchor, accepting mismatches only as corpus-attested, concept-bar
|
|
@@ -63,30 +63,6 @@ export declare class Precomputed {
|
|
|
63
63
|
* duplication a profile shows as doubled `annVectorReads` with nothing to
|
|
64
64
|
* account for it. Cached BY PROMISE, so a second caller awaits the first. */
|
|
65
65
|
resonance(): Promise<ReadonlyArray<Hit>>;
|
|
66
|
-
private _wide?;
|
|
67
|
-
/** The response's WIDE candidate list — the top-k when the query's gist has
|
|
68
|
-
* no concept-level match anywhere, and an exhaustive √N read when it does.
|
|
69
|
-
*
|
|
70
|
-
* Every mechanism that has to look PAST the top-k reads this one list: the
|
|
71
|
-
* substitution bridge, prefix completion and the frame filler all did, and
|
|
72
|
-
* it was memoised inside recall for exactly that reason (measured: 490 ms
|
|
73
|
-
* median re-issued against 13 ms non-exhaustive, 36x). A memo inside one
|
|
74
|
-
* mechanism only serves that mechanism's own tiers, so it lives here now —
|
|
75
|
-
* the same move `resonance` made for the top-k.
|
|
76
|
-
*
|
|
77
|
-
* THE CONDITION IS THE TOP HIT'S SCORE, NOT THE CORPUS SIZE. When nothing
|
|
78
|
-
* ranks at concept level, an exhaustive ANN only scores more vectors below
|
|
79
|
-
* the bar (profiled at 38K–40K annVectorReads per refusing query on a 325K-
|
|
80
|
-
* context store); the structural channels — junction walks, anchor climbs,
|
|
81
|
-
* the write side's window index — are the correct proposal source there,
|
|
82
|
-
* because the ANN cannot propose what the gist cannot rank. This was once
|
|
83
|
-
* spelled `corpusN(ctx) <= (k · W)³`, which asks a different question and
|
|
84
|
-
* answers it wrongly at exactly the scale it was written from: at N =
|
|
85
|
-
* 325,608 with k = 24 and W = 4 the cube is 884,736, so that store took the
|
|
86
|
-
* exhaustive branch — the very branch measured above. Measured cost of the
|
|
87
|
-
* mismatch: substitutionBridge 8,544 ms of a 19,548 ms think (44%), against
|
|
88
|
-
* 1,248 ms and 14,218 ms without it, every answer byte-identical. */
|
|
89
|
-
wideResonance(): Promise<ReadonlyArray<number>>;
|
|
90
66
|
private _frames?;
|
|
91
67
|
/** THE FRAME INVENTORY — every ranked candidate that reads as an instance of
|
|
92
68
|
* the same frame as the query, each with the query spans it leaves VARIABLE
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
// 4. TRAVELING EVIDENCE — run() returns MechanismResult with accounted, moves,
|
|
14
14
|
// and unexplained. The pipeline computes the weight.
|
|
15
15
|
import { indexOf } from "../bytes.js";
|
|
16
|
-
import {
|
|
16
|
+
import { dominates } from "../geometry.js";
|
|
17
17
|
import { windowIds } from "./canonical.js";
|
|
18
18
|
import { read, resolve } from "./primitives.js";
|
|
19
19
|
import { alignGraded, frameSlots, skillExemplar, } from "./match.js";
|
|
20
20
|
import { climbAttentionAll } from "./attention.js";
|
|
21
|
-
import {
|
|
21
|
+
import { sharedReachMemo } from "./traverse.js";
|
|
22
22
|
// ── Precomputed ──────────────────────────────────────────────────────────────
|
|
23
23
|
//
|
|
24
24
|
// Precomputed is a LAZY container for structural analyses of the query — the
|
|
@@ -128,40 +128,17 @@ export class Precomputed {
|
|
|
128
128
|
resonance() {
|
|
129
129
|
return this._resonance ??= this.shared("resonance", () => this.ctx.store.resonate(this.guide, this.k));
|
|
130
130
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
* THE CONDITION IS THE TOP HIT'S SCORE, NOT THE CORPUS SIZE. When nothing
|
|
143
|
-
* ranks at concept level, an exhaustive ANN only scores more vectors below
|
|
144
|
-
* the bar (profiled at 38K–40K annVectorReads per refusing query on a 325K-
|
|
145
|
-
* context store); the structural channels — junction walks, anchor climbs,
|
|
146
|
-
* the write side's window index — are the correct proposal source there,
|
|
147
|
-
* because the ANN cannot propose what the gist cannot rank. This was once
|
|
148
|
-
* spelled `corpusN(ctx) <= (k · W)³`, which asks a different question and
|
|
149
|
-
* answers it wrongly at exactly the scale it was written from: at N =
|
|
150
|
-
* 325,608 with k = 24 and W = 4 the cube is 884,736, so that store took the
|
|
151
|
-
* exhaustive branch — the very branch measured above. Measured cost of the
|
|
152
|
-
* mismatch: substitutionBridge 8,544 ms of a 19,548 ms think (44%), against
|
|
153
|
-
* 1,248 ms and 14,218 ms without it, every answer byte-identical. */
|
|
154
|
-
wideResonance() {
|
|
155
|
-
return this._wide ??= this.shared("wideResonance", async () => {
|
|
156
|
-
const hits = await this.resonance();
|
|
157
|
-
if (hits.length > 0 &&
|
|
158
|
-
hits[0].score >= conceptThreshold(this.ctx.store.D)) {
|
|
159
|
-
const exhaustive = await this.ctx.store.resonate(this.guide, hubBound(this.ctx), true);
|
|
160
|
-
return exhaustive.map((h) => h.id);
|
|
161
|
-
}
|
|
162
|
-
return hits.map((h) => h.id);
|
|
163
|
-
});
|
|
164
|
-
}
|
|
131
|
+
// REMOVED — the WIDE exhaustive-√N resonance list (`wideResonance`). It ran
|
|
132
|
+
// `resonate(guide, √N, exhaustive=true)` whenever the top hit cleared
|
|
133
|
+
// conceptThreshold, so consumers could look "past the top-k". Every consumer
|
|
134
|
+
// only ever needed ≤ 2·recallQueryK proposals (the substitution bridge's own
|
|
135
|
+
// candidate cap) or a content-addressed answer (prefix completion's
|
|
136
|
+
// formsOpenedBy), and every proposal is byte-verified downstream (§2.3), so
|
|
137
|
+
// the exhaustive scan bought recall at O(index) cost for an O(k) need —
|
|
138
|
+
// measured: 244K annVectorReads per refusing query, ~1.5 s, every answer
|
|
139
|
+
// byte-identical to a top-k read. The two consumers now read `resonance()`
|
|
140
|
+
// (the one top-k read) and the write side's window index respectively — see
|
|
141
|
+
// recall.ts and prefix-completion.ts.
|
|
165
142
|
_frames;
|
|
166
143
|
/** THE FRAME INVENTORY — every ranked candidate that reads as an instance of
|
|
167
144
|
* the same frame as the query, each with the query spans it leaves VARIABLE
|
|
@@ -37,6 +37,29 @@ export interface NarrowDecisionData {
|
|
|
37
37
|
version: 1;
|
|
38
38
|
margin: number;
|
|
39
39
|
}
|
|
40
|
+
/** Structured payload of the "regimePrediction" rationale step — the R8
|
|
41
|
+
* observation exposed as data. After the first mechanism (cover, which §2.6
|
|
42
|
+
* runs first) grounds or abstains, the market's whole outcome is already
|
|
43
|
+
* determined by the one cost ladder: the consensus climb runs exactly when
|
|
44
|
+
* `worthRunning(2 * STEP)` is true — CAST (floor 2·STEP) is the cheapest
|
|
45
|
+
* mechanism that first-touches it, and confluence (3·STEP) / extraction
|
|
46
|
+
* (CONCEPT+STEP) are only reached after CAST is. An incumbent at or below
|
|
47
|
+
* that floor prunes CAST and, with it, the climb (retrieval); anything above
|
|
48
|
+
* — or no incumbent — runs the full market and the climb (composition).
|
|
49
|
+
* Purely observational; never read by inference. */
|
|
50
|
+
export interface RegimePredictionData {
|
|
51
|
+
version: 1;
|
|
52
|
+
/** retrieval | composition — the two regimes R1 measured as a ~100× cost
|
|
53
|
+
* step. */
|
|
54
|
+
regime: "retrieval" | "composition";
|
|
55
|
+
/** The incumbent's grade right after the first mechanism ran, or null when
|
|
56
|
+
* it grounded nothing (best === null — composition, with no incumbent). */
|
|
57
|
+
incumbentGrade: number | null;
|
|
58
|
+
/** The cheapest composition floor in grade units (`grade(2 * STEP)` = 2,
|
|
59
|
+
* CAST's floor) — the bar the incumbent must sit at or below for the
|
|
60
|
+
* consensus climb to be skipped. */
|
|
61
|
+
climbFloorGrade: number;
|
|
62
|
+
}
|
|
40
63
|
/** Think: a single lightest-derivation exploration of the Sema graph.
|
|
41
64
|
*
|
|
42
65
|
* Every answer travels the same path:
|
|
@@ -92,9 +92,18 @@ export async function think(ctx, query, mechs) {
|
|
|
92
92
|
};
|
|
93
93
|
// ── Pre-computation ──────────────────────────────────────────────────
|
|
94
94
|
const mechanisms = mechs ?? defaultMechanisms;
|
|
95
|
-
const
|
|
95
|
+
const meter = ctx.meter;
|
|
96
|
+
// recognition is a shared analysis (§2.14 contract 5): it does the query's
|
|
97
|
+
// own store work (perceive → foldTree → resolve), which used to land in
|
|
98
|
+
// `think` and in nothing narrower — the meter's one accounting surface must
|
|
99
|
+
// charge it to itself, exactly as attention/weave/resonance are charged.
|
|
100
|
+
const rec = meter
|
|
101
|
+
? await meter.time("recognise", async () => recognise(ctx, query))
|
|
102
|
+
: recognise(ctx, query);
|
|
96
103
|
// Phase 1: collect computed spans from mechanisms that implement parse()
|
|
97
|
-
const computed =
|
|
104
|
+
const computed = meter
|
|
105
|
+
? await meter.time("collectComputed", () => collectComputed(ctx, mechanisms, query))
|
|
106
|
+
: await collectComputed(ctx, mechanisms, query);
|
|
98
107
|
if (computed.length > 0) {
|
|
99
108
|
ctx.trace?.step("computeExtensions", [rItem(query, "query")], computed.map((u) => rItem(query.subarray(u.i, u.j), "operand", undefined, [u.i, u.j])), `extensions recognised and evaluated ${computed.length} computation(s)`);
|
|
100
109
|
for (const u of computed) {
|
|
@@ -107,6 +116,9 @@ export async function think(ctx, query, mechs) {
|
|
|
107
116
|
// method on Precomputed, first-touched by whichever mechanism's floor
|
|
108
117
|
// survives its cheap gates and the worthRunning check. A query no
|
|
109
118
|
// mechanism climbs for (e.g. one an extension decided) never climbs.
|
|
119
|
+
// NOT phased: the constructor itself is trivial (it only derives `k`), so a
|
|
120
|
+
// phase here would add a zero-work entry to every profiled report — the meter
|
|
121
|
+
// attributes WORK (§2.14); the trace already represents structure.
|
|
110
122
|
const pre = new Precomputed(ctx, query, rec, computed, ctx._edgeGuide);
|
|
111
123
|
const grade = (w) => Math.floor(w / STEP);
|
|
112
124
|
const unaccounted = (spans) => unexplainedSpans(query.length, spans)
|
|
@@ -155,7 +167,7 @@ export async function think(ctx, query, mechs) {
|
|
|
155
167
|
// Per-mechanism accounting (src/meter.ts). The market's whole premise is
|
|
156
168
|
// that mechanisms compete on one cost scale — so the profiling read-out is
|
|
157
169
|
// also per-mechanism, uniformly: the loop never asks which one it holds.
|
|
158
|
-
|
|
170
|
+
let regimeReported = false;
|
|
159
171
|
for (const mech of mechanisms) {
|
|
160
172
|
const floor = meter
|
|
161
173
|
? await meter.time(`${mech.name}.floor`, () => mech.floor(ctx, query, pre, worthRunning))
|
|
@@ -192,6 +204,42 @@ export async function think(ctx, query, mechs) {
|
|
|
192
204
|
scaffolding: r.scaffolding,
|
|
193
205
|
});
|
|
194
206
|
}
|
|
207
|
+
// REGIME PREDICTION (R8) — observational only. After the FIRST mechanism
|
|
208
|
+
// runs (cover, which §2.6 places first and floors at 0), the market's
|
|
209
|
+
// outcome is already determined: the consensus climb runs exactly when
|
|
210
|
+
// `worthRunning(2 * STEP)` is true — CAST (floor 2·STEP) is the cheapest
|
|
211
|
+
// mechanism that first-touches it, so an incumbent at or below grade 2
|
|
212
|
+
// prunes CAST and, with it, confluence (3·STEP) and extraction
|
|
213
|
+
// (CONCEPT+STEP) (retrieval); anything above — or no incumbent — runs the
|
|
214
|
+
// full market and the climb (composition). The predicate is
|
|
215
|
+
// `worthRunning`, the same function the loop just used — nothing is
|
|
216
|
+
// computed here that the engine had not already computed, and nothing is
|
|
217
|
+
// read back by inference.
|
|
218
|
+
if (!regimeReported) {
|
|
219
|
+
regimeReported = true;
|
|
220
|
+
const climbFloorGrade = grade(2 * STEP);
|
|
221
|
+
// TS narrows `best` to null in the outer flow (it cannot see the closure
|
|
222
|
+
// assignments in `consider`) — cast back, the same read-back as `decided`
|
|
223
|
+
// below.
|
|
224
|
+
const incumbent = best;
|
|
225
|
+
const incumbentGrade = incumbent === null
|
|
226
|
+
? null
|
|
227
|
+
: grade(incumbent.weight);
|
|
228
|
+
const regime = worthRunning(2 * STEP)
|
|
229
|
+
? "composition"
|
|
230
|
+
: "retrieval";
|
|
231
|
+
ctx.trace?.step("regimePrediction", [rItem(query, "query")], [], regime === "retrieval"
|
|
232
|
+
? `retrieval regime — incumbent grade ${incumbentGrade} ≤ climb floor ${climbFloorGrade}, so no composition mechanism runs; ` +
|
|
233
|
+
`the consensus climb will not run`
|
|
234
|
+
: `composition regime — ${incumbentGrade === null
|
|
235
|
+
? "no incumbent (nothing grounded)"
|
|
236
|
+
: `incumbent grade ${incumbentGrade}`} above climb floor ${climbFloorGrade}, so the full market and climb run`, undefined, {
|
|
237
|
+
version: 1,
|
|
238
|
+
regime,
|
|
239
|
+
incumbentGrade,
|
|
240
|
+
climbFloorGrade,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
195
243
|
}
|
|
196
244
|
// (TS cannot see the closure assignments into `best` and narrows it to its
|
|
197
245
|
// initial null, so the read-back needs the assertion.)
|
|
@@ -11,7 +11,12 @@ import type { MindContext, Recognition, Segment } from "./types.js";
|
|
|
11
11
|
* query's own cut cannot, and records sub-leaf boundaries as `splits`.
|
|
12
12
|
*
|
|
13
13
|
* Both O(n · maxGroup) bounded O(1) probes — never a scan of the corpus. */
|
|
14
|
-
|
|
14
|
+
/** Decompose `bytes` into the learnt forms it contains. `trimmed` skips the
|
|
15
|
+
* edge-trim fallbacks (which recover misaligned FRAGMENTS) — for callers whose
|
|
16
|
+
* own gate rejects fragments anyway (the pivot), so the O(n·W²) trim search is
|
|
17
|
+
* paid only where its output can be used. Byte-identical for every caller
|
|
18
|
+
* that keeps only top-level forms. */
|
|
19
|
+
export declare function recognise(ctx: MindContext, bytes: Uint8Array, trimmed?: boolean): Recognition;
|
|
15
20
|
/** Segment bytes using the geometry's own groupings — leaf-parent
|
|
16
21
|
* nodes from the perceived tree, with consecutive bare leaves merged
|
|
17
22
|
* into one segment. Each segment's gist is perceived from its bytes
|
|
@@ -22,7 +22,12 @@ import { isChunk } from "../sema.js";
|
|
|
22
22
|
* query's own cut cannot, and records sub-leaf boundaries as `splits`.
|
|
23
23
|
*
|
|
24
24
|
* Both O(n · maxGroup) bounded O(1) probes — never a scan of the corpus. */
|
|
25
|
-
|
|
25
|
+
/** Decompose `bytes` into the learnt forms it contains. `trimmed` skips the
|
|
26
|
+
* edge-trim fallbacks (which recover misaligned FRAGMENTS) — for callers whose
|
|
27
|
+
* own gate rejects fragments anyway (the pivot), so the O(n·W²) trim search is
|
|
28
|
+
* paid only where its output can be used. Byte-identical for every caller
|
|
29
|
+
* that keeps only top-level forms. */
|
|
30
|
+
export function recognise(ctx, bytes, trimmed = false) {
|
|
26
31
|
// Content-keyed memo — works for both single-turn respond() and multi-turn
|
|
27
32
|
// respondTurn() (where the map persists across calls). ALWAYS consulted,
|
|
28
33
|
// regardless of tracing — matching perceive()'s own memo, which carries no
|
|
@@ -62,7 +67,7 @@ export function recognise(ctx, bytes) {
|
|
|
62
67
|
// not silent), so it is emitted here directly rather than only inside
|
|
63
68
|
// recogniseImpl.
|
|
64
69
|
if (ctx.recogniseMemo) {
|
|
65
|
-
const key = latin1Key(bytes);
|
|
70
|
+
const key = (trimmed ? "t" : "f") + latin1Key(bytes);
|
|
66
71
|
const hit = ctx.recogniseMemo.get(key);
|
|
67
72
|
if (hit !== undefined) {
|
|
68
73
|
if (ctx.meter)
|
|
@@ -74,13 +79,13 @@ export function recognise(ctx, bytes) {
|
|
|
74
79
|
`lead somewhere (over ${hit.leaves.length} perceived leaves) [cached]`);
|
|
75
80
|
return hit;
|
|
76
81
|
}
|
|
77
|
-
const fresh = recogniseImpl(ctx, bytes);
|
|
82
|
+
const fresh = recogniseImpl(ctx, bytes, trimmed);
|
|
78
83
|
ctx.recogniseMemo.set(key, fresh);
|
|
79
84
|
return fresh;
|
|
80
85
|
}
|
|
81
|
-
return recogniseImpl(ctx, bytes);
|
|
86
|
+
return recogniseImpl(ctx, bytes, trimmed);
|
|
82
87
|
}
|
|
83
|
-
function recogniseImpl(ctx, bytes) {
|
|
88
|
+
function recogniseImpl(ctx, bytes, trimmed = false) {
|
|
84
89
|
if (ctx.meter) {
|
|
85
90
|
ctx.meter.recognitions++;
|
|
86
91
|
ctx.meter.recognisedBytes += bytes.length;
|
|
@@ -194,7 +199,7 @@ function recogniseImpl(ctx, bytes) {
|
|
|
194
199
|
// n.kids !== null enforces above) rather than degenerate into
|
|
195
200
|
// single-byte-atom territory, which atomIsHub already governs
|
|
196
201
|
// separately.
|
|
197
|
-
else if (end - start - 1 >= 2) {
|
|
202
|
+
else if (!trimmed && end - start - 1 >= 2) {
|
|
198
203
|
// The chunk's own boundary is drawn by content geometry, not by
|
|
199
204
|
// any notion of "form" — it can include one edge byte the query's
|
|
200
205
|
// fold happened to attach here that the trained span never had
|
|
@@ -9,7 +9,7 @@ import { mergeThreshold } from "../geometry.js";
|
|
|
9
9
|
import { concat2, concatBytes, indexOf } from "../bytes.js";
|
|
10
10
|
import { gistOf, read, resolve, walkTree } from "./primitives.js";
|
|
11
11
|
import { perceive } from "./primitives.js";
|
|
12
|
-
import {
|
|
12
|
+
import { argmaxCosine, candidateGist, hubBound } from "./traverse.js";
|
|
13
13
|
import { cachedRead, junctionContainers, junctionSynonyms, walkCache, } from "./junction.js";
|
|
14
14
|
import { recognise } from "./recognition.js";
|
|
15
15
|
// ── The bridge — the junction between two adjacent results ──────────────────
|
|
@@ -288,7 +288,13 @@ export async function pivotInto(ctx, answer, consumed, voiced = []) {
|
|
|
288
288
|
for (const c of n.kids)
|
|
289
289
|
queue.push(c); // breadth-first: larger regions first
|
|
290
290
|
}
|
|
291
|
-
|
|
291
|
+
// TRIMMED recognition: the pivot's own filter below rejects fragments
|
|
292
|
+
// (`hasParents || hasContainers → -Infinity`), and recognition's edge-trim
|
|
293
|
+
// fallbacks exist to find exactly those misaligned FRAGMENTS. Skipping them
|
|
294
|
+
// (the structural pass + canonResolve still run) is byte-identical for every
|
|
295
|
+
// pivot — the fallbacks' output is discarded by the filter — and halves the
|
|
296
|
+
// O(n·W²) recognition of a long answer (measured: 36KB recognise 4.0s → 2.0s).
|
|
297
|
+
const rec = recognise(ctx, answer, true);
|
|
292
298
|
for (const s of rec.sites) {
|
|
293
299
|
if (!consumed.has(s.payload) && ctx.store.hasNext(s.payload)) {
|
|
294
300
|
scored.set(s.payload, Math.max(scored.get(s.payload) ?? 0, 1));
|
|
@@ -296,7 +302,26 @@ export async function pivotInto(ctx, answer, consumed, voiced = []) {
|
|
|
296
302
|
}
|
|
297
303
|
// Byte containment, longest wins — the answer literally contains the
|
|
298
304
|
// pivot's bytes, and the biggest well-evidenced span is the real pivot.
|
|
299
|
-
|
|
305
|
+
//
|
|
306
|
+
// REAL SATURATION, not a hard cap: the score IS the candidate's byte
|
|
307
|
+
// length, so the scan is DECIDED the moment the first candidate that passes
|
|
308
|
+
// every filter is found in DESCENDING length order — a shorter candidate can
|
|
309
|
+
// never outscore it. `contentLen` (the prefix-capped length read, §2.8) is
|
|
310
|
+
// the cheap ordering key, and the first-inserted tie-break is made explicit
|
|
311
|
+
// (`a.index - b.index`) so equal lengths keep `scored`'s insertion order —
|
|
312
|
+
// exactly the tie argmaxBy(strict) used to keep. The bytes of at most ONE
|
|
313
|
+
// winning candidate are read; every shorter candidate the probes proposed is
|
|
314
|
+
// skipped without reconstruction, where the old argmax read them all.
|
|
315
|
+
const ranked = [...scored.keys()]
|
|
316
|
+
.map((id, index) => ({
|
|
317
|
+
id,
|
|
318
|
+
index,
|
|
319
|
+
len: ctx.store.contentLen(id, answer.length + 1),
|
|
320
|
+
}))
|
|
321
|
+
.sort((a, b) => b.len - a.len || a.index - b.index);
|
|
322
|
+
let pivotId = null;
|
|
323
|
+
for (const c of ranked) {
|
|
324
|
+
const id = c.id;
|
|
300
325
|
// A PIVOT MUST BE A THING THE CORPUS DEPOSITED, NOT A PIECE OF ONE.
|
|
301
326
|
// "Longest wins" ranks candidates but never asks whether the winner is
|
|
302
327
|
// an entity at all, and by the time a chain reaches here `consumeAll`
|
|
@@ -330,18 +355,28 @@ export async function pivotInto(ctx, answer, consumed, voiced = []) {
|
|
|
330
355
|
// what `parents`/`containers` record. Reasoning steps THROUGH a fact;
|
|
331
356
|
// a span that was never a fact on its own is not one to step through.
|
|
332
357
|
// No constant enters — it is a structural predicate, not a threshold.
|
|
333
|
-
if (ctx.store.hasParents(id) || ctx.store.hasContainers(id))
|
|
334
|
-
|
|
335
|
-
|
|
358
|
+
if (ctx.store.hasParents(id) || ctx.store.hasContainers(id))
|
|
359
|
+
continue;
|
|
360
|
+
// A candidate whose bytes are LONGER than the answer cannot be a
|
|
361
|
+
// substring of it — `indexOf` would return −1 regardless. Prune by
|
|
362
|
+
// length BEFORE reconstructing the bytes: `read` is an UNCAPPED read
|
|
363
|
+
// (AGENTS §2.8), and a resonated context far longer than the answer is
|
|
364
|
+
// exactly the candidate that makes it cost a whole deposit's worth of
|
|
365
|
+
// reconstruction for a containment test that must fail. `contentLen`
|
|
366
|
+
// with the `answer.length + 1` cap is the prefix-capped length read the
|
|
367
|
+
// same contract prescribes; the prune is byte-identical to the old
|
|
368
|
+
// `indexOf` miss (it returns −1 for a needle longer than the haystack).
|
|
369
|
+
if (c.len > answer.length)
|
|
370
|
+
continue;
|
|
336
371
|
const bytes = read(ctx, id);
|
|
337
372
|
if (indexOf(answer, bytes, 0) < 0)
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
return
|
|
373
|
+
continue;
|
|
374
|
+
if (voiced.some((v) => indexOf(v, bytes, 0) >= 0))
|
|
375
|
+
continue;
|
|
376
|
+
pivotId = id;
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
return pivotId;
|
|
345
380
|
}
|
|
346
381
|
/** Which of the given labelled forms a span MEANS — generic resonance over
|
|
347
382
|
* perceived gists. Each anchor form's gist is memoised; the span's gist
|
package/dist/src/store.js
CHANGED
|
@@ -723,7 +723,28 @@ export class AbstractStore {
|
|
|
723
723
|
parts.push(child);
|
|
724
724
|
got += child.length;
|
|
725
725
|
}
|
|
726
|
-
|
|
726
|
+
const out = concat(parts);
|
|
727
|
+
// Cache the BRANCH too, not just the leaf above. Reconstruction is a pure
|
|
728
|
+
// function of the store, so this is a transparent cache in the strict sense
|
|
729
|
+
// — an eviction costs a re-walk and nothing else — which is exactly what
|
|
730
|
+
// `_bytesCache`'s "smallest"/"clock" configuration is for.
|
|
731
|
+
//
|
|
732
|
+
// Caching only leaves made every branch re-walk its whole subtree on every
|
|
733
|
+
// request, and the DAG is hash-consed, so the same children recur under many
|
|
734
|
+
// parents. Measured on the 18.9M-node store, ONE 1,314-byte query:
|
|
735
|
+
// 20,021,474 `_prefix` calls over 469,083 distinct ids (42.7x reuse) to
|
|
736
|
+
// produce 87,789 results — 97.7% of the work re-derived bytes it had already
|
|
737
|
+
// built. One single-byte leaf was reconstructed 2,599,984 times. Measuring
|
|
738
|
+
// reuse at the TOP level only shows 1.1x and hides all of it.
|
|
739
|
+
//
|
|
740
|
+
// Only a COMPLETE reconstruction may be cached: `_prefix` is also called
|
|
741
|
+
// with a cap, and a truncated prefix stored under `id` would be served as
|
|
742
|
+
// if it were the node's whole content by the `_bytesCache` hit above.
|
|
743
|
+
// `got < maxLen` is that proof — the walk ran out of children before it ran
|
|
744
|
+
// out of budget, so nothing below was truncated either.
|
|
745
|
+
if (got < maxLen)
|
|
746
|
+
this._bytesCache.set(id, out);
|
|
747
|
+
return out;
|
|
727
748
|
}
|
|
728
749
|
contentLen(id, cap = Infinity) {
|
|
729
750
|
if (this.meter)
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hviana/sema",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Sema: a non-parametric, instance-based reasoning system.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/hviana/sema.git"
|
|
8
|
+
},
|
|
5
9
|
"type": "module",
|
|
6
10
|
"main": "dist/src/index.js",
|
|
7
11
|
"types": "dist/src/index.d.ts",
|
|
@@ -14,7 +18,8 @@
|
|
|
14
18
|
"scripts": {
|
|
15
19
|
"build": "tsc",
|
|
16
20
|
"demo": "tsc && node dist/example/demo.js",
|
|
17
|
-
"test": "tsc && node --test test/**/*.test.mjs"
|
|
21
|
+
"test": "tsc && node --test test/**/*.test.mjs",
|
|
22
|
+
"prepublishOnly": "npm test"
|
|
18
23
|
},
|
|
19
24
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
20
25
|
"devDependencies": {
|
package/src/meter.ts
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* PHASES NEST, AND ARE NOT DISJOINT. `think` contains every mechanism
|
|
33
33
|
* phase; a mechanism's `floor` contains whatever shared analysis it
|
|
34
34
|
* first-touched (`attention`, `weave`); `recall.run` contains
|
|
35
|
-
* `substitutionBridge
|
|
35
|
+
* `substitutionBridge`. Read a
|
|
36
36
|
* phase as "wall-clock spent inside this, inclusive" — never sum them and
|
|
37
37
|
* expect the total. `CostReport.elapsedMs` is the only whole.
|
|
38
38
|
*
|
|
@@ -198,9 +198,6 @@ export class Meter {
|
|
|
198
198
|
mechanismRuns = 0;
|
|
199
199
|
/** Candidates the decider weighed. */
|
|
200
200
|
candidates = 0;
|
|
201
|
-
/** Candidates refused before the competition for explaining less than 1/W
|
|
202
|
-
* of the query — the honesty-density floor (see pipeline.ts `consider`). */
|
|
203
|
-
thinRejects = 0;
|
|
204
201
|
|
|
205
202
|
// ── Phases ──────────────────────────────────────────────────────────────
|
|
206
203
|
|
package/src/mind/attention.ts
CHANGED
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
import { recognise } from "./recognition.js";
|
|
46
46
|
import { leafIdRun } from "./canonical.js";
|
|
47
47
|
import {
|
|
48
|
+
atomIsHub,
|
|
48
49
|
corpusN,
|
|
49
50
|
edgeAncestors,
|
|
50
51
|
hubBound,
|
|
@@ -2647,26 +2648,28 @@ async function crossRegionVotes(
|
|
|
2647
2648
|
// the same container (or a sub-container of it) twice.
|
|
2648
2649
|
const consumed = new Set<number>();
|
|
2649
2650
|
let probes = 0;
|
|
2650
|
-
//
|
|
2651
|
-
//
|
|
2652
|
-
//
|
|
2653
|
-
//
|
|
2654
|
-
//
|
|
2655
|
-
//
|
|
2651
|
+
// When atoms themselves are hubs (atomIsHub — a single byte reaches ≥ √N
|
|
2652
|
+
// contexts, §2.8's own predicate), the corpus is large enough that the
|
|
2653
|
+
// cross-region junction walks are dominated by the drift through common
|
|
2654
|
+
// content's ancestry. Each of k candidate pairs otherwise spends its own
|
|
2655
|
+
// √N·W budget (profiled: 160,210 junction pops, 31% of think at
|
|
2656
|
+
// N = 325,608), and a cumulative dialogue multiplies bounded work into tens
|
|
2657
|
+
// of seconds. The structural walk is therefore given ONE k·W allowance per
|
|
2658
|
+
// evidence tier, shared across every pair — k pairs × W phrase-scale levels,
|
|
2659
|
+
// the minimal exact check; a pair whose container is not reached within it
|
|
2660
|
+
// falls through to the resonance tier (the ANN proposes what the shallow
|
|
2661
|
+
// walk no longer exhaustively scans, §2.3).
|
|
2656
2662
|
//
|
|
2657
|
-
//
|
|
2658
|
-
//
|
|
2659
|
-
//
|
|
2660
|
-
//
|
|
2661
|
-
//
|
|
2662
|
-
//
|
|
2663
|
-
// too tight below ~10³ contexts
|
|
2664
|
-
//
|
|
2665
|
-
//
|
|
2666
|
-
|
|
2667
|
-
// already makes — pricing the gate on the synthetic corpora.
|
|
2668
|
-
const marketScale = k * ctx.space.maxGroup;
|
|
2669
|
-
const corpusScale = N > marketScale ** 3;
|
|
2663
|
+
// Below atomIsHub the store is small and atoms still discriminate, so the
|
|
2664
|
+
// walks keep exhaustive exact traversal (per-walk √N·W) — the shared budget
|
|
2665
|
+
// would otherwise be smaller than the structures the tests deliberately
|
|
2666
|
+
// build. The gate is the SAME derived predicate the climb already uses for
|
|
2667
|
+
// byte atoms, not a separate corpus-size knob: an earlier `N > (k·W)³` cube
|
|
2668
|
+
// never engaged at real scale (96³ = 884,736 > 325,608), and a "share one
|
|
2669
|
+
// √N·W" experiment was too tight below ~10³ contexts (test/36, test/14) —
|
|
2670
|
+
// both are the same mistake of pricing the gate on corpus size instead of on
|
|
2671
|
+
// the atom-hub scale.
|
|
2672
|
+
const corpusScale = atomIsHub(ctx, N);
|
|
2670
2673
|
const exactBudget = corpusScale ? { n: k * ctx.space.maxGroup } : undefined;
|
|
2671
2674
|
const synonymBudget = corpusScale ? { n: k * ctx.space.maxGroup } : undefined;
|
|
2672
2675
|
|