@hviana/sema 0.1.0 → 0.1.2
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 +2216 -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 +212 -0
- package/dist/src/alu/src/parser.js +469 -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/config.d.ts +114 -0
- package/dist/src/config.js +96 -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 +137 -0
- package/dist/src/geometry.js +430 -0
- package/dist/src/index.d.ts +15 -0
- package/dist/src/index.js +21 -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 +72 -0
- package/dist/src/mind/attention.js +894 -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 +270 -0
- package/dist/src/mind/graph-search.js +847 -0
- package/dist/src/mind/index.d.ts +5 -0
- package/dist/src/mind/index.js +5 -0
- package/dist/src/mind/junction.d.ts +95 -0
- package/dist/src/mind/junction.js +262 -0
- package/dist/src/mind/learning.d.ts +47 -0
- package/dist/src/mind/learning.js +201 -0
- package/dist/src/mind/match.d.ts +111 -0
- package/dist/src/mind/match.js +422 -0
- package/dist/src/mind/mechanisms/alu.d.ts +4 -0
- package/dist/src/mind/mechanisms/alu.js +29 -0
- package/dist/src/mind/mechanisms/cast.d.ts +35 -0
- package/dist/src/mind/mechanisms/cast.js +447 -0
- package/dist/src/mind/mechanisms/confluence.d.ts +24 -0
- package/dist/src/mind/mechanisms/confluence.js +213 -0
- package/dist/src/mind/mechanisms/cover.d.ts +6 -0
- package/dist/src/mind/mechanisms/cover.js +179 -0
- package/dist/src/mind/mechanisms/extraction.d.ts +67 -0
- package/dist/src/mind/mechanisms/extraction.js +342 -0
- package/dist/src/mind/mechanisms/recall.d.ts +13 -0
- package/dist/src/mind/mechanisms/recall.js +151 -0
- package/dist/src/mind/mind.d.ts +147 -0
- package/dist/src/mind/mind.js +300 -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 +185 -0
- package/dist/src/mind/primitives.d.ts +43 -0
- package/dist/src/mind/primitives.js +162 -0
- package/dist/src/mind/rationale.d.ts +134 -0
- package/dist/src/mind/rationale.js +162 -0
- package/dist/src/mind/reasoning.d.ts +15 -0
- package/dist/src/mind/reasoning.js +162 -0
- package/dist/src/mind/recognition.d.ts +20 -0
- package/dist/src/mind/recognition.js +223 -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 +100 -0
- package/dist/src/mind/traverse.js +447 -0
- package/dist/src/mind/types.d.ts +174 -0
- package/dist/src/mind/types.js +84 -0
- package/dist/src/rabitq-hnsw/src/database.d.ts +200 -0
- package/dist/src/rabitq-hnsw/src/database.js +388 -0
- package/dist/src/rabitq-hnsw/src/heap.d.ts +22 -0
- package/dist/src/rabitq-hnsw/src/heap.js +89 -0
- package/dist/src/rabitq-hnsw/src/hnsw.d.ts +125 -0
- package/dist/src/rabitq-hnsw/src/hnsw.js +474 -0
- package/dist/src/rabitq-hnsw/src/index.d.ts +10 -0
- package/dist/src/rabitq-hnsw/src/index.js +6 -0
- package/dist/src/rabitq-hnsw/src/prng.d.ts +19 -0
- package/dist/src/rabitq-hnsw/src/prng.js +36 -0
- package/dist/src/rabitq-hnsw/src/rabitq.d.ts +95 -0
- package/dist/src/rabitq-hnsw/src/rabitq.js +283 -0
- package/dist/src/rabitq-hnsw/src/store.d.ts +162 -0
- package/dist/src/rabitq-hnsw/src/store.js +825 -0
- package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +1 -0
- package/dist/src/rabitq-hnsw/test/hnsw.test.js +948 -0
- package/dist/src/store-sqlite.d.ts +149 -0
- package/dist/src/store-sqlite.js +702 -0
- package/dist/src/store.d.ts +638 -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,185 @@
|
|
|
1
|
+
// pipeline.ts — the think pipeline (Section 5 of the mind).
|
|
2
|
+
//
|
|
3
|
+
// think() is the whole file's job: one lightest-derivation choice among
|
|
4
|
+
// UNIFORM mechanisms. The pipeline sees mechanisms through the
|
|
5
|
+
// PipelineMechanism interface only — it never imports a mechanism-specific
|
|
6
|
+
// type and never has a special-case branch for any mechanism. Adding a
|
|
7
|
+
// mechanism means registering one object; removing one means dropping it
|
|
8
|
+
// from the list. The mechanisms themselves live in mechanisms/ (one file
|
|
9
|
+
// each); the shared pre-computation they exchange lives in Precomputed
|
|
10
|
+
// (pipeline-mechanism.ts).
|
|
11
|
+
import { PASS, STEP } from "./graph-search.js";
|
|
12
|
+
import { gistOf, resolve } from "./primitives.js";
|
|
13
|
+
import { recognise } from "./recognition.js";
|
|
14
|
+
import { fuseAttention, reason } from "./reasoning.js";
|
|
15
|
+
import { unexplainedSpans } from "./rationale.js";
|
|
16
|
+
import { rItem } from "./trace.js";
|
|
17
|
+
import { Precomputed } from "./pipeline-mechanism.js";
|
|
18
|
+
import { coverMechanism } from "./mechanisms/cover.js";
|
|
19
|
+
import { castMechanism } from "./mechanisms/cast.js";
|
|
20
|
+
import { confluenceMechanism } from "./mechanisms/confluence.js";
|
|
21
|
+
import { extractionMechanism } from "./mechanisms/extraction.js";
|
|
22
|
+
import { recallMechanism } from "./mechanisms/recall.js";
|
|
23
|
+
// Re-exports: cover's pre-resolution helpers and the ALU adapter kept
|
|
24
|
+
// importable from the pipeline module (their historical home).
|
|
25
|
+
export { resolveConcepts, resolveConnectors } from "./mechanisms/cover.js";
|
|
26
|
+
export { aluToMechanism } from "./mechanisms/alu.js";
|
|
27
|
+
// ── Extension dispatch (pre-loop parse) ─────────────────────────────────────
|
|
28
|
+
async function collectComputed(mechanisms, query) {
|
|
29
|
+
const out = [];
|
|
30
|
+
for (const m of mechanisms) {
|
|
31
|
+
if (m.parse)
|
|
32
|
+
out.push(...await m.parse(query));
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
// ── Built-in mechanisms ─────────────────────────────────────────────────────
|
|
37
|
+
// ORDER MATTERS, but only through the uniform floor/worthRunning pruning —
|
|
38
|
+
// no mechanism is special-cased. Cover runs FIRST: when a computed
|
|
39
|
+
// extension result (e.g. ALU) exists, cover masks it in at near-zero cost
|
|
40
|
+
// (see mechanisms/cover.ts), which becomes `best` before any other mechanism
|
|
41
|
+
// invests in its own precomputation. CAST's and confluence's floors (2*STEP,
|
|
42
|
+
// 3*STEP) then fail `worthRunning` and are skipped by the SAME admissible-
|
|
43
|
+
// floor pruning every mechanism is already subject to — not by asking
|
|
44
|
+
// "is this an extension?". Grade TIES keep the earlier candidate, so this
|
|
45
|
+
// order is also the tie-break priority: cover, cast, confluence, extraction,
|
|
46
|
+
// recall.
|
|
47
|
+
export const defaultMechanisms = [
|
|
48
|
+
coverMechanism,
|
|
49
|
+
castMechanism,
|
|
50
|
+
confluenceMechanism,
|
|
51
|
+
extractionMechanism,
|
|
52
|
+
recallMechanism,
|
|
53
|
+
];
|
|
54
|
+
/** Think: a single lightest-derivation exploration of the Sema graph.
|
|
55
|
+
*
|
|
56
|
+
* Every answer travels the same path:
|
|
57
|
+
* 1. Pre-computation — recognise, extension parse, guide; everything
|
|
58
|
+
* expensive stays lazy on Precomputed until a mechanism asks.
|
|
59
|
+
* 2. Grounding — every mechanism yields candidates weighed in the one
|
|
60
|
+
* cost ladder; the lightest grounding derivation wins.
|
|
61
|
+
* 3. Post-grounding — diagnostics (narrowDecision, thinGrounding),
|
|
62
|
+
* reasoning (multi-hop), fusion (multi-topic). */
|
|
63
|
+
export async function think(ctx, query, mechs) {
|
|
64
|
+
if (query.length === 0)
|
|
65
|
+
return null;
|
|
66
|
+
ctx._edgeGuide = gistOf(ctx, query);
|
|
67
|
+
ctx._edgeChoice.clear();
|
|
68
|
+
const t = ctx.trace?.enter("think", [rItem(query, "query")]);
|
|
69
|
+
const done = (answer, note) => {
|
|
70
|
+
t?.done(answer
|
|
71
|
+
? [rItem(answer, "answer", resolve(ctx, answer) ?? undefined)]
|
|
72
|
+
: [], note);
|
|
73
|
+
return answer;
|
|
74
|
+
};
|
|
75
|
+
// ── Pre-computation ──────────────────────────────────────────────────
|
|
76
|
+
const mechanisms = mechs ?? defaultMechanisms;
|
|
77
|
+
const rec = recognise(ctx, query);
|
|
78
|
+
// Phase 1: collect computed spans from mechanisms that implement parse()
|
|
79
|
+
const computed = await collectComputed(mechanisms, query);
|
|
80
|
+
if (computed.length > 0) {
|
|
81
|
+
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)`);
|
|
82
|
+
for (const u of computed) {
|
|
83
|
+
ctx.trace?.step("evalComputation", [rItem(query.subarray(u.i, u.j), "expression", undefined, [u.i, u.j])], [rItem(u.bytes, "result", resolve(ctx, u.bytes) ?? undefined)], "evaluate the recognised operation to its authoritative result");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Phase 2: the shared pre-computation container. Eager fields only
|
|
87
|
+
// (recognition, computed spans, guide) — every expensive analysis
|
|
88
|
+
// (consensus climb, weave, span-shape classification) is a lazily-cached
|
|
89
|
+
// method on Precomputed, first-touched by whichever mechanism's floor
|
|
90
|
+
// survives its cheap gates and the worthRunning check. A query no
|
|
91
|
+
// mechanism climbs for (e.g. one an extension decided) never climbs.
|
|
92
|
+
const pre = new Precomputed(ctx, query, rec, computed, ctx._edgeGuide);
|
|
93
|
+
const grade = (w) => Math.floor(w / STEP);
|
|
94
|
+
const unaccounted = (spans) => unexplainedSpans(query.length, spans)
|
|
95
|
+
.reduce((sum, [s, e]) => sum + (e - s), 0);
|
|
96
|
+
const weigh = (accounted, moves) => moves + PASS * unaccounted(accounted);
|
|
97
|
+
const candidates = [];
|
|
98
|
+
let best = null;
|
|
99
|
+
const consider = (c) => {
|
|
100
|
+
if (c.bytes.length === 0)
|
|
101
|
+
return;
|
|
102
|
+
candidates.push(c);
|
|
103
|
+
if (best === null || grade(c.weight) < grade(best.weight))
|
|
104
|
+
best = c;
|
|
105
|
+
};
|
|
106
|
+
const worthRunning = (floor) => best === null || grade(floor) < grade(best.weight);
|
|
107
|
+
// Phase 3: grounding loop
|
|
108
|
+
for (const mech of mechanisms) {
|
|
109
|
+
const floor = await mech.floor(ctx, query, pre, worthRunning);
|
|
110
|
+
if (floor === null) {
|
|
111
|
+
ctx.trace?.step("skipMechanism", [], [], `${mech.name} skipped — structural precondition failed`);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (!worthRunning(floor)) {
|
|
115
|
+
ctx.trace?.step("skipMechanism", [], [], `${mech.name} skipped — floor ${floor} cannot beat incumbent (grade ${grade(best.weight)})`);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const results = await mech.run(ctx, query, pre);
|
|
119
|
+
for (const r of results) {
|
|
120
|
+
const weight = r.weight ?? weigh(r.accounted, r.moves);
|
|
121
|
+
consider({
|
|
122
|
+
bytes: r.bytes,
|
|
123
|
+
provenance: r.provenance ?? mech.provenance,
|
|
124
|
+
weight,
|
|
125
|
+
used: r.used,
|
|
126
|
+
accounted: r.accounted,
|
|
127
|
+
unexplained: r.unexplained,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// (TS cannot see the closure assignments into `best` and narrows it to its
|
|
132
|
+
// initial null, so the read-back needs the assertion.)
|
|
133
|
+
const decided = best;
|
|
134
|
+
if (candidates.length > 1) {
|
|
135
|
+
ctx.trace?.step("decideGrounding", candidates.map((c) => rItem(c.bytes, `${c.provenance} (weight ${c.weight.toFixed(3)}${c.unexplained ? `, unexplained: "${c.unexplained}"` : ""})`)), decided ? [rItem(decided.bytes, decided.provenance)] : [], "the lightest grounding derivation wins — every mechanism weighed in the one cost ladder");
|
|
136
|
+
if (decided !== null) {
|
|
137
|
+
let runnerUp = null;
|
|
138
|
+
for (const c of candidates) {
|
|
139
|
+
if (c === decided)
|
|
140
|
+
continue;
|
|
141
|
+
if (runnerUp === null || grade(c.weight) < grade(runnerUp.weight)) {
|
|
142
|
+
runnerUp = c;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (runnerUp !== null) {
|
|
146
|
+
const margin = grade(runnerUp.weight) - grade(decided.weight);
|
|
147
|
+
if (margin <= 1) {
|
|
148
|
+
ctx.trace?.step("narrowDecision", [
|
|
149
|
+
rItem(decided.bytes, `${decided.provenance} (weight ${decided.weight.toFixed(3)})`),
|
|
150
|
+
], [
|
|
151
|
+
rItem(runnerUp.bytes, `${runnerUp.provenance} (weight ${runnerUp.weight.toFixed(3)})`),
|
|
152
|
+
], `margin ${margin} grade-unit(s) — the decision could change with one more training fact`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (decided === null) {
|
|
158
|
+
done(null, "no mechanism grounded an answer");
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
// Honesty density
|
|
162
|
+
{
|
|
163
|
+
const covered = query.length - unaccounted(decided.accounted);
|
|
164
|
+
const density = query.length > 0 ? covered / query.length : 1;
|
|
165
|
+
const thinBar = 1 / ctx.space.maxGroup;
|
|
166
|
+
if (density < thinBar) {
|
|
167
|
+
ctx.trace?.step("thinGrounding", [rItem(decided.bytes, decided.provenance)], [], `grounded but thin — density ${density.toFixed(3)} is below 1/W (${thinBar.toFixed(3)})`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const answer = decided.bytes;
|
|
171
|
+
const provenance = decided.provenance;
|
|
172
|
+
const castUsed = decided.used ?? new Set();
|
|
173
|
+
// ── Post-grounding, gated by provenance ──────────────────────────────
|
|
174
|
+
const preConsumed = provenance === "cast" || provenance === "join"
|
|
175
|
+
? castUsed
|
|
176
|
+
: provenance === "recall" || provenance === "recall-echo"
|
|
177
|
+
? new Set()
|
|
178
|
+
: new Set(recognise(ctx, answer).sites.map((s) => s.payload));
|
|
179
|
+
const reasoned = await reason(ctx, query, answer, preConsumed, pre);
|
|
180
|
+
const fused = provenance === "recall" || provenance === "recall-echo"
|
|
181
|
+
? await fuseAttention(ctx, query, reasoned, pre)
|
|
182
|
+
: reasoned;
|
|
183
|
+
done(fused, "grounded, reasoned forward, fused across points of attention");
|
|
184
|
+
return { bytes: fused, provenance };
|
|
185
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Vec } from "../vec.js";
|
|
2
|
+
import { Sema } from "../sema.js";
|
|
3
|
+
import type { Input, MindContext } from "./types.js";
|
|
4
|
+
/** Perceive input into a content-defined tree (the river fold).
|
|
5
|
+
* Deterministic — identical bytes always produce an identical tree. */
|
|
6
|
+
export declare function perceive(ctx: MindContext, input: Input, leafAt?: (i: number) => number | null, lookup?: (ids: number[]) => number | null): Sema;
|
|
7
|
+
/** The DEPOSIT-shaped perceive: the PLAIN fold (bit-identical to inference
|
|
8
|
+
* perception — that structural train/inference agreement is load-bearing
|
|
9
|
+
* for exact recall), computed INCREMENTALLY via the fold's level pyramid
|
|
10
|
+
* ({@link bytesToTreePyramid}). An accumulated context (a conversation)
|
|
11
|
+
* grows by suffixes: the previous context's pyramid is cached by CONTENT
|
|
12
|
+
* (ctx._depositTrees), and this deposit refolds only the right edge of
|
|
13
|
+
* each level — O(turn) instead of O(context) per turn. Purely a cache:
|
|
14
|
+
* the produced tree never depends on cache state. */
|
|
15
|
+
export declare function perceiveDeposit(ctx: MindContext, bytes: Uint8Array): Sema;
|
|
16
|
+
/** The raw bytes of an input — modality-neutral conversion. */
|
|
17
|
+
export declare function inputBytes(ctx: MindContext, input: Input): Uint8Array;
|
|
18
|
+
/** Convenience: the gist vector of a byte span. */
|
|
19
|
+
export declare function gistOf(ctx: MindContext, bytes: Uint8Array): Vec;
|
|
20
|
+
/** Fold a perceived tree bottom-up against the store's content-addressed maps:
|
|
21
|
+
* every leaf is named by findLeaf, every branch by findBranch over its kids'
|
|
22
|
+
* ids (null the moment any child is unknown). `visit`, when given, sees each
|
|
23
|
+
* node with its byte span and resolved id. Returns the node's byte end and
|
|
24
|
+
* resolved id. */
|
|
25
|
+
export declare function foldTree(ctx: MindContext, n: Sema, start: number, visit?: (n: Sema, start: number, end: number, node: number | null) => void): {
|
|
26
|
+
end: number;
|
|
27
|
+
node: number | null;
|
|
28
|
+
};
|
|
29
|
+
/** The canonical node id of a byte span: perceive it in isolation — the way
|
|
30
|
+
* training did — and recover its root bottom-up. Returns null if any part is
|
|
31
|
+
* unknown. */
|
|
32
|
+
export declare function resolve(ctx: MindContext, bytes: Uint8Array): number | null;
|
|
33
|
+
/** Walk a perceived tree in POST-ORDER with byte offsets — children before
|
|
34
|
+
* their parent, `visit(node, start, end)` for every node including leaves.
|
|
35
|
+
* Returns the byte end. The one shared traversal the offset-carrying tree
|
|
36
|
+
* readers (recognition via foldTree's richer variant, attention's region
|
|
37
|
+
* collection, resonance's branch counting) build on, so each does not
|
|
38
|
+
* re-derive the offset bookkeeping. (recognition.segment keeps its own
|
|
39
|
+
* walk: its flush semantics need PRE-order decisions at leaf-parents, which
|
|
40
|
+
* a post-order visitor cannot express.) */
|
|
41
|
+
export declare function walkTree(n: Sema, start: number, visit: (node: Sema, start: number, end: number) => void): number;
|
|
42
|
+
/** Reconstruct a node's byte content from the DAG, up to `maxLen` bytes. */
|
|
43
|
+
export declare function read(ctx: MindContext, id: number, maxLen?: number): Uint8Array;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// primitives.ts — Address + Read primitives (Section 1 of the mind).
|
|
2
|
+
//
|
|
3
|
+
// Address — bytes → node (perceive, foldTree, resolve)
|
|
4
|
+
// Read — node → bytes (read)
|
|
5
|
+
import { bytesToTree, bytesToTreePyramid, gridToTree, hilbertBytes, stackGrids, } from "../geometry.js";
|
|
6
|
+
import { ALL } from "./types.js";
|
|
7
|
+
// ── Address: bytes → node ──────────────────────────────────────────────
|
|
8
|
+
/** The content key of a byte span — one latin1 char per byte, an exact,
|
|
9
|
+
* collision-free encoding. Spans on the perception path are query-scale
|
|
10
|
+
* (windows, regions, candidate spans), so key construction is far cheaper
|
|
11
|
+
* than the river fold it deduplicates. */
|
|
12
|
+
function latin1Key(bytes) {
|
|
13
|
+
// Batched String.fromCharCode — avoids the O(n²) cost of repeated += on
|
|
14
|
+
// potentially-large query spans, and stays well under the ~65536 arg limit.
|
|
15
|
+
const n = bytes.length;
|
|
16
|
+
let s = "";
|
|
17
|
+
for (let i = 0; i < n; i += 4096) {
|
|
18
|
+
s += String.fromCharCode(...bytes.subarray(i, Math.min(i + 4096, n)));
|
|
19
|
+
}
|
|
20
|
+
return s;
|
|
21
|
+
}
|
|
22
|
+
/** Perceive input into a content-defined tree (the river fold).
|
|
23
|
+
* Deterministic — identical bytes always produce an identical tree. */
|
|
24
|
+
export function perceive(ctx, input, leafAt, lookup) {
|
|
25
|
+
if (typeof input === "string" || input instanceof Uint8Array) {
|
|
26
|
+
const bytes = typeof input === "string"
|
|
27
|
+
? new TextEncoder().encode(input)
|
|
28
|
+
: input;
|
|
29
|
+
if (leafAt === undefined && lookup === undefined) {
|
|
30
|
+
// Per-response memo (see MindContext.perceiveMemo): only the plain
|
|
31
|
+
// inference shape — raw bytes, no store capabilities — is memoised,
|
|
32
|
+
// keyed by CONTENT so byte-identical spans in fresh arrays still hit.
|
|
33
|
+
// The tree is shared by reference; Sema nodes are never mutated.
|
|
34
|
+
const memo = ctx.perceiveMemo;
|
|
35
|
+
if (memo) {
|
|
36
|
+
const key = latin1Key(bytes);
|
|
37
|
+
const hit = memo.get(key);
|
|
38
|
+
if (hit !== undefined)
|
|
39
|
+
return hit;
|
|
40
|
+
const tree = bytesToTree(ctx.space, ctx.alphabet, bytes);
|
|
41
|
+
memo.set(key, tree);
|
|
42
|
+
return tree;
|
|
43
|
+
}
|
|
44
|
+
return bytesToTree(ctx.space, ctx.alphabet, bytes);
|
|
45
|
+
}
|
|
46
|
+
return bytesToTree(ctx.space, ctx.alphabet, bytes, leafAt, lookup);
|
|
47
|
+
}
|
|
48
|
+
if (Array.isArray(input)) {
|
|
49
|
+
return gridToTree(ctx.space, ctx.alphabet, stackGrids(input));
|
|
50
|
+
}
|
|
51
|
+
return gridToTree(ctx.space, ctx.alphabet, input);
|
|
52
|
+
}
|
|
53
|
+
/** The DEPOSIT-shaped perceive: the PLAIN fold (bit-identical to inference
|
|
54
|
+
* perception — that structural train/inference agreement is load-bearing
|
|
55
|
+
* for exact recall), computed INCREMENTALLY via the fold's level pyramid
|
|
56
|
+
* ({@link bytesToTreePyramid}). An accumulated context (a conversation)
|
|
57
|
+
* grows by suffixes: the previous context's pyramid is cached by CONTENT
|
|
58
|
+
* (ctx._depositTrees), and this deposit refolds only the right edge of
|
|
59
|
+
* each level — O(turn) instead of O(context) per turn. Purely a cache:
|
|
60
|
+
* the produced tree never depends on cache state. */
|
|
61
|
+
export function perceiveDeposit(ctx, bytes) {
|
|
62
|
+
let prev;
|
|
63
|
+
// Longest cached PROPER prefix first.
|
|
64
|
+
const lens = [...ctx._depositLens]
|
|
65
|
+
.filter((L) => L >= 2 && L < bytes.length)
|
|
66
|
+
.sort((a, b) => b - a);
|
|
67
|
+
for (const L of lens) {
|
|
68
|
+
const hit = ctx._depositTrees.get(latin1Key(bytes.subarray(0, L)));
|
|
69
|
+
if (hit !== undefined) {
|
|
70
|
+
prev = hit;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const { tree, pyramid } = bytesToTreePyramid(ctx.space, ctx.alphabet, bytes, prev);
|
|
75
|
+
if (bytes.length >= 2) {
|
|
76
|
+
// The lengths set drifts as the map evicts; past the probe budget the
|
|
77
|
+
// drift itself becomes the cost (each stale length is an O(len) key
|
|
78
|
+
// build), so both reset together — losing only warm-up on live chains.
|
|
79
|
+
if (ctx._depositLens.size > 64) {
|
|
80
|
+
ctx._depositLens.clear();
|
|
81
|
+
ctx._depositTrees.clear();
|
|
82
|
+
}
|
|
83
|
+
ctx._depositTrees.set(latin1Key(bytes), pyramid);
|
|
84
|
+
ctx._depositLens.add(bytes.length);
|
|
85
|
+
}
|
|
86
|
+
return tree;
|
|
87
|
+
}
|
|
88
|
+
/** The raw bytes of an input — modality-neutral conversion. */
|
|
89
|
+
export function inputBytes(ctx, input) {
|
|
90
|
+
if (typeof input === "string")
|
|
91
|
+
return new TextEncoder().encode(input);
|
|
92
|
+
if (input instanceof Uint8Array)
|
|
93
|
+
return input;
|
|
94
|
+
if (Array.isArray(input))
|
|
95
|
+
return hilbertBytes(stackGrids(input));
|
|
96
|
+
return hilbertBytes(input);
|
|
97
|
+
}
|
|
98
|
+
/** Convenience: the gist vector of a byte span. */
|
|
99
|
+
export function gistOf(ctx, bytes) {
|
|
100
|
+
return perceive(ctx, bytes).v;
|
|
101
|
+
}
|
|
102
|
+
/** Fold a perceived tree bottom-up against the store's content-addressed maps:
|
|
103
|
+
* every leaf is named by findLeaf, every branch by findBranch over its kids'
|
|
104
|
+
* ids (null the moment any child is unknown). `visit`, when given, sees each
|
|
105
|
+
* node with its byte span and resolved id. Returns the node's byte end and
|
|
106
|
+
* resolved id. */
|
|
107
|
+
export function foldTree(ctx, n, start, visit) {
|
|
108
|
+
if (n.kids === null) {
|
|
109
|
+
const b = n.leaf ?? new Uint8Array(0);
|
|
110
|
+
const end = start + b.length;
|
|
111
|
+
const node = ctx.store.findLeaf(b);
|
|
112
|
+
visit?.(n, start, end, node);
|
|
113
|
+
return { end, node };
|
|
114
|
+
}
|
|
115
|
+
let pos = start;
|
|
116
|
+
let known = true;
|
|
117
|
+
const kids = [];
|
|
118
|
+
for (const k of n.kids) {
|
|
119
|
+
const r = foldTree(ctx, k, pos, visit);
|
|
120
|
+
if (r.node === null)
|
|
121
|
+
known = false;
|
|
122
|
+
else if (known)
|
|
123
|
+
kids.push(r.node);
|
|
124
|
+
pos = r.end;
|
|
125
|
+
}
|
|
126
|
+
const node = known ? ctx.store.findBranch(kids) : null;
|
|
127
|
+
visit?.(n, start, pos, node);
|
|
128
|
+
return { end: pos, node };
|
|
129
|
+
}
|
|
130
|
+
/** The canonical node id of a byte span: perceive it in isolation — the way
|
|
131
|
+
* training did — and recover its root bottom-up. Returns null if any part is
|
|
132
|
+
* unknown. */
|
|
133
|
+
export function resolve(ctx, bytes) {
|
|
134
|
+
if (bytes.length === 0)
|
|
135
|
+
return null;
|
|
136
|
+
return foldTree(ctx, perceive(ctx, bytes), 0).node;
|
|
137
|
+
}
|
|
138
|
+
/** Walk a perceived tree in POST-ORDER with byte offsets — children before
|
|
139
|
+
* their parent, `visit(node, start, end)` for every node including leaves.
|
|
140
|
+
* Returns the byte end. The one shared traversal the offset-carrying tree
|
|
141
|
+
* readers (recognition via foldTree's richer variant, attention's region
|
|
142
|
+
* collection, resonance's branch counting) build on, so each does not
|
|
143
|
+
* re-derive the offset bookkeeping. (recognition.segment keeps its own
|
|
144
|
+
* walk: its flush semantics need PRE-order decisions at leaf-parents, which
|
|
145
|
+
* a post-order visitor cannot express.) */
|
|
146
|
+
export function walkTree(n, start, visit) {
|
|
147
|
+
if (n.kids === null) {
|
|
148
|
+
const end = start + (n.leaf?.length ?? 0);
|
|
149
|
+
visit(n, start, end);
|
|
150
|
+
return end;
|
|
151
|
+
}
|
|
152
|
+
let pos = start;
|
|
153
|
+
for (const k of n.kids)
|
|
154
|
+
pos = walkTree(k, pos, visit);
|
|
155
|
+
visit(n, start, pos);
|
|
156
|
+
return pos;
|
|
157
|
+
}
|
|
158
|
+
// ── Read: node → bytes ──────────────────────────────────────────────────
|
|
159
|
+
/** Reconstruct a node's byte content from the DAG, up to `maxLen` bytes. */
|
|
160
|
+
export function read(ctx, id, maxLen = ALL) {
|
|
161
|
+
return ctx.store.bytesPrefix(id, maxLen);
|
|
162
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { Vec } from "../vec.js";
|
|
2
|
+
/** One element of a step's input or output vector.
|
|
3
|
+
*
|
|
4
|
+
* Modality-neutral and deliberately partial: an element might be a byte span of
|
|
5
|
+
* the query, a resolved graph node, a resonance hit with its score, a spliced
|
|
6
|
+
* connector — so every descriptive field is optional and a mechanism fills only
|
|
7
|
+
* the ones that carry meaning for what it did. `text` is always present (the
|
|
8
|
+
* human-readable rendering); the rest is provenance a debugger can lean on. */
|
|
9
|
+
export interface RationaleItem {
|
|
10
|
+
/** Human-readable rendering — decoded text for a byte span, else a label like
|
|
11
|
+
* "‹none›" or an operator name. Always set, so a step always reads. */
|
|
12
|
+
text: string;
|
|
13
|
+
/** The graph node this element is, or resolved to, when known — the handle to
|
|
14
|
+
* point back at the exact stored fact in the content-addressed DAG. */
|
|
15
|
+
node?: number;
|
|
16
|
+
/** The `[start, end)` span this element occupies in its step's frame of
|
|
17
|
+
* reference (usually the query or the answer being composed). */
|
|
18
|
+
span?: [number, number];
|
|
19
|
+
/** The resonance / cosine score that selected this element, when it was chosen
|
|
20
|
+
* by similarity rather than by exact structure. */
|
|
21
|
+
score?: number;
|
|
22
|
+
/** A short role tag — "query", "leaf", "form", "hit", "connector", "answer",
|
|
23
|
+
* … — naming what KIND of element this is within the step. */
|
|
24
|
+
role?: string;
|
|
25
|
+
/** The gist vector, only when the element fundamentally IS a vector and a
|
|
26
|
+
* caller asked to carry it (off by default — a D-float array per item would
|
|
27
|
+
* bury the reasoning it is meant to explain). */
|
|
28
|
+
v?: Vec;
|
|
29
|
+
}
|
|
30
|
+
/** A single completed act of inference — one mechanism, run once.
|
|
31
|
+
*
|
|
32
|
+
* Steps are emitted in COMPLETION order (a sub-mechanism finishes, and is
|
|
33
|
+
* reported, before the mechanism that called it), while `index` is assigned in
|
|
34
|
+
* ENTRY order (a parent reserves its index before its children run). So a
|
|
35
|
+
* parent's `index` is always lower than its children's, and the two orderings
|
|
36
|
+
* together give a valid topological reading of the dependency graph. */
|
|
37
|
+
export interface RationaleStep {
|
|
38
|
+
/** This step's index, assigned when the mechanism was ENTERED — a strict,
|
|
39
|
+
* incremental ordering over the whole inference. */
|
|
40
|
+
index: number;
|
|
41
|
+
/** The mechanism and its enclosing mechanisms, outermost → innermost, e.g.
|
|
42
|
+
* `["respond", "think", "recognise"]`. The last entry is this step; the
|
|
43
|
+
* prefix is the nest of sub-mechanisms it ran inside. */
|
|
44
|
+
mechanism: string[];
|
|
45
|
+
/** The enclosing mechanism's step index, or -1 at the root — the NESTING edge
|
|
46
|
+
* of the dependency graph (which step this one is a part of). */
|
|
47
|
+
parent: number;
|
|
48
|
+
/** The earlier steps whose OUTPUTS became this step's inputs — the DATA-FLOW
|
|
49
|
+
* edges of the dependency graph. Defaults to the previous sibling (the step
|
|
50
|
+
* run just before this one inside the same mechanism), or the parent when
|
|
51
|
+
* this is the first sub-step; a mechanism that fuses several earlier results
|
|
52
|
+
* names them all explicitly. */
|
|
53
|
+
dependsOn: number[];
|
|
54
|
+
/** The vector of elements handed to the mechanism (one or more). */
|
|
55
|
+
inputs: RationaleItem[];
|
|
56
|
+
/** The vector of elements the mechanism produced (one or more) — longer than
|
|
57
|
+
* `inputs` when it decomposed, shorter when it combined. */
|
|
58
|
+
outputs: RationaleItem[];
|
|
59
|
+
/** A one-line, human account of what the mechanism did and why — the sentence
|
|
60
|
+
* that turns the data into an explanation. */
|
|
61
|
+
note?: string;
|
|
62
|
+
}
|
|
63
|
+
/** The callback {@link Mind.respond} / {@link Mind.respondText} accept. It is
|
|
64
|
+
* invoked once per completed step, AS the inference unfolds — never batched at
|
|
65
|
+
* the end — so a caller can stream the reasoning live or accumulate it. */
|
|
66
|
+
export type InspectRationale = (step: RationaleStep) => void;
|
|
67
|
+
/** Decode bytes to text for display, dropping the NUL padding the encoder uses
|
|
68
|
+
* (the same cleanup {@link Mind.respondText} does for its result). */
|
|
69
|
+
export declare function decodeText(bytes: Uint8Array): string;
|
|
70
|
+
/** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
|
|
71
|
+
* the same union-of-spans reading think's grounding decider prices at PASS
|
|
72
|
+
* per byte, exposed here so a mechanism can turn it into a human label. */
|
|
73
|
+
export declare function unexplainedSpans(queryLen: number, accounted: ReadonlyArray<[number, number]>): Array<[number, number]>;
|
|
74
|
+
/** A human-readable label for the query bytes a mechanism's `accounted`
|
|
75
|
+
* spans leave unexplained — purely diagnostic (Task 2's negative evidence):
|
|
76
|
+
* it never changes a candidate's weight, only what the rationale trace
|
|
77
|
+
* says the mechanism left on the table. `""` when nothing is unexplained. */
|
|
78
|
+
export declare function unexplainedLabel(query: Uint8Array, accounted: ReadonlyArray<[number, number]>): string;
|
|
79
|
+
/** An open mechanism — the handle {@link Rationale.enter} returns. Hold it for
|
|
80
|
+
* the duration of the mechanism and call {@link Scope.done} with the outputs
|
|
81
|
+
* when it finishes; that emits the step and pops the nesting. */
|
|
82
|
+
export interface Scope {
|
|
83
|
+
/** The step index reserved for this mechanism at entry — pass it as an
|
|
84
|
+
* explicit dependency of a later step that consumes this one's output. */
|
|
85
|
+
readonly index: number;
|
|
86
|
+
/** Close the mechanism: emit its step with these outputs and pop it off the
|
|
87
|
+
* nesting stack. Idempotent — a second call is ignored, so a `finally` that
|
|
88
|
+
* closes after an early return is safe. */
|
|
89
|
+
done(outputs: RationaleItem[], note?: string): void;
|
|
90
|
+
}
|
|
91
|
+
/** The live tracer: a stack of open mechanisms over one {@link Mind.respond}.
|
|
92
|
+
*
|
|
93
|
+
* Sema's inference is single-threaded and strictly sequential — every async
|
|
94
|
+
* step is awaited before the next begins, and `respond` holds no two thoughts
|
|
95
|
+
* at once — so a plain stack exactly tracks the current nesting: {@link enter}
|
|
96
|
+
* pushes, {@link Scope.done} pops, and {@link step} (a mechanism with no
|
|
97
|
+
* sub-steps) is the two fused. The tracer never branches the control flow; it
|
|
98
|
+
* only records it. */
|
|
99
|
+
export declare class Rationale {
|
|
100
|
+
private readonly sink;
|
|
101
|
+
private next;
|
|
102
|
+
/** Open mechanisms, outermost first. Each frame remembers the last child it
|
|
103
|
+
* has spawned so the next sibling can default its data-flow edge to it. */
|
|
104
|
+
private readonly stack;
|
|
105
|
+
/** The most recent step index emitted under each mechanism name — the handle
|
|
106
|
+
* a later step uses to name an EARLIER mechanism as its data-flow producer
|
|
107
|
+
* (e.g. cover depends on the latest recognise / computeExtensions). One tracer is
|
|
108
|
+
* built per response and inference is sequential, so "most recent" is exactly
|
|
109
|
+
* "the one that produced the inputs I am about to consume". */
|
|
110
|
+
private readonly lastByName;
|
|
111
|
+
constructor(sink: InspectRationale);
|
|
112
|
+
/** The index of the most recent step with this mechanism name, or undefined if
|
|
113
|
+
* none has run. Used to wire an explicit producer edge into {@link
|
|
114
|
+
* Scope.done} / {@link step}'s `deps`. */
|
|
115
|
+
lastIndex(name: string): number | undefined;
|
|
116
|
+
/** The mechanism names currently open, outermost → innermost. */
|
|
117
|
+
private path;
|
|
118
|
+
/** The default data-flow edge for a step entering now: the previous sibling
|
|
119
|
+
* inside the current mechanism, else the enclosing mechanism, else nothing
|
|
120
|
+
* (the root). An explicit `deps` overrides this. */
|
|
121
|
+
private defaultDeps;
|
|
122
|
+
/** Reserve this step's index and register it as the current mechanism's most
|
|
123
|
+
* recent child (so the NEXT sibling chains to it) and as the most recent step
|
|
124
|
+
* of its own NAME (so a later mechanism can name it as a producer). */
|
|
125
|
+
private reserve;
|
|
126
|
+
private emit;
|
|
127
|
+
/** Enter a mechanism that has sub-steps. Captures its inputs and the nesting
|
|
128
|
+
* now; the matching {@link Scope.done} supplies the outputs when it finishes.
|
|
129
|
+
* `deps` overrides the default data-flow edge (previous sibling / parent). */
|
|
130
|
+
enter(name: string, inputs: RationaleItem[], deps?: number[]): Scope;
|
|
131
|
+
/** Record a mechanism that has no sub-steps — its inputs and outputs are both
|
|
132
|
+
* known at the call site. Returns its index, for a later step to depend on. */
|
|
133
|
+
step(name: string, inputs: RationaleItem[], outputs: RationaleItem[], note?: string, deps?: number[]): number;
|
|
134
|
+
}
|