@hviana/sema 0.2.6 → 0.2.7
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/example/demo.d.ts +1 -0
- package/dist/example/demo.js +39 -0
- package/dist/example/train_base.d.ts +87 -0
- package/dist/example/train_base.js +2252 -0
- package/dist/src/alphabet.d.ts +7 -0
- package/dist/src/alphabet.js +33 -0
- package/dist/src/alu/src/alu.d.ts +185 -0
- package/dist/src/alu/src/alu.js +440 -0
- package/dist/src/alu/src/expr.d.ts +61 -0
- package/dist/src/alu/src/expr.js +318 -0
- package/dist/src/alu/src/index.d.ts +11 -0
- package/dist/src/alu/src/index.js +19 -0
- package/dist/src/alu/src/kernel-arith.d.ts +16 -0
- package/dist/src/alu/src/kernel-arith.js +264 -0
- package/dist/src/alu/src/kernel-bits.d.ts +19 -0
- package/dist/src/alu/src/kernel-bits.js +152 -0
- package/dist/src/alu/src/kernel-logic.d.ts +4 -0
- package/dist/src/alu/src/kernel-logic.js +60 -0
- package/dist/src/alu/src/kernel-nd.d.ts +3 -0
- package/dist/src/alu/src/kernel-nd.js +208 -0
- package/dist/src/alu/src/kernel-numeric.d.ts +54 -0
- package/dist/src/alu/src/kernel-numeric.js +366 -0
- package/dist/src/alu/src/operation.d.ts +168 -0
- package/dist/src/alu/src/operation.js +189 -0
- package/dist/src/alu/src/parser.d.ts +221 -0
- package/dist/src/alu/src/parser.js +577 -0
- package/dist/src/alu/src/resonance.d.ts +55 -0
- package/dist/src/alu/src/resonance.js +126 -0
- package/dist/src/alu/src/text.d.ts +31 -0
- package/dist/src/alu/src/text.js +73 -0
- package/dist/src/alu/src/value.d.ts +109 -0
- package/dist/src/alu/src/value.js +300 -0
- package/dist/src/alu/test/alu.test.d.ts +1 -0
- package/dist/src/alu/test/alu.test.js +764 -0
- package/dist/src/bytes.d.ts +14 -0
- package/dist/src/bytes.js +59 -0
- package/dist/src/canon.d.ts +26 -0
- package/dist/src/canon.js +57 -0
- package/dist/src/config.d.ts +111 -0
- package/dist/src/config.js +91 -0
- package/dist/src/derive/src/deduction.d.ts +125 -0
- package/dist/src/derive/src/deduction.js +155 -0
- package/dist/src/derive/src/index.d.ts +7 -0
- package/dist/src/derive/src/index.js +11 -0
- package/dist/src/derive/src/priority-queue.d.ts +20 -0
- package/dist/src/derive/src/priority-queue.js +73 -0
- package/dist/src/derive/src/rewrite.d.ts +56 -0
- package/dist/src/derive/src/rewrite.js +100 -0
- package/dist/src/derive/src/trie.d.ts +90 -0
- package/dist/src/derive/src/trie.js +217 -0
- package/dist/src/derive/test/derive.test.d.ts +1 -0
- package/dist/src/derive/test/derive.test.js +122 -0
- package/dist/src/extension.d.ts +37 -0
- package/dist/src/extension.js +7 -0
- package/dist/src/geometry.d.ts +202 -0
- package/dist/src/geometry.js +575 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.js +18 -0
- package/dist/src/ingest-cache.d.ts +41 -0
- package/dist/src/ingest-cache.js +161 -0
- package/dist/src/mind/articulation.d.ts +6 -0
- package/dist/src/mind/articulation.js +99 -0
- package/dist/src/mind/attention.d.ts +400 -0
- package/dist/src/mind/attention.js +1763 -0
- package/dist/src/mind/bridge.d.ts +30 -0
- package/dist/src/mind/bridge.js +569 -0
- package/dist/src/mind/canonical.d.ts +29 -0
- package/dist/src/mind/canonical.js +88 -0
- package/dist/src/mind/graph-search.d.ts +285 -0
- package/dist/src/mind/graph-search.js +956 -0
- package/dist/src/mind/index.d.ts +7 -0
- package/dist/src/mind/index.js +5 -0
- package/dist/src/mind/junction.d.ts +124 -0
- package/dist/src/mind/junction.js +317 -0
- package/dist/src/mind/learning.d.ts +47 -0
- package/dist/src/mind/learning.js +248 -0
- package/dist/src/mind/match.d.ts +126 -0
- package/dist/src/mind/match.js +441 -0
- package/dist/src/mind/mechanisms/alu.d.ts +4 -0
- package/dist/src/mind/mechanisms/alu.js +36 -0
- package/dist/src/mind/mechanisms/cast.d.ts +89 -0
- package/dist/src/mind/mechanisms/cast.js +716 -0
- package/dist/src/mind/mechanisms/confluence.d.ts +24 -0
- package/dist/src/mind/mechanisms/confluence.js +225 -0
- package/dist/src/mind/mechanisms/cover.d.ts +6 -0
- package/dist/src/mind/mechanisms/cover.js +203 -0
- package/dist/src/mind/mechanisms/extraction.d.ts +67 -0
- package/dist/src/mind/mechanisms/extraction.js +387 -0
- package/dist/src/mind/mechanisms/recall.d.ts +13 -0
- package/dist/src/mind/mechanisms/recall.js +325 -0
- package/dist/src/mind/mind.d.ts +276 -0
- package/dist/src/mind/mind.js +572 -0
- package/dist/src/mind/pipeline-mechanism.d.ts +142 -0
- package/dist/src/mind/pipeline-mechanism.js +213 -0
- package/dist/src/mind/pipeline.d.ts +20 -0
- package/dist/src/mind/pipeline.js +217 -0
- package/dist/src/mind/primitives.d.ts +70 -0
- package/dist/src/mind/primitives.js +287 -0
- package/dist/src/mind/rationale.d.ts +139 -0
- package/dist/src/mind/rationale.js +163 -0
- package/dist/src/mind/reasoning.d.ts +34 -0
- package/dist/src/mind/reasoning.js +240 -0
- package/dist/src/mind/recognition.d.ts +20 -0
- package/dist/src/mind/recognition.js +390 -0
- package/dist/src/mind/resonance.d.ts +23 -0
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/trace.d.ts +15 -0
- package/dist/src/mind/trace.js +73 -0
- package/dist/src/mind/traverse.d.ts +113 -0
- package/dist/src/mind/traverse.js +568 -0
- package/dist/src/mind/types.d.ts +289 -0
- package/dist/src/mind/types.js +130 -0
- package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
- package/dist/src/rabitq-ivf/src/database.js +201 -0
- package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
- package/dist/src/rabitq-ivf/src/index.js +4 -0
- package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
- package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
- package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
- package/dist/src/rabitq-ivf/src/prng.js +36 -0
- package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
- package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
- package/dist/src/sema.d.ts +31 -0
- package/dist/src/sema.js +63 -0
- package/dist/src/store-sqlite.d.ts +184 -0
- package/dist/src/store-sqlite.js +942 -0
- package/dist/src/store.d.ts +663 -0
- package/dist/src/store.js +1618 -0
- package/dist/src/vec.d.ts +31 -0
- package/dist/src/vec.js +109 -0
- package/package.json +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { Mind } from "./mind.js";
|
|
2
|
+
export type { Input, Response } from "./mind.js";
|
|
3
|
+
export type { ComputedSpan, ExtensionHost } from "./mind.js";
|
|
4
|
+
export type { MechanismResult, PipelineMechanism, Precomputed, } from "./pipeline-mechanism.js";
|
|
5
|
+
export type { InspectRationale, RationaleItem, RationaleStep, } from "./rationale.js";
|
|
6
|
+
export type { AnchorRejectionReason, ClimbConsensusData, ConsensusAnchorTrace, ConsensusReachTrace, ConsensusRegionTrace, CrossRegionTier, JunctionVoteTrace, RegionOutcome, } from "./attention.js";
|
|
7
|
+
export type { AncestorReach, AttentionRead, SaturationReason, SaturationStop, } from "./types.js";
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { Hit } from "../store.js";
|
|
2
|
+
import type { MindContext } from "./types.js";
|
|
3
|
+
export interface Junction {
|
|
4
|
+
/** The node whose learnt bytes evidence this junction (a container form,
|
|
5
|
+
* a continuation, or a context). */
|
|
6
|
+
id: number;
|
|
7
|
+
/** The bytes that belong between left and right. */
|
|
8
|
+
interior: Uint8Array;
|
|
9
|
+
}
|
|
10
|
+
/** Which relaxation produced a {@link SynonymJunction}: one side replaced by
|
|
11
|
+
* a distributional halo sibling (`single-synonym`), or both (`double-
|
|
12
|
+
* synonym`) — the two remaining rungs of the graded ladder below exact DAG
|
|
13
|
+
* containment (see the module doc atop {@link junctionSynonyms}). */
|
|
14
|
+
export type SynonymJunctionTier = "single-synonym" | "double-synonym";
|
|
15
|
+
export interface SynonymJunction extends Junction {
|
|
16
|
+
tier: SynonymJunctionTier;
|
|
17
|
+
/** Sibling score for a single-synonym junction; min(left, right) sibling
|
|
18
|
+
* score for a double-synonym junction. */
|
|
19
|
+
confidence: number;
|
|
20
|
+
}
|
|
21
|
+
/** The exact node ids and halo siblings resolved for one junction call's two
|
|
22
|
+
* sides — computed ONCE and reused by every ladder rung that needs them
|
|
23
|
+
* (junctionSynonyms' two tiers, and the structural-resonance tier beyond
|
|
24
|
+
* it). A failed synonym junction means only "no common DAG container was
|
|
25
|
+
* proven" — it does NOT mean the loaded siblings stop being useful. */
|
|
26
|
+
export interface JunctionSynonymSides {
|
|
27
|
+
leftId: number | null;
|
|
28
|
+
rightId: number | null;
|
|
29
|
+
leftSiblings: Hit[];
|
|
30
|
+
rightSiblings: Hit[];
|
|
31
|
+
}
|
|
32
|
+
/** Resolve `left`/`right` to their exact node ids (when known) and load each
|
|
33
|
+
* resolved side's halo siblings once — deterministic (haloSiblings already
|
|
34
|
+
* ranks nearest-first) and shared by every ladder rung that consults
|
|
35
|
+
* siblings, so no ladder rung repeats a halo ANN query the previous one
|
|
36
|
+
* already paid for. */
|
|
37
|
+
export declare function loadJunctionSynonymSides(ctx: MindContext, left: Uint8Array, right: Uint8Array): Promise<JunctionSynonymSides>;
|
|
38
|
+
/** Seed node ids to ascend from for one side of a junction: the side's own
|
|
39
|
+
* node when it is a stored form, plus — when the node has no structural
|
|
40
|
+
* parents — its canonical window ids. A non-W-aligned node may have no
|
|
41
|
+
* parents, but its constituent W-grams typically do; the window ids
|
|
42
|
+
* provide alternative ascent paths. The `parentsFirst(…, 1)` probe is a
|
|
43
|
+
* single indexed lookup, far cheaper than computing every window id, so
|
|
44
|
+
* the window-id path is only taken when the node alone cannot ascend.
|
|
45
|
+
* Exported for callers (synonym junctions) that hold one side FIXED across
|
|
46
|
+
* several calls and so compute its seeds once instead of per call. */
|
|
47
|
+
export declare function junctionSeeds(ctx: MindContext, b: Uint8Array): number[];
|
|
48
|
+
/** Per-response cache of the identity walks' pure reads (capped bytes,
|
|
49
|
+
* parent pages, container pages), keyed by the response lifecycle object
|
|
50
|
+
* (ctx.climbMemo). One response issues many walks whose ancestries overlap
|
|
51
|
+
* heavily (pair sides repeat across combos, and synonym walks revisit the
|
|
52
|
+
* same neighbourhoods); the store is read-only while a response is in flight,
|
|
53
|
+
* so every one of these reads is a pure function of the id — repeats cost a
|
|
54
|
+
* Map hit instead of a SQL statement or a byte reconstruction. */
|
|
55
|
+
export interface WalkCache {
|
|
56
|
+
/** id → prefix bytes read so far + whether they are the COMPLETE bytes
|
|
57
|
+
* (shorter than the cap that read them). */
|
|
58
|
+
reads: Map<number, {
|
|
59
|
+
b: Uint8Array;
|
|
60
|
+
complete: boolean;
|
|
61
|
+
}>;
|
|
62
|
+
parents: Map<number, number[]>;
|
|
63
|
+
containers: Map<number, number[]>;
|
|
64
|
+
}
|
|
65
|
+
export declare function walkCache(ctx: MindContext): WalkCache | null;
|
|
66
|
+
export declare function cachedRead(ctx: MindContext, cache: WalkCache | null, id: number, cap: number): Uint8Array;
|
|
67
|
+
/** Tier 1 body, parameterised on already-resolved seed lists so a caller
|
|
68
|
+
* holding one side FIXED across several calls (synonym junctions) pays for
|
|
69
|
+
* that side's seeds once, not once per call. The byte-containment check
|
|
70
|
+
* below ensures only genuine containers are returned regardless of seeds.
|
|
71
|
+
*
|
|
72
|
+
* BOUNDED at corpus scale by three disciplines (profiled on a 17.7M-node
|
|
73
|
+
* store, where the unbounded form spent >90% of a query's CPU here):
|
|
74
|
+
* • PHRASE-SCALE READS — a junction container is by contract a whole the
|
|
75
|
+
* pair nearly exhausts (glue from a period to a phrase), so every visit
|
|
76
|
+
* reads at most `maxContainer + 1` bytes (`bytesPrefix` stops early).
|
|
77
|
+
* A node whose bytes exceed the cap cannot be a junction container, and
|
|
78
|
+
* its ancestors are strictly larger — the branch is PRUNED, never
|
|
79
|
+
* reconstructing a corpus-sized deposit (an oasst2 conversation) just
|
|
80
|
+
* to reject it.
|
|
81
|
+
* • EXPANSION BUDGET — at most √N·W nodes are popped in total: a √N-wide
|
|
82
|
+
* frontier (the one fan-out convention) through the ~W structural
|
|
83
|
+
* levels that separate phrase-scale content from its containers
|
|
84
|
+
* (perception trees are W-ary, so a junction container lies within a
|
|
85
|
+
* few levels of its parts). A side too common to decide within the
|
|
86
|
+
* budget abstains here and falls through to the resonance tier (the
|
|
87
|
+
* climb's own saturation semantics).
|
|
88
|
+
* • per-node hub guards — parent fan-outs beyond √N are hubs (not
|
|
89
|
+
* expanded); each node contributes at most one √N page of containers;
|
|
90
|
+
* √N collected candidates decide. */
|
|
91
|
+
export declare function junctionContainersFrom(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxContainer: number, leftSeeds: number[], rightSeeds: number[],
|
|
92
|
+
/** Shared expansion budget — a TIER's √N pops, not each walk's, when one
|
|
93
|
+
* tier issues several walks (synonym junctions try up to 2·haloQueryK
|
|
94
|
+
* siblings; without a shared budget each sibling would spend its own √N). */
|
|
95
|
+
budget?: {
|
|
96
|
+
n: number;
|
|
97
|
+
},
|
|
98
|
+
/** ORDER-FREE containment: also accept containers holding right-then-left.
|
|
99
|
+
* A junction is evidence that the two forms were LEARNT TOGETHER; which
|
|
100
|
+
* one the query happened to mention first is a fact about the query, not
|
|
101
|
+
* about the learnt whole. The walk is identical (the seed ascent does not
|
|
102
|
+
* depend on order) — only the byte-containment test gains a second probe,
|
|
103
|
+
* so order-freedom costs two indexOf calls per visited node, never a
|
|
104
|
+
* second walk. */
|
|
105
|
+
unordered?: boolean): Junction[];
|
|
106
|
+
/** Tier 1 entry point: every learnt whole that literally contains
|
|
107
|
+
* left-then-right, found by ascending the structural DAG (parents +
|
|
108
|
+
* containment links) from the two sides' content-addressed identities.
|
|
109
|
+
* Both sides' seeds resolved fresh, one call. */
|
|
110
|
+
export declare function junctionContainers(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxContainer: number, unordered?: boolean): Junction[];
|
|
111
|
+
/** Tier 2.5: synonym junctions — the container ascent (tier 1) applied to
|
|
112
|
+
* halo siblings of left and right. When a distributional synonym of one
|
|
113
|
+
* form participates in a learnt whole with the other form, the container
|
|
114
|
+
* between the synonym and the other side is valid evidence for the
|
|
115
|
+
* original pair. The container evidence is exact (content-addressed DAG
|
|
116
|
+
* ascent, with window-id-enhanced seeds so non-W-aligned siblings still
|
|
117
|
+
* ascend); the relaxation is only in which form occupies one side — a
|
|
118
|
+
* distributional sibling rather than the exact form.
|
|
119
|
+
*
|
|
120
|
+
* ONE expansion budget is shared by every sibling walk in this call, so
|
|
121
|
+
* cost is bounded at √N·W pops total regardless of how many siblings are
|
|
122
|
+
* tried. A sibling whose bytes exceed `maxInterior` is skipped (it
|
|
123
|
+
* cannot be junction-sized). */
|
|
124
|
+
export declare function junctionSynonyms(ctx: MindContext, left: Uint8Array, right: Uint8Array, maxInterior: number, unordered?: boolean, sides?: JunctionSynonymSides): Promise<SynonymJunction[]>;
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
// junction.ts — content-addressed junction search (bridge Tier 1).
|
|
2
|
+
//
|
|
3
|
+
// "Which learnt wholes ran L and R together?" answered by DAG ascent, not a
|
|
4
|
+
// similarity guess: hash-consing means any deposit containing L's bytes shares
|
|
5
|
+
// L's node (or L's canonical-window ids — position-independent identities), so
|
|
6
|
+
// climbing parents + containment links from L's and R's seeds reaches every
|
|
7
|
+
// container that literally holds L-then-R. A resonance seed (the gist of the
|
|
8
|
+
// bare concatenation — an object never learnt) could rank the true container
|
|
9
|
+
// out of its top-k; the ascent cannot.
|
|
10
|
+
//
|
|
11
|
+
// Extracted from resonance.ts so BOTH the bridge (a connector between two
|
|
12
|
+
// adjacent ANSWER pieces) and cross-region attention (the joint CONTEXT of two
|
|
13
|
+
// non-adjacent QUERY regions) ascend by the same disciplined, bounded walk.
|
|
14
|
+
import { read, resolve } from "./primitives.js";
|
|
15
|
+
import { windowIds } from "./canonical.js";
|
|
16
|
+
import { hubBound } from "./traverse.js";
|
|
17
|
+
import { haloSiblings } from "./match.js";
|
|
18
|
+
import { indexOf } from "../bytes.js";
|
|
19
|
+
/** Resolve `left`/`right` to their exact node ids (when known) and load each
|
|
20
|
+
* resolved side's halo siblings once — deterministic (haloSiblings already
|
|
21
|
+
* ranks nearest-first) and shared by every ladder rung that consults
|
|
22
|
+
* siblings, so no ladder rung repeats a halo ANN query the previous one
|
|
23
|
+
* already paid for. */
|
|
24
|
+
export async function loadJunctionSynonymSides(ctx, left, right) {
|
|
25
|
+
const leftId = resolve(ctx, left);
|
|
26
|
+
const rightId = resolve(ctx, right);
|
|
27
|
+
const leftSiblings = leftId !== null ? await haloSiblings(ctx, leftId) : [];
|
|
28
|
+
const rightSiblings = rightId !== null
|
|
29
|
+
? await haloSiblings(ctx, rightId)
|
|
30
|
+
: [];
|
|
31
|
+
return { leftId, rightId, leftSiblings, rightSiblings };
|
|
32
|
+
}
|
|
33
|
+
/** Seed node ids to ascend from for one side of a junction: the side's own
|
|
34
|
+
* node when it is a stored form, plus — when the node has no structural
|
|
35
|
+
* parents — its canonical window ids. A non-W-aligned node may have no
|
|
36
|
+
* parents, but its constituent W-grams typically do; the window ids
|
|
37
|
+
* provide alternative ascent paths. The `parentsFirst(…, 1)` probe is a
|
|
38
|
+
* single indexed lookup, far cheaper than computing every window id, so
|
|
39
|
+
* the window-id path is only taken when the node alone cannot ascend.
|
|
40
|
+
* Exported for callers (synonym junctions) that hold one side FIXED across
|
|
41
|
+
* several calls and so compute its seeds once instead of per call. */
|
|
42
|
+
export function junctionSeeds(ctx, b) {
|
|
43
|
+
const r = resolve(ctx, b);
|
|
44
|
+
if (r !== null) {
|
|
45
|
+
if (ctx.store.parentsFirst(r, 1).length > 0)
|
|
46
|
+
return [r];
|
|
47
|
+
const wids = [...windowIds(ctx, b).values()];
|
|
48
|
+
return [r, ...wids];
|
|
49
|
+
}
|
|
50
|
+
const wids = [...windowIds(ctx, b).values()];
|
|
51
|
+
if (wids.length <= 2)
|
|
52
|
+
return wids;
|
|
53
|
+
return [wids[0], wids[wids.length - 1]];
|
|
54
|
+
}
|
|
55
|
+
const walkCaches = new WeakMap();
|
|
56
|
+
export function walkCache(ctx) {
|
|
57
|
+
if (ctx.climbMemo === null)
|
|
58
|
+
return null;
|
|
59
|
+
let c = walkCaches.get(ctx.climbMemo);
|
|
60
|
+
if (c === undefined) {
|
|
61
|
+
walkCaches.set(ctx.climbMemo, c = { reads: new Map(), parents: new Map(), containers: new Map() });
|
|
62
|
+
}
|
|
63
|
+
return c;
|
|
64
|
+
}
|
|
65
|
+
export function cachedRead(ctx, cache, id, cap) {
|
|
66
|
+
if (cache === null)
|
|
67
|
+
return read(ctx, id, cap + 1);
|
|
68
|
+
const hit = cache.reads.get(id);
|
|
69
|
+
// A cached COMPLETE read serves any cap; a cached truncated read serves
|
|
70
|
+
// any cap it already covers (the caller only checks `length > cap`).
|
|
71
|
+
if (hit !== undefined && (hit.complete || hit.b.length > cap))
|
|
72
|
+
return hit.b;
|
|
73
|
+
const b = read(ctx, id, cap + 1);
|
|
74
|
+
cache.reads.set(id, { b, complete: b.length <= cap });
|
|
75
|
+
return b;
|
|
76
|
+
}
|
|
77
|
+
function cachedParents(ctx, cache, id, limit) {
|
|
78
|
+
if (cache === null)
|
|
79
|
+
return ctx.store.parentsFirst(id, limit);
|
|
80
|
+
let v = cache.parents.get(id);
|
|
81
|
+
if (v === undefined) {
|
|
82
|
+
v = ctx.store.parentsFirst(id, limit);
|
|
83
|
+
cache.parents.set(id, v);
|
|
84
|
+
}
|
|
85
|
+
return v;
|
|
86
|
+
}
|
|
87
|
+
function cachedContainers(ctx, cache, id, limit) {
|
|
88
|
+
if (cache === null)
|
|
89
|
+
return ctx.store.containersSlice(id, 0, limit);
|
|
90
|
+
let v = cache.containers.get(id);
|
|
91
|
+
if (v === undefined) {
|
|
92
|
+
v = ctx.store.containersSlice(id, 0, limit);
|
|
93
|
+
cache.containers.set(id, v);
|
|
94
|
+
}
|
|
95
|
+
return v;
|
|
96
|
+
}
|
|
97
|
+
/** Tier 1 body, parameterised on already-resolved seed lists so a caller
|
|
98
|
+
* holding one side FIXED across several calls (synonym junctions) pays for
|
|
99
|
+
* that side's seeds once, not once per call. The byte-containment check
|
|
100
|
+
* below ensures only genuine containers are returned regardless of seeds.
|
|
101
|
+
*
|
|
102
|
+
* BOUNDED at corpus scale by three disciplines (profiled on a 17.7M-node
|
|
103
|
+
* store, where the unbounded form spent >90% of a query's CPU here):
|
|
104
|
+
* • PHRASE-SCALE READS — a junction container is by contract a whole the
|
|
105
|
+
* pair nearly exhausts (glue from a period to a phrase), so every visit
|
|
106
|
+
* reads at most `maxContainer + 1` bytes (`bytesPrefix` stops early).
|
|
107
|
+
* A node whose bytes exceed the cap cannot be a junction container, and
|
|
108
|
+
* its ancestors are strictly larger — the branch is PRUNED, never
|
|
109
|
+
* reconstructing a corpus-sized deposit (an oasst2 conversation) just
|
|
110
|
+
* to reject it.
|
|
111
|
+
* • EXPANSION BUDGET — at most √N·W nodes are popped in total: a √N-wide
|
|
112
|
+
* frontier (the one fan-out convention) through the ~W structural
|
|
113
|
+
* levels that separate phrase-scale content from its containers
|
|
114
|
+
* (perception trees are W-ary, so a junction container lies within a
|
|
115
|
+
* few levels of its parts). A side too common to decide within the
|
|
116
|
+
* budget abstains here and falls through to the resonance tier (the
|
|
117
|
+
* climb's own saturation semantics).
|
|
118
|
+
* • per-node hub guards — parent fan-outs beyond √N are hubs (not
|
|
119
|
+
* expanded); each node contributes at most one √N page of containers;
|
|
120
|
+
* √N collected candidates decide. */
|
|
121
|
+
export function junctionContainersFrom(ctx, left, right, maxContainer, leftSeeds, rightSeeds,
|
|
122
|
+
/** Shared expansion budget — a TIER's √N pops, not each walk's, when one
|
|
123
|
+
* tier issues several walks (synonym junctions try up to 2·haloQueryK
|
|
124
|
+
* siblings; without a shared budget each sibling would spend its own √N). */
|
|
125
|
+
budget,
|
|
126
|
+
/** ORDER-FREE containment: also accept containers holding right-then-left.
|
|
127
|
+
* A junction is evidence that the two forms were LEARNT TOGETHER; which
|
|
128
|
+
* one the query happened to mention first is a fact about the query, not
|
|
129
|
+
* about the learnt whole. The walk is identical (the seed ascent does not
|
|
130
|
+
* depend on order) — only the byte-containment test gains a second probe,
|
|
131
|
+
* so order-freedom costs two indexOf calls per visited node, never a
|
|
132
|
+
* second walk. */
|
|
133
|
+
unordered = false) {
|
|
134
|
+
const bound = hubBound(ctx);
|
|
135
|
+
const joinedLength = left.length + right.length;
|
|
136
|
+
const seeds = [...new Set([...leftSeeds, ...rightSeeds])];
|
|
137
|
+
if (seeds.length === 0)
|
|
138
|
+
return [];
|
|
139
|
+
const b = budget ?? { n: bound * ctx.space.maxGroup };
|
|
140
|
+
// DEPTH CAP: perception trees are W-ary and a junction container is
|
|
141
|
+
// phrase-scale, so it sits within ~log_W(maxContainer) structural levels
|
|
142
|
+
// of its parts — at most W levels for any practical W (plus the
|
|
143
|
+
// containment hop the seeds already are). Ancestry beyond that depth is
|
|
144
|
+
// strictly larger than any admissible container; walking it can only burn
|
|
145
|
+
// budget, never find a junction.
|
|
146
|
+
const maxDepth = ctx.space.maxGroup;
|
|
147
|
+
const out = [];
|
|
148
|
+
const cache = walkCache(ctx);
|
|
149
|
+
const seen = new Set(seeds);
|
|
150
|
+
const stack = seeds.map((id) => ({
|
|
151
|
+
id,
|
|
152
|
+
d: 0,
|
|
153
|
+
}));
|
|
154
|
+
while (stack.length > 0 && out.length < bound && b.n-- > 0) {
|
|
155
|
+
const { id: x, d } = stack.pop();
|
|
156
|
+
const f = cachedRead(ctx, cache, x, maxContainer);
|
|
157
|
+
if (f.length > maxContainer)
|
|
158
|
+
continue; // beyond phrase scale: prune branch
|
|
159
|
+
if (unordered) {
|
|
160
|
+
// Order-free containment does NOT require disjoint occurrences: two
|
|
161
|
+
// grid-aligned fragments of the same whole legitimately OVERLAP inside
|
|
162
|
+
// it ("red " at 0 and " cir" at 3 in `red circle`), and both being
|
|
163
|
+
// literal substrings is the evidence. The interior is the gap between
|
|
164
|
+
// them when they are disjoint, empty otherwise. Only the containment
|
|
165
|
+
// test differs from the ordered form — and because occurrences may
|
|
166
|
+
// overlap or abut, `f.length > joinedLength` is too strict (grid
|
|
167
|
+
// fragments of one whole sum past it; "red " + "circle" exactly equals
|
|
168
|
+
// `red circle`). The container must be a STRICT super-form of each
|
|
169
|
+
// side, so that holding both is more than restating either.
|
|
170
|
+
const li = indexOf(f, left, 0);
|
|
171
|
+
const ri = li >= 0 ? indexOf(f, right, 0) : -1;
|
|
172
|
+
if (li >= 0 && ri >= 0 && f.length > Math.max(left.length, right.length)) {
|
|
173
|
+
const lo = Math.min(li + left.length, ri + right.length);
|
|
174
|
+
const hi = Math.max(li, ri);
|
|
175
|
+
out.push({
|
|
176
|
+
id: x,
|
|
177
|
+
interior: lo < hi ? f.subarray(lo, hi) : f.subarray(0, 0),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else if (f.length > joinedLength) {
|
|
182
|
+
const li = indexOf(f, left, 0);
|
|
183
|
+
if (li >= 0) {
|
|
184
|
+
const ri = indexOf(f, right, li + left.length);
|
|
185
|
+
if (ri >= 0) {
|
|
186
|
+
out.push({ id: x, interior: f.subarray(li + left.length, ri) });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (d >= maxDepth)
|
|
191
|
+
continue; // deeper ancestry is beyond phrase scale
|
|
192
|
+
const parents = cachedParents(ctx, cache, x, bound + 1);
|
|
193
|
+
if (parents.length <= bound) { // beyond √N parents: a hub, not expanded
|
|
194
|
+
for (const p of parents) {
|
|
195
|
+
if (!seen.has(p)) {
|
|
196
|
+
seen.add(p);
|
|
197
|
+
stack.push({ id: p, d: d + 1 });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Containment fan-out under the SAME hub reading as parents: a node
|
|
202
|
+
// whose containers fill a whole √N page is COMMON content — its
|
|
203
|
+
// containment ancestry reaches a non-discriminative slice of the corpus
|
|
204
|
+
// (the climb's saturation semantics), and walking it would spend the
|
|
205
|
+
// entire budget discovering nothing a junction could use. Such a node
|
|
206
|
+
// is not expanded through containment; a pair whose sides are common
|
|
207
|
+
// abstains here in a handful of pops and falls through to the resonance
|
|
208
|
+
// tier. Below the page bound the read IS the full container list, so
|
|
209
|
+
// the walk stays exact exactly where identity evidence discriminates.
|
|
210
|
+
const containers = cachedContainers(ctx, cache, x, bound);
|
|
211
|
+
if (containers.length < bound) {
|
|
212
|
+
for (const c of containers) {
|
|
213
|
+
if (!seen.has(c)) {
|
|
214
|
+
seen.add(c);
|
|
215
|
+
stack.push({ id: c, d: d + 1 });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
/** Tier 1 entry point: every learnt whole that literally contains
|
|
223
|
+
* left-then-right, found by ascending the structural DAG (parents +
|
|
224
|
+
* containment links) from the two sides' content-addressed identities.
|
|
225
|
+
* Both sides' seeds resolved fresh, one call. */
|
|
226
|
+
export function junctionContainers(ctx, left, right, maxContainer, unordered = false) {
|
|
227
|
+
return junctionContainersFrom(ctx, left, right, maxContainer, junctionSeeds(ctx, left), junctionSeeds(ctx, right), undefined, unordered);
|
|
228
|
+
}
|
|
229
|
+
/** Tier 2.5: synonym junctions — the container ascent (tier 1) applied to
|
|
230
|
+
* halo siblings of left and right. When a distributional synonym of one
|
|
231
|
+
* form participates in a learnt whole with the other form, the container
|
|
232
|
+
* between the synonym and the other side is valid evidence for the
|
|
233
|
+
* original pair. The container evidence is exact (content-addressed DAG
|
|
234
|
+
* ascent, with window-id-enhanced seeds so non-W-aligned siblings still
|
|
235
|
+
* ascend); the relaxation is only in which form occupies one side — a
|
|
236
|
+
* distributional sibling rather than the exact form.
|
|
237
|
+
*
|
|
238
|
+
* ONE expansion budget is shared by every sibling walk in this call, so
|
|
239
|
+
* cost is bounded at √N·W pops total regardless of how many siblings are
|
|
240
|
+
* tried. A sibling whose bytes exceed `maxInterior` is skipped (it
|
|
241
|
+
* cannot be junction-sized). */
|
|
242
|
+
export async function junctionSynonyms(ctx, left, right, maxInterior, unordered = false, sides) {
|
|
243
|
+
const s = sides ?? await loadJunctionSynonymSides(ctx, left, right);
|
|
244
|
+
if (s.leftId === null && s.rightId === null)
|
|
245
|
+
return [];
|
|
246
|
+
// ── Tier 2.5a: single-synonym — one side replaced by a halo sibling ──────
|
|
247
|
+
// ONE shared expansion budget across BOTH directions of this tier.
|
|
248
|
+
const singleBudget = { n: hubBound(ctx) * ctx.space.maxGroup };
|
|
249
|
+
const singleOut = new Map();
|
|
250
|
+
const keepBest = (map, j, tier, confidence) => {
|
|
251
|
+
const prev = map.get(j.id);
|
|
252
|
+
if (prev === undefined || confidence > prev.confidence) {
|
|
253
|
+
map.set(j.id, { ...j, tier, confidence });
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
// Left-side synonyms: containers of sibling+right. `right`'s seeds are
|
|
257
|
+
// FIXED across every sibling this loop tries.
|
|
258
|
+
if (s.leftId !== null) {
|
|
259
|
+
const rightSeeds = junctionSeeds(ctx, right);
|
|
260
|
+
for (const sib of s.leftSiblings) {
|
|
261
|
+
const sibBytes = read(ctx, sib.id, maxInterior + 1);
|
|
262
|
+
if (sibBytes.length === 0 || sibBytes.length > maxInterior)
|
|
263
|
+
continue;
|
|
264
|
+
const containers = junctionContainersFrom(ctx, sibBytes, right, sibBytes.length + right.length + maxInterior, junctionSeeds(ctx, sibBytes), rightSeeds, singleBudget, unordered);
|
|
265
|
+
for (const c of containers) {
|
|
266
|
+
keepBest(singleOut, c, "single-synonym", sib.score);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Right-side synonyms: containers of left+sibling. `left`'s seeds are
|
|
271
|
+
// likewise fixed across this loop.
|
|
272
|
+
if (s.rightId !== null) {
|
|
273
|
+
const leftSeeds = junctionSeeds(ctx, left);
|
|
274
|
+
for (const sib of s.rightSiblings) {
|
|
275
|
+
const sibBytes = read(ctx, sib.id, maxInterior + 1);
|
|
276
|
+
if (sibBytes.length === 0 || sibBytes.length > maxInterior)
|
|
277
|
+
continue;
|
|
278
|
+
const containers = junctionContainersFrom(ctx, left, sibBytes, left.length + sibBytes.length + maxInterior, leftSeeds, junctionSeeds(ctx, sibBytes), singleBudget, unordered);
|
|
279
|
+
for (const c of containers) {
|
|
280
|
+
keepBest(singleOut, c, "single-synonym", sib.score);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (singleOut.size > 0)
|
|
285
|
+
return [...singleOut.values()];
|
|
286
|
+
// ── Tier 2.5b: double-synonym — BOTH sides replaced, tried only when
|
|
287
|
+
// single-synonym found NOTHING. Every (leftSibling, rightSibling) pair,
|
|
288
|
+
// sorted deterministically, bounded to haloQueryK pairs total, ONE fresh
|
|
289
|
+
// shared budget for the whole tier. ─────────────────────────────────────
|
|
290
|
+
if (s.leftSiblings.length === 0 || s.rightSiblings.length === 0)
|
|
291
|
+
return [];
|
|
292
|
+
const pairs = [];
|
|
293
|
+
for (const l of s.leftSiblings) {
|
|
294
|
+
for (const r of s.rightSiblings) {
|
|
295
|
+
pairs.push({ l, r, confidence: Math.min(l.score, r.score) });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
pairs.sort((a, b) => b.confidence - a.confidence ||
|
|
299
|
+
a.l.id - b.l.id ||
|
|
300
|
+
a.r.id - b.r.id);
|
|
301
|
+
const doubleOut = new Map();
|
|
302
|
+
const budget = { n: hubBound(ctx) * ctx.space.maxGroup };
|
|
303
|
+
const tries = Math.min(pairs.length, ctx.cfg.haloQueryK);
|
|
304
|
+
for (let i = 0; i < tries; i++) {
|
|
305
|
+
const { l, r, confidence } = pairs[i];
|
|
306
|
+
const lBytes = read(ctx, l.id, maxInterior + 1);
|
|
307
|
+
const rBytes = read(ctx, r.id, maxInterior + 1);
|
|
308
|
+
if (lBytes.length === 0 || lBytes.length > maxInterior ||
|
|
309
|
+
rBytes.length === 0 || rBytes.length > maxInterior)
|
|
310
|
+
continue;
|
|
311
|
+
const containers = junctionContainersFrom(ctx, lBytes, rBytes, lBytes.length + rBytes.length + maxInterior, junctionSeeds(ctx, lBytes), junctionSeeds(ctx, rBytes), budget, unordered);
|
|
312
|
+
for (const c of containers) {
|
|
313
|
+
keepBest(doubleOut, c, "double-synonym", confidence);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return [...doubleOut.values()];
|
|
317
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Sema } from "../sema.js";
|
|
2
|
+
import type { Input, MindContext } from "./types.js";
|
|
3
|
+
/** Intern a perceived tree into node ids, bottom-up, sharing equal subtrees.
|
|
4
|
+
* Returns the root node id and a map from tree nodes to their ids.
|
|
5
|
+
*
|
|
6
|
+
* Memoized by NODE IDENTITY (ctx._internIds): the pyramid fold shares a
|
|
7
|
+
* prefix's subtree OBJECTS across an accumulated context's deposits, and a
|
|
8
|
+
* node already interned needs nothing again — its id is permanent
|
|
9
|
+
* (content-addressed) and its intern-time side effects (gist capture, kid
|
|
10
|
+
* rows) fired at first mint; re-interning was pure lookups. A memo hit
|
|
11
|
+
* therefore skips the WHOLE shared subtree, making the intern walk
|
|
12
|
+
* O(new nodes) per deposit instead of O(context). Only the hit node
|
|
13
|
+
* itself enters `ids`; descendants stay reachable via the memo (see
|
|
14
|
+
* idOf in indexSubSpans and the changedNodes prune). */
|
|
15
|
+
export declare function internTreeIds(ctx: MindContext, node: Sema, ids: Map<Sema, number>): Promise<number>;
|
|
16
|
+
/** Index flat branches for sub-spans of a deposit's byte stream, linked to
|
|
17
|
+
* their structural chunks via durable CONTAINMENT edges. */
|
|
18
|
+
export declare function indexSubSpans(ctx: MindContext, tree: Sema, ids: Map<Sema, number>): Promise<boolean>;
|
|
19
|
+
/** Perceive, intern, and index a single input. Returns the perceived tree,
|
|
20
|
+
* root id, id map, and the changed (new) subtrees for halo reinforcement. */
|
|
21
|
+
export declare function deposit(ctx: MindContext, input: Input, track: boolean, conversational?: boolean): Promise<{
|
|
22
|
+
tree: Sema;
|
|
23
|
+
rootId: number;
|
|
24
|
+
ids: Map<Sema, number>;
|
|
25
|
+
changed: Sema[];
|
|
26
|
+
}>;
|
|
27
|
+
/** Ingest a single input (a bare experience, no continuation). */
|
|
28
|
+
export declare function ingestOne(ctx: MindContext, input: Input): Promise<Sema & {
|
|
29
|
+
id: number;
|
|
30
|
+
}>;
|
|
31
|
+
/** Ingest a pair (context, continuation) — learn an edge and pour halos. */
|
|
32
|
+
export declare function ingestPair(ctx: MindContext, ctxInput: Input, cont: Input): Promise<void>;
|
|
33
|
+
/** Dispatch the public ingest input shapes onto one-input / pair handlers —
|
|
34
|
+
* THE one reading of ingest's polymorphic surface (scalar, (context,
|
|
35
|
+
* continuation) pair, or a list mixing bare inputs and pairs). Both ingest
|
|
36
|
+
* paths — the direct one below and {@link CachedIngest} — route through
|
|
37
|
+
* this, so the shape-detection can never drift between them again (the
|
|
38
|
+
* ingest cache once re-implemented it and drifted). */
|
|
39
|
+
export declare function dispatchIngest(input: Input | (Input | [Input, Input])[], second: Input | undefined, onOne: (input: Input) => Promise<Sema & {
|
|
40
|
+
id: number;
|
|
41
|
+
}>, onPair: (ctxInput: Input, cont: Input) => Promise<void>): Promise<(Sema & {
|
|
42
|
+
id: number;
|
|
43
|
+
}) | undefined>;
|
|
44
|
+
/** Ingest an input or array of inputs/pairs. The public ingest entry point. */
|
|
45
|
+
export declare function ingest(ctx: MindContext, input: Input | (Input | [Input, Input])[], second?: Input): Promise<(Sema & {
|
|
46
|
+
id: number;
|
|
47
|
+
}) | undefined>;
|