@hviana/sema 0.2.0 → 0.2.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/src/geometry.d.ts +28 -0
- package/dist/src/geometry.js +35 -0
- package/dist/src/mind/attention.js +29 -0
- package/dist/src/mind/graph-search.js +127 -47
- package/dist/src/mind/learning.d.ts +1 -1
- package/dist/src/mind/learning.js +20 -5
- package/dist/src/mind/mechanisms/cover.js +32 -0
- package/dist/src/mind/mechanisms/recall.js +26 -2
- package/dist/src/mind/mind.d.ts +2 -2
- package/dist/src/mind/mind.js +22 -17
- package/dist/src/mind/primitives.d.ts +16 -9
- package/dist/src/mind/primitives.js +58 -22
- package/dist/src/mind/types.d.ts +32 -10
- package/package.json +1 -1
- package/src/geometry.ts +61 -1
- package/src/mind/attention.ts +38 -0
- package/src/mind/graph-search.ts +128 -46
- package/src/mind/learning.ts +20 -3
- package/src/mind/mechanisms/cover.ts +38 -0
- package/src/mind/mechanisms/recall.ts +30 -2
- package/src/mind/mind.ts +31 -23
- package/src/mind/primitives.ts +71 -29
- package/src/mind/types.ts +33 -10
package/src/mind/mind.ts
CHANGED
|
@@ -14,8 +14,6 @@ import { bindSeat, fold, Sema, Space } from "../sema.js";
|
|
|
14
14
|
import { Alphabet } from "../alphabet.js";
|
|
15
15
|
import {
|
|
16
16
|
bytesToTree,
|
|
17
|
-
bytesToTreePyramid,
|
|
18
|
-
type FoldPyramid,
|
|
19
17
|
Grid,
|
|
20
18
|
gridToTree,
|
|
21
19
|
hilbertBytes,
|
|
@@ -109,7 +107,7 @@ export interface Conversation {
|
|
|
109
107
|
* foldTree returns their ids immediately — O(suffix) instead of
|
|
110
108
|
* O(context) for every tree walk. */
|
|
111
109
|
interface ConversationData {
|
|
112
|
-
|
|
110
|
+
tree: Sema;
|
|
113
111
|
bytes: Uint8Array;
|
|
114
112
|
boundaries: number[];
|
|
115
113
|
perceiveMemo: Map<string, Sema>;
|
|
@@ -244,9 +242,10 @@ export class Mind implements MindContext {
|
|
|
244
242
|
// bounded: a pyramid costs ~KB per content byte (one D-float gist per
|
|
245
243
|
// interior node), and only the few live conversation chains need to stay
|
|
246
244
|
// warm, so 8 entries is the honest budget.
|
|
247
|
-
_depositTrees = new BoundedMap<
|
|
248
|
-
|
|
249
|
-
|
|
245
|
+
_depositTrees = new BoundedMap<
|
|
246
|
+
string,
|
|
247
|
+
import("./types.js").DepositCacheEntry
|
|
248
|
+
>(8);
|
|
250
249
|
_depositLens = new Set<number>();
|
|
251
250
|
_internIds = new WeakMap<import("../sema.js").Sema, number>();
|
|
252
251
|
|
|
@@ -513,18 +512,20 @@ export class Mind implements MindContext {
|
|
|
513
512
|
* where one turn ends and the next begins. */
|
|
514
513
|
beginConversation(state?: ConversationState): Conversation {
|
|
515
514
|
const id = this._nextConvId++;
|
|
516
|
-
// Build the initial pyramid: from scratch for a new conversation,
|
|
517
|
-
// or from the saved context bytes when restoring.
|
|
518
515
|
const initBytes = state?.context ?? new Uint8Array(0);
|
|
519
|
-
const
|
|
516
|
+
const initBoundaries = state?.boundaries ? [...state.boundaries] : [];
|
|
517
|
+
const tree = bytesToTree(
|
|
520
518
|
this.space,
|
|
521
519
|
this.alphabet,
|
|
522
520
|
initBytes,
|
|
521
|
+
undefined,
|
|
522
|
+
undefined,
|
|
523
|
+
initBoundaries.length > 0 ? initBoundaries : undefined,
|
|
523
524
|
);
|
|
524
525
|
this._conversations.set(id, {
|
|
525
|
-
|
|
526
|
+
tree,
|
|
526
527
|
bytes: initBytes,
|
|
527
|
-
boundaries:
|
|
528
|
+
boundaries: initBoundaries,
|
|
528
529
|
perceiveMemo: new Map(),
|
|
529
530
|
recogniseMemo: new Map(),
|
|
530
531
|
climbMemo: new Map(),
|
|
@@ -572,25 +573,23 @@ export class Mind implements MindContext {
|
|
|
572
573
|
* place a context grows ({@link addTurn} and {@link respondTurn} both
|
|
573
574
|
* come through here), so the append semantics cannot drift. */
|
|
574
575
|
private _growContext(data: ConversationData, turnBytes: Uint8Array): Sema {
|
|
575
|
-
const prevLen = data.
|
|
576
|
+
const prevLen = data.bytes.length;
|
|
576
577
|
// An empty turn neither grows the context nor marks a boundary —
|
|
577
578
|
// boundaries are documented strictly increasing, and a zero-length
|
|
578
|
-
// "turn" is no turn.
|
|
579
|
-
// refold below O(1), so the existing tree is returned unchanged.
|
|
579
|
+
// "turn" is no turn. Nothing changed, so the existing tree stands.
|
|
580
580
|
const grow = turnBytes.length > 0;
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
: turnBytes;
|
|
586
|
-
if (grow && prevLen > 0) data.boundaries.push(prevLen);
|
|
587
|
-
const { tree, pyramid } = bytesToTreePyramid(
|
|
581
|
+
if (!grow) return data.tree;
|
|
582
|
+
const grown = prevLen > 0 ? concat2(data.bytes, turnBytes) : turnBytes;
|
|
583
|
+
if (prevLen > 0) data.boundaries.push(prevLen);
|
|
584
|
+
const tree = bytesToTree(
|
|
588
585
|
this.space,
|
|
589
586
|
this.alphabet,
|
|
590
587
|
grown,
|
|
591
|
-
|
|
588
|
+
undefined,
|
|
589
|
+
undefined,
|
|
590
|
+
data.boundaries.length > 0 ? data.boundaries : undefined,
|
|
592
591
|
);
|
|
593
|
-
data.
|
|
592
|
+
data.tree = tree;
|
|
594
593
|
data.bytes = grown;
|
|
595
594
|
data.perceiveMemo.set(latin1Key(grown), tree);
|
|
596
595
|
return tree;
|
|
@@ -644,6 +643,15 @@ export class Mind implements MindContext {
|
|
|
644
643
|
this.canonMemo = this.canon ? new Map() : null;
|
|
645
644
|
|
|
646
645
|
try {
|
|
646
|
+
// No recognise-memo pre-seeding here: that used to be necessary
|
|
647
|
+
// because the flat/positional fold lost visibility into an earlier
|
|
648
|
+
// turn's own structure once later bytes shifted its position
|
|
649
|
+
// (foldTree no longer visited the turn's root node). The
|
|
650
|
+
// STABLE-PREFIX fold (see {@link ConversationData}) makes every
|
|
651
|
+
// turn's subtree independent of what follows it by construction, so
|
|
652
|
+
// recognise() finds it correctly on its own, first-touch, exactly
|
|
653
|
+
// once per turn — no workaround needed, and none pre-empting
|
|
654
|
+
// recognise()'s own memo with a partial result.
|
|
647
655
|
const top = this.trace?.enter("respondTurn", [
|
|
648
656
|
rItem(newContext, "query"),
|
|
649
657
|
]);
|
package/src/mind/primitives.ts
CHANGED
|
@@ -8,16 +8,16 @@ import { Sema } from "../sema.js";
|
|
|
8
8
|
import {
|
|
9
9
|
bytesToTree,
|
|
10
10
|
bytesToTreePyramid,
|
|
11
|
-
type FoldPyramid,
|
|
12
11
|
Grid,
|
|
13
12
|
gridToTree,
|
|
14
13
|
hilbertBytes,
|
|
14
|
+
stablePrefixFoldIncremental,
|
|
15
15
|
stackGrids,
|
|
16
16
|
} from "../geometry.js";
|
|
17
17
|
import { canonHash } from "../canon.js";
|
|
18
18
|
import { bytesEqual } from "../bytes.js";
|
|
19
19
|
import { ALL } from "./types.js";
|
|
20
|
-
import type { Input, MindContext } from "./types.js";
|
|
20
|
+
import type { DepositCacheEntry, Input, MindContext } from "./types.js";
|
|
21
21
|
|
|
22
22
|
// ── Address: bytes → node ──────────────────────────────────────────────
|
|
23
23
|
|
|
@@ -93,34 +93,76 @@ export function perceive(
|
|
|
93
93
|
return gridToTree(ctx.space, ctx.alphabet, input as Grid);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
/** The DEPOSIT-shaped perceive
|
|
97
|
-
* perception
|
|
98
|
-
* for exact recall),
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
96
|
+
/** The DEPOSIT-shaped perceive. A FIRST-SEEN input takes the PLAIN fold
|
|
97
|
+
* (bit-identical to inference perception of a standalone query — that
|
|
98
|
+
* structural train/inference agreement is load-bearing for exact recall),
|
|
99
|
+
* computed incrementally via the fold's level pyramid
|
|
100
|
+
* ({@link bytesToTreePyramid}). An input that EXTENDS a previously
|
|
101
|
+
* deposited one is a conversation context grown by one turn — the cached
|
|
102
|
+
* prefix length IS the turn boundary (derived from the deposit sequence
|
|
103
|
+
* itself, never from content conventions) — and takes the STABLE-PREFIX
|
|
104
|
+
* fold over the accumulated boundaries, bit-identical to the boundary
|
|
105
|
+
* fold query-time conversation perception uses, so the trained context
|
|
106
|
+
* node and the query's context subtree are the SAME node. Segment folds
|
|
107
|
+
* reuse across deposits ({@link stablePrefixFoldIncremental}) — O(turn)
|
|
108
|
+
* instead of O(context) per turn. The fold state is purely a cache; the
|
|
109
|
+
* boundary accumulation is what an evicted chain loses (falling back to
|
|
110
|
+
* the plain fold, the pre-boundary shape — a warm replay restores it). */
|
|
111
|
+
export function perceiveDeposit(
|
|
112
|
+
ctx: MindContext,
|
|
113
|
+
bytes: Uint8Array,
|
|
114
|
+
conversational = false,
|
|
115
|
+
): Sema {
|
|
116
|
+
let prev: DepositCacheEntry | undefined;
|
|
117
|
+
let prefixLen = 0;
|
|
118
|
+
// Cache consult (both boundary lookup and stable-prefix reuse) is scoped
|
|
119
|
+
// to conversational deposits only — a bare, unrelated fact whose bytes
|
|
120
|
+
// happen to extend an earlier deposit is NOT a conversation turn, and
|
|
121
|
+
// must keep the plain fold so it shares structure with ITS OWN prior
|
|
122
|
+
// deposits, not fragment against a coincidental byte-prefix.
|
|
123
|
+
if (conversational) {
|
|
124
|
+
// Longest cached PROPER prefix first.
|
|
125
|
+
const lens = [...ctx._depositLens]
|
|
126
|
+
.filter((L) => L >= 2 && L < bytes.length)
|
|
127
|
+
.sort((a, b) => b - a);
|
|
128
|
+
for (const L of lens) {
|
|
129
|
+
const hit = ctx._depositTrees.get(latin1Key(bytes.subarray(0, L)));
|
|
130
|
+
// The suffix must bytes-equal the hit's OWN recorded continuation —
|
|
131
|
+
// proof this deposit is that turn's actual next turn, not a fact
|
|
132
|
+
// that coincidentally shares its byte prefix.
|
|
133
|
+
if (
|
|
134
|
+
hit !== undefined && hit.nextBytes !== undefined &&
|
|
135
|
+
bytesEqual(hit.nextBytes, bytes.subarray(L))
|
|
136
|
+
) {
|
|
137
|
+
prev = hit;
|
|
138
|
+
prefixLen = L;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
115
141
|
}
|
|
116
142
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
143
|
+
let tree: Sema;
|
|
144
|
+
let entry: DepositCacheEntry;
|
|
145
|
+
if (prev !== undefined) {
|
|
146
|
+
const boundaries = [...prev.boundaries, prefixLen];
|
|
147
|
+
const folded = stablePrefixFoldIncremental(
|
|
148
|
+
ctx.space,
|
|
149
|
+
ctx.alphabet,
|
|
150
|
+
bytes,
|
|
151
|
+
boundaries,
|
|
152
|
+
prev.stable,
|
|
153
|
+
);
|
|
154
|
+
tree = folded.tree;
|
|
155
|
+
entry = { boundaries, stable: folded.fold };
|
|
156
|
+
} else {
|
|
157
|
+
const plain = bytesToTreePyramid(ctx.space, ctx.alphabet, bytes);
|
|
158
|
+
tree = plain.tree;
|
|
159
|
+
entry = { boundaries: [], pyramid: plain.pyramid };
|
|
160
|
+
}
|
|
161
|
+
// Only a conversational deposit writes the cache too — otherwise a bare
|
|
162
|
+
// fact's plain fold could later be misread as a conversation's turn-zero
|
|
163
|
+
// boundary by an unrelated conversational deposit that happens to extend
|
|
164
|
+
// its bytes.
|
|
165
|
+
if (conversational && bytes.length >= 2) {
|
|
124
166
|
// The lengths set drifts as the map evicts; past the probe budget the
|
|
125
167
|
// drift itself becomes the cost (each stale length is an O(len) key
|
|
126
168
|
// build), so both reset together — losing only warm-up on live chains.
|
|
@@ -128,7 +170,7 @@ export function perceiveDeposit(ctx: MindContext, bytes: Uint8Array): Sema {
|
|
|
128
170
|
ctx._depositLens.clear();
|
|
129
171
|
ctx._depositTrees.clear();
|
|
130
172
|
}
|
|
131
|
-
ctx._depositTrees.set(latin1Key(bytes),
|
|
173
|
+
ctx._depositTrees.set(latin1Key(bytes), entry);
|
|
132
174
|
ctx._depositLens.add(bytes.length);
|
|
133
175
|
}
|
|
134
176
|
return tree;
|
package/src/mind/types.ts
CHANGED
|
@@ -19,7 +19,26 @@ import type {
|
|
|
19
19
|
Site,
|
|
20
20
|
} from "./graph-search.js";
|
|
21
21
|
import type { Rationale } from "./rationale.js";
|
|
22
|
-
import type { FoldPyramid, Grid } from "../geometry.js";
|
|
22
|
+
import type { FoldPyramid, Grid, StableFold } from "../geometry.js";
|
|
23
|
+
|
|
24
|
+
/** One {@link MindContext._depositTrees} entry — see that field's doc. */
|
|
25
|
+
export interface DepositCacheEntry {
|
|
26
|
+
/** Turn boundaries accumulated over this content's deposit chain —
|
|
27
|
+
* strictly increasing proper offsets, each a previously-deposited
|
|
28
|
+
* whole-context length. Empty for a first-seen (single-turn) input. */
|
|
29
|
+
boundaries: number[];
|
|
30
|
+
/** Plain-fold pyramid (first-seen inputs only). */
|
|
31
|
+
pyramid?: FoldPyramid;
|
|
32
|
+
/** Stable-prefix segment folds (grown-context inputs only). */
|
|
33
|
+
stable?: StableFold;
|
|
34
|
+
/** The continuation bytes this ctxInput was paired with in ingestPair, if
|
|
35
|
+
* any — the ONLY thing that makes a later, longer ctxInput a genuine next
|
|
36
|
+
* TURN of the same conversation rather than an unrelated fact that
|
|
37
|
+
* happens to share this one's byte prefix (e.g. "2+2" vs. "2+2=5"). A
|
|
38
|
+
* later deposit only takes this entry as its stable-prefix `prev` when
|
|
39
|
+
* its own suffix bytes-equal this exactly. */
|
|
40
|
+
nextBytes?: Uint8Array;
|
|
41
|
+
}
|
|
23
42
|
import { concatBytes } from "../bytes.js";
|
|
24
43
|
import { dominates } from "../geometry.js";
|
|
25
44
|
|
|
@@ -189,15 +208,19 @@ export interface MindContext extends GraphSearchHost {
|
|
|
189
208
|
* never invalidated. Bounded LRU (byte-sized); a miss only re-perceives,
|
|
190
209
|
* never a correctness risk. */
|
|
191
210
|
_gistCache: BoundedMap<number, Vec>;
|
|
192
|
-
/** DEPOSIT-path
|
|
193
|
-
*
|
|
194
|
-
* whose
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
|
|
211
|
+
/** DEPOSIT-path perception cache: content key (latin1) of a deposited
|
|
212
|
+
* input → its accumulated turn BOUNDARIES plus reusable fold state. A
|
|
213
|
+
* deposit whose content extends a cached entry IS a conversation context
|
|
214
|
+
* grown by one turn — the cached length is the new boundary — so it
|
|
215
|
+
* folds with the SAME stable-prefix fold query-time perception uses
|
|
216
|
+
* (structural train/inference agreement, load-bearing for recall),
|
|
217
|
+
* reusing every already-folded segment via `stable` (see StableFold) —
|
|
218
|
+
* O(turn) per deposit instead of O(context). A first-seen input keeps
|
|
219
|
+
* the plain fold and caches its `pyramid` (see FoldPyramid). Purely a
|
|
220
|
+
* performance cache for the FOLD STATE; the boundaries are semantic but
|
|
221
|
+
* derived only from the deposit sequence itself (an evicted chain falls
|
|
222
|
+
* back to plain-fold behavior, exactly the pre-boundary shape). */
|
|
223
|
+
_depositTrees: BoundedMap<string, DepositCacheEntry>;
|
|
201
224
|
/** The byte lengths present in {@link _depositTrees} — the candidate
|
|
202
225
|
* prefix lengths probed (longest first). Drifts on eviction (a stale
|
|
203
226
|
* length only costs a miss); cleared with the map when it outgrows the
|