@hviana/sema 0.5.2 → 0.5.4

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.
Files changed (157) hide show
  1. package/AGENTS.md +115 -53
  2. package/HOW_IT_WORKS.md +291 -184
  3. package/jsr.json +6 -0
  4. package/package.json +1 -1
  5. package/src/mind/bridge.ts +10 -104
  6. package/src/mind/match.ts +497 -7
  7. package/src/mind/{prefix-completion.ts → mechanisms/prefix-completion.ts} +66 -92
  8. package/src/mind/mechanisms/recall.ts +38 -129
  9. package/src/mind/mechanisms/reference.ts +343 -0
  10. package/src/mind/mind.ts +12 -8
  11. package/src/mind/pipeline-mechanism.ts +120 -3
  12. package/src/mind/pipeline.ts +16 -2
  13. package/src/mind/traverse.ts +92 -1
  14. package/src/store.ts +13 -4
  15. package/test/33-multi-candidate.test.mjs +21 -11
  16. package/test/70-prefix-completion.test.mjs +1 -1
  17. package/test/72-prefix-candidate-supply.test.mjs +7 -9
  18. package/test/74-prefix-trap-not-sprung-early.test.mjs +1 -1
  19. package/test/76-reference-binding.test.mjs +543 -0
  20. package/dist/example/demo.d.ts +0 -1
  21. package/dist/example/demo.js +0 -39
  22. package/dist/example/train_base.d.ts +0 -93
  23. package/dist/example/train_base.js +0 -2269
  24. package/dist/src/alphabet.d.ts +0 -7
  25. package/dist/src/alphabet.js +0 -33
  26. package/dist/src/alu/src/alu.d.ts +0 -185
  27. package/dist/src/alu/src/alu.js +0 -440
  28. package/dist/src/alu/src/expr.d.ts +0 -61
  29. package/dist/src/alu/src/expr.js +0 -318
  30. package/dist/src/alu/src/index.d.ts +0 -11
  31. package/dist/src/alu/src/index.js +0 -19
  32. package/dist/src/alu/src/kernel-arith.d.ts +0 -16
  33. package/dist/src/alu/src/kernel-arith.js +0 -264
  34. package/dist/src/alu/src/kernel-bits.d.ts +0 -19
  35. package/dist/src/alu/src/kernel-bits.js +0 -152
  36. package/dist/src/alu/src/kernel-logic.d.ts +0 -4
  37. package/dist/src/alu/src/kernel-logic.js +0 -60
  38. package/dist/src/alu/src/kernel-nd.d.ts +0 -3
  39. package/dist/src/alu/src/kernel-nd.js +0 -208
  40. package/dist/src/alu/src/kernel-numeric.d.ts +0 -54
  41. package/dist/src/alu/src/kernel-numeric.js +0 -366
  42. package/dist/src/alu/src/operation.d.ts +0 -168
  43. package/dist/src/alu/src/operation.js +0 -189
  44. package/dist/src/alu/src/parser.d.ts +0 -221
  45. package/dist/src/alu/src/parser.js +0 -577
  46. package/dist/src/alu/src/resonance.d.ts +0 -55
  47. package/dist/src/alu/src/resonance.js +0 -126
  48. package/dist/src/alu/src/text.d.ts +0 -31
  49. package/dist/src/alu/src/text.js +0 -73
  50. package/dist/src/alu/src/value.d.ts +0 -109
  51. package/dist/src/alu/src/value.js +0 -300
  52. package/dist/src/alu/test/alu.test.d.ts +0 -1
  53. package/dist/src/alu/test/alu.test.js +0 -764
  54. package/dist/src/bytes.d.ts +0 -14
  55. package/dist/src/bytes.js +0 -59
  56. package/dist/src/canon.d.ts +0 -45
  57. package/dist/src/canon.js +0 -85
  58. package/dist/src/config.d.ts +0 -111
  59. package/dist/src/config.js +0 -91
  60. package/dist/src/derive/src/deduction.d.ts +0 -136
  61. package/dist/src/derive/src/deduction.js +0 -159
  62. package/dist/src/derive/src/index.d.ts +0 -8
  63. package/dist/src/derive/src/index.js +0 -11
  64. package/dist/src/derive/src/priority-queue.d.ts +0 -20
  65. package/dist/src/derive/src/priority-queue.js +0 -73
  66. package/dist/src/derive/src/rewrite.d.ts +0 -56
  67. package/dist/src/derive/src/rewrite.js +0 -100
  68. package/dist/src/derive/src/trie.d.ts +0 -90
  69. package/dist/src/derive/src/trie.js +0 -217
  70. package/dist/src/derive/test/derive.test.d.ts +0 -1
  71. package/dist/src/derive/test/derive.test.js +0 -122
  72. package/dist/src/extension.d.ts +0 -37
  73. package/dist/src/extension.js +0 -7
  74. package/dist/src/geometry.d.ts +0 -233
  75. package/dist/src/geometry.js +0 -1089
  76. package/dist/src/index.d.ts +0 -17
  77. package/dist/src/index.js +0 -19
  78. package/dist/src/ingest-cache.d.ts +0 -41
  79. package/dist/src/ingest-cache.js +0 -165
  80. package/dist/src/meter.d.ts +0 -176
  81. package/dist/src/meter.js +0 -274
  82. package/dist/src/mind/articulation.d.ts +0 -6
  83. package/dist/src/mind/articulation.js +0 -99
  84. package/dist/src/mind/attention.d.ts +0 -430
  85. package/dist/src/mind/attention.js +0 -2418
  86. package/dist/src/mind/bridge.d.ts +0 -39
  87. package/dist/src/mind/bridge.js +0 -998
  88. package/dist/src/mind/canonical.d.ts +0 -34
  89. package/dist/src/mind/canonical.js +0 -93
  90. package/dist/src/mind/frame-filler.d.ts +0 -15
  91. package/dist/src/mind/frame-filler.js +0 -535
  92. package/dist/src/mind/graph-search.d.ts +0 -294
  93. package/dist/src/mind/graph-search.js +0 -996
  94. package/dist/src/mind/index.d.ts +0 -9
  95. package/dist/src/mind/index.js +0 -5
  96. package/dist/src/mind/junction.d.ts +0 -137
  97. package/dist/src/mind/junction.js +0 -358
  98. package/dist/src/mind/learning.d.ts +0 -75
  99. package/dist/src/mind/learning.js +0 -265
  100. package/dist/src/mind/match.d.ts +0 -192
  101. package/dist/src/mind/match.js +0 -666
  102. package/dist/src/mind/mechanisms/alu.d.ts +0 -4
  103. package/dist/src/mind/mechanisms/alu.js +0 -36
  104. package/dist/src/mind/mechanisms/cast.d.ts +0 -89
  105. package/dist/src/mind/mechanisms/cast.js +0 -1186
  106. package/dist/src/mind/mechanisms/confluence.d.ts +0 -24
  107. package/dist/src/mind/mechanisms/confluence.js +0 -271
  108. package/dist/src/mind/mechanisms/cover.d.ts +0 -6
  109. package/dist/src/mind/mechanisms/cover.js +0 -232
  110. package/dist/src/mind/mechanisms/extraction.d.ts +0 -33
  111. package/dist/src/mind/mechanisms/extraction.js +0 -327
  112. package/dist/src/mind/mechanisms/recall.d.ts +0 -16
  113. package/dist/src/mind/mechanisms/recall.js +0 -558
  114. package/dist/src/mind/mind.d.ts +0 -386
  115. package/dist/src/mind/mind.js +0 -754
  116. package/dist/src/mind/pipeline-mechanism.d.ts +0 -183
  117. package/dist/src/mind/pipeline-mechanism.js +0 -521
  118. package/dist/src/mind/pipeline.d.ts +0 -49
  119. package/dist/src/mind/pipeline.js +0 -331
  120. package/dist/src/mind/prefix-completion.d.ts +0 -59
  121. package/dist/src/mind/prefix-completion.js +0 -270
  122. package/dist/src/mind/primitives.d.ts +0 -85
  123. package/dist/src/mind/primitives.js +0 -333
  124. package/dist/src/mind/rationale.d.ts +0 -139
  125. package/dist/src/mind/rationale.js +0 -163
  126. package/dist/src/mind/reasoning.d.ts +0 -42
  127. package/dist/src/mind/reasoning.js +0 -305
  128. package/dist/src/mind/recognition.d.ts +0 -20
  129. package/dist/src/mind/recognition.js +0 -696
  130. package/dist/src/mind/resonance.d.ts +0 -35
  131. package/dist/src/mind/resonance.js +0 -365
  132. package/dist/src/mind/trace.d.ts +0 -15
  133. package/dist/src/mind/trace.js +0 -73
  134. package/dist/src/mind/traverse.d.ts +0 -158
  135. package/dist/src/mind/traverse.js +0 -716
  136. package/dist/src/mind/types.d.ts +0 -418
  137. package/dist/src/mind/types.js +0 -179
  138. package/dist/src/rabitq-ivf/src/database.d.ts +0 -113
  139. package/dist/src/rabitq-ivf/src/database.js +0 -201
  140. package/dist/src/rabitq-ivf/src/index.d.ts +0 -7
  141. package/dist/src/rabitq-ivf/src/index.js +0 -4
  142. package/dist/src/rabitq-ivf/src/ivf.d.ts +0 -200
  143. package/dist/src/rabitq-ivf/src/ivf.js +0 -1165
  144. package/dist/src/rabitq-ivf/src/prng.d.ts +0 -19
  145. package/dist/src/rabitq-ivf/src/prng.js +0 -36
  146. package/dist/src/rabitq-ivf/src/rabitq.d.ts +0 -95
  147. package/dist/src/rabitq-ivf/src/rabitq.js +0 -313
  148. package/dist/src/sema.d.ts +0 -41
  149. package/dist/src/sema.js +0 -77
  150. package/dist/src/store-sqlite.d.ts +0 -184
  151. package/dist/src/store-sqlite.js +0 -942
  152. package/dist/src/store.d.ts +0 -762
  153. package/dist/src/store.js +0 -1823
  154. package/dist/src/vec.d.ts +0 -31
  155. package/dist/src/vec.js +0 -109
  156. package/src/mind/frame-filler.ts +0 -604
  157. package/test/69-frame-filler.test.mjs +0 -115
@@ -1,85 +0,0 @@
1
- import { Vec } from "../vec.js";
2
- import { Sema } from "../sema.js";
3
- import type { Input, MindContext } from "./types.js";
4
- /** The content key of a byte span — one latin1 char per byte, an exact,
5
- * collision-free encoding. Spans on the perception path are query-scale
6
- * (windows, regions, candidate spans), so key construction is far cheaper
7
- * than the river fold it deduplicates. */
8
- export declare function latin1Key(bytes: Uint8Array): string;
9
- /** The {@link perceive} memo key: the span's content PLUS the boundary set it
10
- * was folded under. The tree is a function of BOTH — the same bytes fold
11
- * plainly with no boundaries and into a left-nested stable-prefix shape with
12
- * them — so a content-only key returns whichever shape was computed first.
13
- * That is exactly what happened: a conversation seeded its cumulative context
14
- * under the content key, and every later plain `perceive` of those bytes was
15
- * served the boundary tree instead (measured: respondTurn answered where
16
- * respond() on byte-identical input did not). NUL separates the two parts —
17
- * the boundary rendering is digits and commas, so no content byte can forge
18
- * the split. */
19
- export declare function perceiveKey(bytes: Uint8Array, boundaries?: readonly number[]): string;
20
- /** Perceive input into a content-defined tree (the river fold).
21
- * Deterministic — identical bytes always produce an identical tree.
22
- *
23
- * `boundaries` is an optional sorted list of proper byte offsets where the
24
- * fold must split so that each prefix segment folds identically to how it
25
- * folded when it was learned (§10.3 stable-prefix contract). Only the
26
- * CALLER — who assembled the multi-turn context — knows where those
27
- * boundaries are; the geometry never guesses them from the bytes. */
28
- export declare function perceive(ctx: MindContext, input: Input, leafAt?: (i: number) => number | null, lookup?: (ids: number[]) => number | null, boundaries?: readonly number[]): Sema;
29
- /** The DEPOSIT-shaped perceive. Folds over the stream's own content cuts —
30
- * bit-identical to what inference computes for the same bytes. That
31
- * train/inference agreement is the whole contract: the trained context node
32
- * and the node `resolve(query)` reaches must be the SAME node, and the only
33
- * way to guarantee it is to give this function nothing extra to say. It
34
- * imposes no boundaries, knows nothing about turns, and reads no convention
35
- * out of the bytes.
36
- *
37
- * An input that EXTENDS a previously deposited one — a conversation context
38
- * grown by a turn, or a resumed replay — reuses that deposit's already-folded
39
- * content segments ({@link contentFoldIncremental}), so it costs O(new bytes)
40
- * instead of O(context). The reuse is TRANSPARENT by construction: a segment
41
- * is a pure function of its own bytes, so a reused one is bit-identical to a
42
- * refolded one. Nothing has to prove that the extending deposit is "really"
43
- * a next turn — a coincidental byte prefix reuses the same segments and gets
44
- * the same tree it would have got anyway. (It used to matter: while this
45
- * path imposed turn BOUNDARIES, a wrong guess changed the tree, so the cache
46
- * needed a continuation-bytes proof to gate it. Nothing is imposed now, so
47
- * there is nothing to gate.) */
48
- export declare function perceiveDeposit(ctx: MindContext, bytes: Uint8Array, conversational?: boolean): Sema;
49
- /** The raw bytes of an input — modality-neutral conversion. */
50
- export declare function inputBytes(ctx: MindContext, input: Input): Uint8Array;
51
- /** Convenience: the gist vector of a byte span. */
52
- export declare function gistOf(ctx: MindContext, bytes: Uint8Array): Vec;
53
- /** Fold a perceived tree bottom-up against the store's content-addressed maps:
54
- * every leaf is named by findLeaf, every branch by findBranch over its kids'
55
- * ids (null the moment any child is unknown). `visit`, when given, sees each
56
- * node with its byte span and resolved id. Returns the node's byte end and
57
- * resolved id. */
58
- export declare function foldTree(ctx: MindContext, n: Sema, start: number, visit?: (n: Sema, start: number, end: number, node: number | null) => void): {
59
- end: number;
60
- node: number | null;
61
- };
62
- /** The canonical node id of a byte span: perceive it in isolation — the way
63
- * training did — and recover its root bottom-up. Returns null if any part is
64
- * unknown. */
65
- export declare function resolve(ctx: MindContext, bytes: Uint8Array): number | null;
66
- /** Equivalence-class resolution: when the exact content-addressed lookup
67
- * misses, find a stored node whose CANONICAL key equals the span's — the
68
- * store's canon index proposes candidates by key hash, and each is verified
69
- * by re-canonicalizing its bytes (hash-then-verify, like every content
70
- * lookup). Among verified candidates, one that leads somewhere (has a
71
- * continuation edge) is preferred; ties break to the lowest id — a corpus
72
- * property, not a seed property. Null when the response carries no
73
- * canonicalizer, the store has no canon index, or nothing verifies. */
74
- export declare function canonResolve(ctx: MindContext, bytes: Uint8Array): number | null;
75
- /** Walk a perceived tree in POST-ORDER with byte offsets — children before
76
- * their parent, `visit(node, start, end)` for every node including leaves.
77
- * Returns the byte end. The one shared traversal the offset-carrying tree
78
- * readers (recognition via foldTree's richer variant, attention's region
79
- * collection, resonance's branch counting) build on, so each does not
80
- * re-derive the offset bookkeeping. (recognition.segment keeps its own
81
- * walk: its flush semantics need PRE-order decisions at leaf-parents, which
82
- * a post-order visitor cannot express.) */
83
- export declare function walkTree(n: Sema, start: number, visit: (node: Sema, start: number, end: number) => void): number;
84
- /** Reconstruct a node's byte content from the DAG, up to `maxLen` bytes. */
85
- export declare function read(ctx: MindContext, id: number, maxLen?: number): Uint8Array;
@@ -1,333 +0,0 @@
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, contentFoldIncremental, gridToTree, hilbertBytes, stackGrids, } from "../geometry.js";
6
- import { canonHash } from "../canon.js";
7
- import { bytesEqual } from "../bytes.js";
8
- import { ALL } from "./types.js";
9
- // ── Address: bytes → node ──────────────────────────────────────────────
10
- /** The content key of a byte span — one latin1 char per byte, an exact,
11
- * collision-free encoding. Spans on the perception path are query-scale
12
- * (windows, regions, candidate spans), so key construction is far cheaper
13
- * than the river fold it deduplicates. */
14
- export function latin1Key(bytes) {
15
- // Batched String.fromCharCode — avoids the O(n²) cost of repeated += on
16
- // potentially-large query spans, and stays well under the ~65536 arg limit.
17
- const n = bytes.length;
18
- let s = "";
19
- for (let i = 0; i < n; i += 4096) {
20
- s += String.fromCharCode(...bytes.subarray(i, Math.min(i + 4096, n)));
21
- }
22
- return s;
23
- }
24
- /** The {@link perceive} memo key: the span's content PLUS the boundary set it
25
- * was folded under. The tree is a function of BOTH — the same bytes fold
26
- * plainly with no boundaries and into a left-nested stable-prefix shape with
27
- * them — so a content-only key returns whichever shape was computed first.
28
- * That is exactly what happened: a conversation seeded its cumulative context
29
- * under the content key, and every later plain `perceive` of those bytes was
30
- * served the boundary tree instead (measured: respondTurn answered where
31
- * respond() on byte-identical input did not). NUL separates the two parts —
32
- * the boundary rendering is digits and commas, so no content byte can forge
33
- * the split. */
34
- export function perceiveKey(bytes, boundaries) {
35
- const k = latin1Key(bytes);
36
- return boundaries === undefined || boundaries.length === 0
37
- ? k
38
- : k + "\u0000" + boundaries.join(",");
39
- }
40
- /** Perceive input into a content-defined tree (the river fold).
41
- * Deterministic — identical bytes always produce an identical tree.
42
- *
43
- * `boundaries` is an optional sorted list of proper byte offsets where the
44
- * fold must split so that each prefix segment folds identically to how it
45
- * folded when it was learned (§10.3 stable-prefix contract). Only the
46
- * CALLER — who assembled the multi-turn context — knows where those
47
- * boundaries are; the geometry never guesses them from the bytes. */
48
- export function perceive(ctx, input, leafAt, lookup, boundaries) {
49
- if (typeof input === "string" || input instanceof Uint8Array) {
50
- const bytes = typeof input === "string"
51
- ? new TextEncoder().encode(input)
52
- : input;
53
- if (leafAt === undefined && lookup === undefined) {
54
- // Per-response memo (see MindContext.perceiveMemo): only the plain
55
- // inference shape — raw bytes, no store capabilities — is memoised,
56
- // keyed by CONTENT so byte-identical spans in fresh arrays still hit.
57
- // The tree is shared by reference; Sema nodes are never mutated.
58
- const memo = ctx.perceiveMemo;
59
- if (memo) {
60
- const key = perceiveKey(bytes, boundaries);
61
- const hit = memo.get(key);
62
- if (hit !== undefined) {
63
- if (ctx.meter)
64
- ctx.meter.perceiveHits++;
65
- return hit;
66
- }
67
- if (ctx.meter) {
68
- ctx.meter.perceptions++;
69
- ctx.meter.perceivedBytes += bytes.length;
70
- }
71
- const tree = bytesToTree(ctx.space, ctx.alphabet, bytes, undefined, undefined, boundaries);
72
- memo.set(key, tree);
73
- return tree;
74
- }
75
- if (ctx.meter) {
76
- ctx.meter.perceptions++;
77
- ctx.meter.perceivedBytes += bytes.length;
78
- }
79
- return bytesToTree(ctx.space, ctx.alphabet, bytes, undefined, undefined, boundaries);
80
- }
81
- return bytesToTree(ctx.space, ctx.alphabet, bytes, leafAt, lookup);
82
- }
83
- if (Array.isArray(input)) {
84
- return gridToTree(ctx.space, ctx.alphabet, stackGrids(input));
85
- }
86
- return gridToTree(ctx.space, ctx.alphabet, input);
87
- }
88
- /** The DEPOSIT-shaped perceive. Folds over the stream's own content cuts —
89
- * bit-identical to what inference computes for the same bytes. That
90
- * train/inference agreement is the whole contract: the trained context node
91
- * and the node `resolve(query)` reaches must be the SAME node, and the only
92
- * way to guarantee it is to give this function nothing extra to say. It
93
- * imposes no boundaries, knows nothing about turns, and reads no convention
94
- * out of the bytes.
95
- *
96
- * An input that EXTENDS a previously deposited one — a conversation context
97
- * grown by a turn, or a resumed replay — reuses that deposit's already-folded
98
- * content segments ({@link contentFoldIncremental}), so it costs O(new bytes)
99
- * instead of O(context). The reuse is TRANSPARENT by construction: a segment
100
- * is a pure function of its own bytes, so a reused one is bit-identical to a
101
- * refolded one. Nothing has to prove that the extending deposit is "really"
102
- * a next turn — a coincidental byte prefix reuses the same segments and gets
103
- * the same tree it would have got anyway. (It used to matter: while this
104
- * path imposed turn BOUNDARIES, a wrong guess changed the tree, so the cache
105
- * needed a continuation-bytes proof to gate it. Nothing is imposed now, so
106
- * there is nothing to gate.) */
107
- export function perceiveDeposit(ctx, bytes, conversational = false) {
108
- // Longest cached PROPER prefix first — the most segments to reuse.
109
- let prev;
110
- const lens = [...ctx._depositLens]
111
- .filter((L) => L >= 2 && L < bytes.length)
112
- .sort((a, b) => b - a);
113
- for (const L of lens) {
114
- const hit = ctx._depositTrees.get(latin1Key(bytes.subarray(0, L)));
115
- if (hit !== undefined) {
116
- prev = hit.content;
117
- break;
118
- }
119
- }
120
- const folded = contentFoldIncremental(ctx.space, ctx.alphabet, bytes, prev);
121
- // Only a CONVERSATIONAL deposit writes the cache: reuse is sound for any
122
- // deposit, but the budget is 8 entries and a corpus of unrelated facts would
123
- // evict the live chains for nothing. Purely a cost decision now, not a
124
- // correctness one.
125
- if (conversational && bytes.length >= 2) {
126
- // The lengths set drifts as the map evicts; past the probe budget the
127
- // drift itself becomes the cost (each stale length is an O(len) key
128
- // build), so both reset together — losing only warm-up on live chains.
129
- if (ctx._depositLens.size > 64) {
130
- ctx._depositLens.clear();
131
- ctx._depositTrees.clear();
132
- }
133
- ctx._depositTrees.set(latin1Key(bytes), { content: folded.fold });
134
- ctx._depositLens.add(bytes.length);
135
- }
136
- return folded.tree;
137
- }
138
- /** The raw bytes of an input — modality-neutral conversion. */
139
- export function inputBytes(ctx, input) {
140
- if (typeof input === "string")
141
- return new TextEncoder().encode(input);
142
- if (input instanceof Uint8Array)
143
- return input;
144
- if (Array.isArray(input))
145
- return hilbertBytes(stackGrids(input));
146
- return hilbertBytes(input);
147
- }
148
- /** Convenience: the gist vector of a byte span. */
149
- export function gistOf(ctx, bytes) {
150
- return perceive(ctx, bytes).v;
151
- }
152
- /** Fold a perceived tree bottom-up against the store's content-addressed maps:
153
- * every leaf is named by findLeaf, every branch by findBranch over its kids'
154
- * ids (null the moment any child is unknown). `visit`, when given, sees each
155
- * node with its byte span and resolved id. Returns the node's byte end and
156
- * resolved id. */
157
- export function foldTree(ctx, n, start, visit) {
158
- // Subtree already resolved (from a previous conversation turn or an earlier
159
- // recognition pass). The pyramid reuses prefix subtrees as identical Sema
160
- // objects, so a conversation's prefix is warm from its second turn on.
161
- // Without a visitor that makes foldTree O(suffix) instead of O(context);
162
- // with one it stays O(context) and saves the per-node store probes instead
163
- // (see below for why the distinction is not negotiable).
164
- //
165
- // WHAT THE CACHE KNOWS, AND WHAT IT DOES NOT. An entry records this
166
- // subtree's id and byte length — nothing about its DESCENDANTS' spans.
167
- // Returning here therefore emits ONE visit() where a cold walk emits one per
168
- // node, and `visit` is not instrumentation: recognise() emits its sites from
169
- // it (recognition.ts) and attention's collectRegions votes over what it
170
- // yields (attention.ts). Skipping the descent silently shrinks the evidence
171
- // those mechanisms see, purely because the cache happened to be warm.
172
- //
173
- // That is not hypothetical and not an edge case — it is every conversation
174
- // turn after the first. `contentFoldIncremental` deliberately shares prefix
175
- // segment OBJECTS across turns (~99% reuse), so by turn 2 the prefix is
176
- // warm; meanwhile recogniseMemo/climbMemo are keyed on exact query BYTES,
177
- // which a growing context never repeats. Warm subtrees + missed memos is
178
- // the unprotected quadrant. Measured over real trained conversations,
179
- // recognising the same context with a warm prefix lost 67-92% of its leaves
180
- // (772->204, 589->47, 872->291, 377->37) with `sites` unchanged, so the loss
181
- // is invisible to the coarse counts; a direct foldTree probe on identical
182
- // bytes and an identical tree object fired visit() 661 times cold and 37
183
- // warm. respond() is immune only because it never sets _resolvedSubtrees
184
- // (mind.ts) — the degradation was unique to the multi-turn API.
185
- //
186
- // So the fast path is taken only when NOBODY IS WATCHING. With a visitor
187
- // present we still walk, and the cache degrades to the thing it soundly is:
188
- // an elision of the store probes (findLeaf/findBranch) at each node, not an
189
- // elision of the traversal. Ids still come from the cache, so a warm walk
190
- // is cheaper than a cold one; it is no longer *different* from one.
191
- const cached = ctx._resolvedSubtrees?.get(n);
192
- if (cached !== undefined && visit === undefined) {
193
- return { end: start + cached.len, node: cached.id };
194
- }
195
- if (n.kids === null) {
196
- const b = n.leaf ?? new Uint8Array(0);
197
- const end = start + b.length;
198
- const node = cached !== undefined ? cached.id : ctx.store.findLeaf(b);
199
- visit?.(n, start, end, node);
200
- if (node !== null && ctx._resolvedSubtrees) {
201
- ctx._resolvedSubtrees.set(n, { id: node, len: b.length });
202
- }
203
- return { end, node };
204
- }
205
- let pos = start;
206
- let known = true;
207
- const kids = [];
208
- for (const k of n.kids) {
209
- const r = foldTree(ctx, k, pos, visit);
210
- if (r.node === null)
211
- known = false;
212
- else if (known)
213
- kids.push(r.node);
214
- pos = r.end;
215
- }
216
- // Same store-probe elision as the leaf case: a cached entry already names
217
- // this subtree, so the descent above was for `visit`'s benefit alone and the
218
- // id need not be re-derived. Using it also keeps a warm walk's ids
219
- // bit-identical to a cold walk's rather than re-deriving them from children
220
- // that may themselves have come from cache.
221
- const node = cached !== undefined
222
- ? cached.id
223
- : known
224
- ? ctx.store.findBranch(kids)
225
- : null;
226
- visit?.(n, start, pos, node);
227
- if (node !== null && ctx._resolvedSubtrees) {
228
- ctx._resolvedSubtrees.set(n, { id: node, len: pos - start });
229
- }
230
- return { end: pos, node };
231
- }
232
- /** The canonical node id of a byte span: perceive it in isolation — the way
233
- * training did — and recover its root bottom-up. Returns null if any part is
234
- * unknown. */
235
- export function resolve(ctx, bytes) {
236
- if (bytes.length === 0)
237
- return null;
238
- if (ctx.meter)
239
- ctx.meter.resolves++;
240
- const exact = foldTree(ctx, perceive(ctx, bytes), 0).node;
241
- if (exact !== null)
242
- return exact;
243
- return canonResolve(ctx, bytes);
244
- }
245
- /** Equivalence-class resolution: when the exact content-addressed lookup
246
- * misses, find a stored node whose CANONICAL key equals the span's — the
247
- * store's canon index proposes candidates by key hash, and each is verified
248
- * by re-canonicalizing its bytes (hash-then-verify, like every content
249
- * lookup). Among verified candidates, one that leads somewhere (has a
250
- * continuation edge) is preferred; ties break to the lowest id — a corpus
251
- * property, not a seed property. Null when the response carries no
252
- * canonicalizer, the store has no canon index, or nothing verifies. */
253
- export function canonResolve(ctx, bytes) {
254
- const canon = ctx.canon;
255
- const store = ctx.store;
256
- if (canon === null || !store.canonFind)
257
- return null;
258
- if (bytes.length < 2)
259
- return null;
260
- const memo = ctx.canonMemo;
261
- const memoKey = memo ? latin1Key(bytes) : "";
262
- if (memo) {
263
- const hit = memo.get(memoKey);
264
- if (hit !== undefined)
265
- return hit;
266
- }
267
- const set = (v) => {
268
- memo?.set(memoKey, v);
269
- return v;
270
- };
271
- const key = canon(bytes);
272
- if (key.length === 0)
273
- return set(null);
274
- // A stored form that IS canonical is not in the index (buildCanonIndex
275
- // skips identity rows) — the exact content-addressed lookup of the
276
- // canonical bytes finds it directly.
277
- if (key.length !== bytes.length || !bytesEqual(key, bytes)) {
278
- const direct = foldTree(ctx, perceive(ctx, key), 0).node;
279
- if (direct !== null)
280
- return set(direct);
281
- }
282
- if (ctx.meter)
283
- ctx.meter.canonLookups++;
284
- const candidates = store.canonFind(canonHash(key));
285
- if (candidates.length === 0)
286
- return set(null);
287
- let best = null;
288
- let bestLeads = false;
289
- for (const id of candidates) {
290
- const bytesOf = read(ctx, id);
291
- const stored = canon(bytesOf);
292
- if (stored.length !== key.length || !bytesEqual(stored, key))
293
- continue;
294
- // The index stores FLAT content twins; the id the exact path would have
295
- // resolved for these bytes is their FOLD — the deposit-shaped node that
296
- // carries the edges and halos. Re-folding the candidate's bytes lands
297
- // on exactly the node the canonical-case query would have found.
298
- const folded = foldTree(ctx, perceive(ctx, bytesOf), 0).node;
299
- const use = folded ?? id;
300
- const leads = store.hasNext(use) || store.haloMass(use) > 0;
301
- if (best === null || (leads && !bestLeads) ||
302
- (leads === bestLeads && use < best)) {
303
- best = use;
304
- bestLeads = leads;
305
- }
306
- }
307
- return set(best);
308
- }
309
- /** Walk a perceived tree in POST-ORDER with byte offsets — children before
310
- * their parent, `visit(node, start, end)` for every node including leaves.
311
- * Returns the byte end. The one shared traversal the offset-carrying tree
312
- * readers (recognition via foldTree's richer variant, attention's region
313
- * collection, resonance's branch counting) build on, so each does not
314
- * re-derive the offset bookkeeping. (recognition.segment keeps its own
315
- * walk: its flush semantics need PRE-order decisions at leaf-parents, which
316
- * a post-order visitor cannot express.) */
317
- export function walkTree(n, start, visit) {
318
- if (n.kids === null) {
319
- const end = start + (n.leaf?.length ?? 0);
320
- visit(n, start, end);
321
- return end;
322
- }
323
- let pos = start;
324
- for (const k of n.kids)
325
- pos = walkTree(k, pos, visit);
326
- visit(n, start, pos);
327
- return pos;
328
- }
329
- // ── Read: node → bytes ──────────────────────────────────────────────────
330
- /** Reconstruct a node's byte content from the DAG, up to `maxLen` bytes. */
331
- export function read(ctx, id, maxLen = ALL) {
332
- return ctx.store.bytesPrefix(id, maxLen);
333
- }
@@ -1,139 +0,0 @@
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
- /** Optional structured payload — a mechanism-specific, plain-serialisable
63
- * shape (no Map/Set/vectors/mutable internals) that carries more than the
64
- * human-readable `note` can, for a debugger or downstream tool to consume
65
- * programmatically. Never read by inference; purely additive. */
66
- data?: unknown;
67
- }
68
- /** The callback {@link Mind.respond} / {@link Mind.respondText} accept. It is
69
- * invoked once per completed step, AS the inference unfolds — never batched at
70
- * the end — so a caller can stream the reasoning live or accumulate it. */
71
- export type InspectRationale = (step: RationaleStep) => void;
72
- /** Decode bytes to text for display, dropping the NUL padding the encoder uses
73
- * (the same cleanup {@link Mind.respondText} does for its result). */
74
- export declare function decodeText(bytes: Uint8Array): string;
75
- /** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
76
- * the same union-of-spans reading think's grounding decider prices at PASS
77
- * per byte, exposed here so a mechanism can turn it into a human label. */
78
- export declare function unexplainedSpans(queryLen: number, accounted: ReadonlyArray<[number, number]>): Array<[number, number]>;
79
- /** A human-readable label for the query bytes a mechanism's `accounted`
80
- * spans leave unexplained — purely diagnostic (Task 2's negative evidence):
81
- * it never changes a candidate's weight, only what the rationale trace
82
- * says the mechanism left on the table. `""` when nothing is unexplained. */
83
- export declare function unexplainedLabel(query: Uint8Array, accounted: ReadonlyArray<[number, number]>): string;
84
- /** An open mechanism — the handle {@link Rationale.enter} returns. Hold it for
85
- * the duration of the mechanism and call {@link Scope.done} with the outputs
86
- * when it finishes; that emits the step and pops the nesting. */
87
- export interface Scope {
88
- /** The step index reserved for this mechanism at entry — pass it as an
89
- * explicit dependency of a later step that consumes this one's output. */
90
- readonly index: number;
91
- /** Close the mechanism: emit its step with these outputs and pop it off the
92
- * nesting stack. Idempotent — a second call is ignored, so a `finally` that
93
- * closes after an early return is safe. */
94
- done(outputs: RationaleItem[], note?: string, data?: unknown): void;
95
- }
96
- /** The live tracer: a stack of open mechanisms over one {@link Mind.respond}.
97
- *
98
- * Sema's inference is single-threaded and strictly sequential — every async
99
- * step is awaited before the next begins, and `respond` holds no two thoughts
100
- * at once — so a plain stack exactly tracks the current nesting: {@link enter}
101
- * pushes, {@link Scope.done} pops, and {@link step} (a mechanism with no
102
- * sub-steps) is the two fused. The tracer never branches the control flow; it
103
- * only records it. */
104
- export declare class Rationale {
105
- private readonly sink;
106
- private next;
107
- /** Open mechanisms, outermost first. Each frame remembers the last child it
108
- * has spawned so the next sibling can default its data-flow edge to it. */
109
- private readonly stack;
110
- /** The most recent step index emitted under each mechanism name — the handle
111
- * a later step uses to name an EARLIER mechanism as its data-flow producer
112
- * (e.g. cover depends on the latest recognise / computeExtensions). One tracer is
113
- * built per response and inference is sequential, so "most recent" is exactly
114
- * "the one that produced the inputs I am about to consume". */
115
- private readonly lastByName;
116
- constructor(sink: InspectRationale);
117
- /** The index of the most recent step with this mechanism name, or undefined if
118
- * none has run. Used to wire an explicit producer edge into {@link
119
- * Scope.done} / {@link step}'s `deps`. */
120
- lastIndex(name: string): number | undefined;
121
- /** The mechanism names currently open, outermost → innermost. */
122
- private path;
123
- /** The default data-flow edge for a step entering now: the previous sibling
124
- * inside the current mechanism, else the enclosing mechanism, else nothing
125
- * (the root). An explicit `deps` overrides this. */
126
- private defaultDeps;
127
- /** Reserve this step's index and register it as the current mechanism's most
128
- * recent child (so the NEXT sibling chains to it) and as the most recent step
129
- * of its own NAME (so a later mechanism can name it as a producer). */
130
- private reserve;
131
- private emit;
132
- /** Enter a mechanism that has sub-steps. Captures its inputs and the nesting
133
- * now; the matching {@link Scope.done} supplies the outputs when it finishes.
134
- * `deps` overrides the default data-flow edge (previous sibling / parent). */
135
- enter(name: string, inputs: RationaleItem[], deps?: number[]): Scope;
136
- /** Record a mechanism that has no sub-steps — its inputs and outputs are both
137
- * known at the call site. Returns its index, for a later step to depend on. */
138
- step(name: string, inputs: RationaleItem[], outputs: RationaleItem[], note?: string, deps?: number[], data?: unknown): number;
139
- }