@hviana/sema 0.4.1 → 0.4.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/dist/src/ingest-cache.js +4 -0
- package/dist/src/meter.d.ts +5 -0
- package/dist/src/meter.js +5 -0
- package/dist/src/mind/attention.js +19 -2
- package/dist/src/mind/bridge.js +265 -56
- package/dist/src/mind/junction.d.ts +7 -4
- package/dist/src/mind/junction.js +16 -5
- package/dist/src/mind/match.d.ts +15 -0
- package/dist/src/mind/match.js +92 -2
- package/dist/src/mind/mechanisms/cast.js +12 -1
- package/dist/src/mind/mechanisms/confluence.js +31 -1
- package/dist/src/mind/mechanisms/cover.d.ts +1 -1
- package/dist/src/mind/mechanisms/cover.js +29 -5
- package/dist/src/mind/mechanisms/recall.js +17 -41
- package/dist/src/mind/mind.d.ts +7 -0
- package/dist/src/mind/mind.js +25 -2
- package/dist/src/mind/pipeline-mechanism.d.ts +2 -2
- package/dist/src/mind/pipeline-mechanism.js +87 -4
- package/dist/src/mind/pipeline.js +1 -1
- package/dist/src/mind/reasoning.js +19 -11
- package/dist/src/mind/recognition.js +41 -0
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/traverse.d.ts +3 -1
- package/dist/src/mind/traverse.js +14 -13
- package/dist/src/mind/types.d.ts +10 -0
- package/package.json +1 -1
- package/src/ingest-cache.ts +4 -0
- package/src/meter.ts +5 -0
- package/src/mind/attention.ts +18 -1
- package/src/mind/bridge.ts +292 -54
- package/src/mind/junction.ts +21 -7
- package/src/mind/match.ts +92 -1
- package/src/mind/mechanisms/cast.ts +12 -0
- package/src/mind/mechanisms/confluence.ts +30 -1
- package/src/mind/mechanisms/cover.ts +36 -4
- package/src/mind/mechanisms/recall.ts +21 -44
- package/src/mind/mind.ts +39 -2
- package/src/mind/pipeline-mechanism.ts +86 -4
- package/src/mind/pipeline.ts +1 -1
- package/src/mind/reasoning.ts +15 -8
- package/src/mind/recognition.ts +40 -0
- package/src/mind/resonance.ts +0 -0
- package/src/mind/traverse.ts +17 -15
- package/src/mind/types.ts +10 -0
- package/test/49-natural-units-synonym-bridge.test.mjs +56 -15
|
@@ -46,7 +46,7 @@ async function collectComputed(ctx, mechanisms, query) {
|
|
|
46
46
|
// invests in its own precomputation. CAST's and confluence's floors (2*STEP,
|
|
47
47
|
// 3*STEP) then fail `worthRunning` and are skipped by the SAME admissible-
|
|
48
48
|
// floor pruning every mechanism is already subject to — not by asking
|
|
49
|
-
// "is this an extension?".
|
|
49
|
+
// "is this an extension?". Grade TIES keep the earlier candidate, so this
|
|
50
50
|
// order is also the tie-break priority: cover, cast, confluence, extraction,
|
|
51
51
|
// recall.
|
|
52
52
|
export const defaultMechanisms = [
|
|
@@ -65,17 +65,25 @@ export async function reason(ctx, query, answer, preConsumed, pre) {
|
|
|
65
65
|
// grounding can pre-consume one node per recognised site, O(query length));
|
|
66
66
|
// nodes past the cap are still consumed directly, they just skip the
|
|
67
67
|
// synonym expansion.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
const preconsume = async () => {
|
|
69
|
+
let haloSweeps = 0;
|
|
70
|
+
for (const id of preConsumed) {
|
|
71
|
+
consumeNode(id);
|
|
72
|
+
if (haloSweeps >= ctx.cfg.haloQueryK)
|
|
73
|
+
continue;
|
|
74
|
+
const h = ctx.store.halo(id);
|
|
75
|
+
if (!h)
|
|
76
|
+
continue;
|
|
77
|
+
haloSweeps++;
|
|
78
|
+
for (const sib of await haloSiblings(ctx, id, h))
|
|
79
|
+
consumeNode(sib.id);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
if (ctx.meter) {
|
|
83
|
+
await ctx.meter.time("reason.preconsumeHalos", preconsume);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
await preconsume();
|
|
79
87
|
}
|
|
80
88
|
let cur = answer;
|
|
81
89
|
const qv = pre.guide; // the response-wide guide IS the query's gist
|
|
@@ -333,6 +333,47 @@ function recogniseImpl(ctx, bytes) {
|
|
|
333
333
|
return null;
|
|
334
334
|
return singleLeaf[p];
|
|
335
335
|
};
|
|
336
|
+
// ── exact query-edge forms beyond the canonical chain reach ─────────
|
|
337
|
+
//
|
|
338
|
+
// At corpus scale off-boundary atom chains are deliberately suppressed,
|
|
339
|
+
// but a whole trained form can be longer than chainReach(W) and sit at a
|
|
340
|
+
// query edge without being a subtree of the query's larger root. Appending
|
|
341
|
+
// another topic demonstrates the failure: the exact 30-byte trained
|
|
342
|
+
// question `What is the capital of France?` ends inside the larger query's
|
|
343
|
+
// [27,33) content segment, so neither the structural walk nor a W² chain
|
|
344
|
+
// can name it.
|
|
345
|
+
//
|
|
346
|
+
// Probe only prefix/suffix endpoints within one maximum segment of the
|
|
347
|
+
// query's own content cuts. The flat-branch lookup is byte-exact and runs
|
|
348
|
+
// before resolveSpan pays for a fold; approximate evidence never enters.
|
|
349
|
+
// This tier is needed only where atom chains are suppressed. Small stores
|
|
350
|
+
// retain their existing decomposition unchanged.
|
|
351
|
+
if (atomsAreHubs) {
|
|
352
|
+
const allLeafIds = singleLeaf.map((x) => x?.id ?? null);
|
|
353
|
+
if (allLeafIds.every((x) => x !== null)) {
|
|
354
|
+
const radius = ctx.space.seats.length;
|
|
355
|
+
const endpoints = new Set([0, bytes.length]);
|
|
356
|
+
for (const cut of startList) {
|
|
357
|
+
for (let p = Math.max(0, cut - radius); p <= Math.min(bytes.length, cut + radius); p++)
|
|
358
|
+
endpoints.add(p);
|
|
359
|
+
}
|
|
360
|
+
const ordered = [...endpoints].sort((a, b) => a - b);
|
|
361
|
+
const probe = (start, end) => {
|
|
362
|
+
if (end - start < W || end - start <= chainReach(W))
|
|
363
|
+
return;
|
|
364
|
+
const ids = allLeafIds.slice(start, end);
|
|
365
|
+
if (store.findBranch(ids) === null)
|
|
366
|
+
return;
|
|
367
|
+
const id = resolveSpan(start, end);
|
|
368
|
+
if (id !== null)
|
|
369
|
+
emit(start, end, id);
|
|
370
|
+
};
|
|
371
|
+
for (const end of ordered)
|
|
372
|
+
probe(0, end);
|
|
373
|
+
for (const start of ordered)
|
|
374
|
+
probe(start, bytes.length);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
336
377
|
const chunkEnd = new Uint32Array(bytes.length);
|
|
337
378
|
const sorted = [...starts].sort((a, b) => a - b);
|
|
338
379
|
for (let si = 0; si < sorted.length; si++) {
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Vec } from "../vec.js";
|
|
2
2
|
import type { AncestorReach, MindContext } from "./types.js";
|
|
3
|
-
/** The reach memo this
|
|
3
|
+
/** The reach memo this ask should use — see the note above.
|
|
4
4
|
*
|
|
5
5
|
* A TRACED response always gets a fresh, empty one. `AncestorReach`'s
|
|
6
6
|
* `visited`/`maxDepth`/`saturation` fields are populated only when a trace
|
|
@@ -11,6 +11,8 @@ import type { AncestorReach, MindContext } from "./types.js";
|
|
|
11
11
|
* Consistent with AGENTS §2.11: a traced response is a different machine —
|
|
12
12
|
* never benchmark with a trace attached. */
|
|
13
13
|
export declare function sharedReachMemo(ctx: MindContext): Map<number, AncestorReach>;
|
|
14
|
+
/** Invalidate every session-lifetime structural read after a write. */
|
|
15
|
+
export declare function invalidateStructuralCaches(ctx: MindContext): void;
|
|
14
16
|
/** Climb the structural DAG from a node to its edge-bearing ancestor contexts.
|
|
15
17
|
* Ascent stops at hub nodes (parents > √N) — their reach is non-discriminative.
|
|
16
18
|
* When the start node has no structural parents, climbs from containment parents
|
|
@@ -18,20 +18,16 @@ const structCaches = new WeakMap();
|
|
|
18
18
|
// every mechanism that prices commonality" — was reached only by confluence.
|
|
19
19
|
// The climb is by far the biggest consumer.
|
|
20
20
|
//
|
|
21
|
-
// Keyed
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// matters — the stable-prefix fold makes each turn's subtree independent of
|
|
26
|
-
// what follows, so 59–70% of a later turn's climb regions are byte-identical
|
|
27
|
-
// repeats of an earlier turn's (measured on a 4-turn session), and every one
|
|
28
|
-
// of them used to re-climb from cold.
|
|
21
|
+
// Keyed by the Mind's structural lifecycle identity: ordinary and
|
|
22
|
+
// conversational asks share it, and every ingest invalidates it. A real
|
|
23
|
+
// battery repeatedly reaches the same corpus scaffolding even when its
|
|
24
|
+
// surface questions differ.
|
|
29
25
|
//
|
|
30
26
|
// Budgeted, not unbounded (AGENTS §2.12): past the cap the whole map is
|
|
31
27
|
// dropped and re-derived, costing a cold climb and never a wrong answer.
|
|
32
28
|
const REACH_MEMO_MAX = 100_000;
|
|
33
29
|
const reachCaches = new WeakMap();
|
|
34
|
-
/** The reach memo this
|
|
30
|
+
/** The reach memo this ask should use — see the note above.
|
|
35
31
|
*
|
|
36
32
|
* A TRACED response always gets a fresh, empty one. `AncestorReach`'s
|
|
37
33
|
* `visited`/`maxDepth`/`saturation` fields are populated only when a trace
|
|
@@ -44,9 +40,9 @@ const reachCaches = new WeakMap();
|
|
|
44
40
|
export function sharedReachMemo(ctx) {
|
|
45
41
|
if (ctx.trace !== null || ctx.climbMemo === null)
|
|
46
42
|
return new Map();
|
|
47
|
-
let m = reachCaches.get(ctx.
|
|
43
|
+
let m = reachCaches.get(ctx._structMemoKey);
|
|
48
44
|
if (m === undefined)
|
|
49
|
-
reachCaches.set(ctx.
|
|
45
|
+
reachCaches.set(ctx._structMemoKey, m = new Map());
|
|
50
46
|
else if (m.size >= REACH_MEMO_MAX)
|
|
51
47
|
m.clear();
|
|
52
48
|
return m;
|
|
@@ -54,9 +50,9 @@ export function sharedReachMemo(ctx) {
|
|
|
54
50
|
function getStructCache(ctx) {
|
|
55
51
|
if (ctx.climbMemo === null)
|
|
56
52
|
return null;
|
|
57
|
-
let c = structCaches.get(ctx.
|
|
53
|
+
let c = structCaches.get(ctx._structMemoKey);
|
|
58
54
|
if (c === undefined) {
|
|
59
|
-
structCaches.set(ctx.
|
|
55
|
+
structCaches.set(ctx._structMemoKey, c = {
|
|
60
56
|
hasNext: new Map(),
|
|
61
57
|
prevCount: new Map(),
|
|
62
58
|
hasParents: new Map(),
|
|
@@ -64,6 +60,11 @@ function getStructCache(ctx) {
|
|
|
64
60
|
}
|
|
65
61
|
return c;
|
|
66
62
|
}
|
|
63
|
+
/** Invalidate every session-lifetime structural read after a write. */
|
|
64
|
+
export function invalidateStructuralCaches(ctx) {
|
|
65
|
+
reachCaches.delete(ctx._structMemoKey);
|
|
66
|
+
structCaches.delete(ctx._structMemoKey);
|
|
67
|
+
}
|
|
67
68
|
/** Cached {@link Store.hasNext} — pure during one respond(). */
|
|
68
69
|
function cachedHasNext(ctx, id, cache) {
|
|
69
70
|
if (cache === null)
|
package/dist/src/mind/types.d.ts
CHANGED
|
@@ -232,6 +232,9 @@ export interface MindContext extends GraphSearchHost {
|
|
|
232
232
|
* Null outside respond(); during respondTurn() the conversation's
|
|
233
233
|
* persistent map is swapped in. */
|
|
234
234
|
climbMemo: Map<string, Map<string, AttentionRead>> | null;
|
|
235
|
+
/** Stable identity for session-lifetime, write-invalidated structural
|
|
236
|
+
* caches. Query-level climb results remain on climbMemo. */
|
|
237
|
+
_structMemoKey: object;
|
|
235
238
|
/** Memo of {@link recognise} — content-keyed (latin1) so recognised
|
|
236
239
|
* forms carry forward across conversation turns. Bypassed while a
|
|
237
240
|
* trace is attached. Null outside respond(). */
|
|
@@ -251,6 +254,13 @@ export interface MindContext extends GraphSearchHost {
|
|
|
251
254
|
id: number;
|
|
252
255
|
len: number;
|
|
253
256
|
}> | null;
|
|
257
|
+
/** Completed assistant-turn byte spans in the current cumulative query.
|
|
258
|
+
* Empty for ordinary respond(); response-scoped structural context for
|
|
259
|
+
* mechanisms that must not re-derive already-produced replies. */
|
|
260
|
+
answeredSpans: ReadonlyArray<readonly [number, number]>;
|
|
261
|
+
/** Start offset of the user turn currently being answered. Zero for an
|
|
262
|
+
* ordinary respond() and for the first turn of a conversation. */
|
|
263
|
+
currentTurnStart: number;
|
|
254
264
|
_edgeGuide: Vec | null;
|
|
255
265
|
_edgeChoice: Map<number, number>;
|
|
256
266
|
_prevSeen: Set<number> | null;
|
package/package.json
CHANGED
package/src/ingest-cache.ts
CHANGED
|
@@ -42,6 +42,8 @@ import { bindSeat, companySignature, type Sema } from "./sema.js";
|
|
|
42
42
|
import type { Input } from "./mind/index.js";
|
|
43
43
|
import { BoundedMap } from "./store.js";
|
|
44
44
|
import type { Vec } from "./vec.js";
|
|
45
|
+
import { invalidateStructuralCaches } from "./mind/traverse.js";
|
|
46
|
+
import { invalidateJunctionCache } from "./mind/junction.js";
|
|
45
47
|
|
|
46
48
|
/** The interned result of perceiving + interning ONE input.
|
|
47
49
|
*
|
|
@@ -122,6 +124,8 @@ export class CachedIngest {
|
|
|
122
124
|
input: Input | (Input | [Input, Input])[],
|
|
123
125
|
second?: Input,
|
|
124
126
|
): Promise<(Sema & { id: number }) | undefined> {
|
|
127
|
+
invalidateStructuralCaches(this.mind);
|
|
128
|
+
invalidateJunctionCache(this.mind);
|
|
125
129
|
// One shape-reading for both ingest paths — see {@link dispatchIngest}.
|
|
126
130
|
return dispatchIngest(
|
|
127
131
|
input,
|
package/src/meter.ts
CHANGED
|
@@ -176,6 +176,11 @@ export class Meter {
|
|
|
176
176
|
/** Nodes popped by those ascents, against their √N·W budget — the counter
|
|
177
177
|
* that shows whether the walks are deciding early or burning the budget. */
|
|
178
178
|
junctionPops = 0;
|
|
179
|
+
/** Arbitrary byte spans whose distributional company was VSA-bundled from
|
|
180
|
+
* existing episode halos. */
|
|
181
|
+
spanHalos = 0;
|
|
182
|
+
/** Canonical W-windows examined while composing those span halos. */
|
|
183
|
+
spanHaloWindows = 0;
|
|
179
184
|
|
|
180
185
|
/** `lightestDerivation` searches started. */
|
|
181
186
|
searches = 0;
|
package/src/mind/attention.ts
CHANGED
|
@@ -2275,6 +2275,16 @@ async function crossRegionVotes(
|
|
|
2275
2275
|
// the same container (or a sub-container of it) twice.
|
|
2276
2276
|
const consumed = new Set<number>();
|
|
2277
2277
|
let probes = 0;
|
|
2278
|
+
// Once atoms themselves are hubs (N > W²), the cross-region analysis gets
|
|
2279
|
+
// one k·W walk allowance per evidence tier. Without a shared allowance,
|
|
2280
|
+
// each of k candidate pairs spends the full corpus-derived budget and a
|
|
2281
|
+
// cumulative dialogue multiplies bounded work into tens of seconds. Small
|
|
2282
|
+
// corpora retain exhaustive exact traversal: below this same scale the
|
|
2283
|
+
// budget would be smaller than the structures the tests deliberately build.
|
|
2284
|
+
const marketScale = k * ctx.space.maxGroup;
|
|
2285
|
+
const corpusScale = N > marketScale ** 3;
|
|
2286
|
+
const exactBudget = corpusScale ? { n: k * ctx.space.maxGroup } : undefined;
|
|
2287
|
+
const synonymBudget = corpusScale ? { n: k * ctx.space.maxGroup } : undefined;
|
|
2278
2288
|
|
|
2279
2289
|
for (let a = 0; a < cand.length && probes < k; a++) {
|
|
2280
2290
|
if (consumed.has(cand[a])) continue;
|
|
@@ -2284,6 +2294,12 @@ async function crossRegionVotes(
|
|
|
2284
2294
|
const rb = regions[cand[b]];
|
|
2285
2295
|
if (!strong.has(cand[a]) && !strong.has(cand[b])) continue;
|
|
2286
2296
|
if (ra.end >= rb.start) continue; // overlap or adjacent — nothing between
|
|
2297
|
+
// In a cumulative conversation, an old↔old interaction cannot explain
|
|
2298
|
+
// the user turn currently being answered; it was already available
|
|
2299
|
+
// before that turn existed. Keep old↔current pairs (the current turn may
|
|
2300
|
+
// refer to a prior answer), but do not repeatedly spend the junction
|
|
2301
|
+
// budget recomposing two regions wholly before the current boundary.
|
|
2302
|
+
if (ctx.currentTurnStart > 0 && rb.end <= ctx.currentTurnStart) continue;
|
|
2287
2303
|
// Candidates strictly BETWEEN ra and rb (cand is sorted by start, so
|
|
2288
2304
|
// that is exactly cand[a+1 .. b-1]) that already cast their OWN vote —
|
|
2289
2305
|
// genuine, individually-corroborated evidence about what fills the gap
|
|
@@ -2364,7 +2380,7 @@ async function crossRegionVotes(
|
|
|
2364
2380
|
cap,
|
|
2365
2381
|
seedsOf(cand[a]),
|
|
2366
2382
|
seedsOf(cand[b]),
|
|
2367
|
-
|
|
2383
|
+
exactBudget,
|
|
2368
2384
|
true,
|
|
2369
2385
|
);
|
|
2370
2386
|
if (probe) {
|
|
@@ -2384,6 +2400,7 @@ async function crossRegionVotes(
|
|
|
2384
2400
|
maxInterior,
|
|
2385
2401
|
true,
|
|
2386
2402
|
sides,
|
|
2403
|
+
synonymBudget,
|
|
2387
2404
|
);
|
|
2388
2405
|
if (probe) {
|
|
2389
2406
|
const singleAttempted = sides.leftSiblings.length > 0 ||
|