@seanhogg/builderforce-memory-engine 2026.6.34 → 2026.6.39

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 (46) hide show
  1. package/README.md +30 -2
  2. package/dist/import/evermind.d.ts +48 -0
  3. package/dist/import/evermind.d.ts.map +1 -0
  4. package/dist/import/evermind.js +111 -0
  5. package/dist/import/evermind.js.map +1 -0
  6. package/dist/import/index.d.ts +11 -0
  7. package/dist/import/index.d.ts.map +1 -0
  8. package/dist/import/index.js +10 -0
  9. package/dist/import/index.js.map +1 -0
  10. package/dist/import/safetensors.d.ts +13 -0
  11. package/dist/import/safetensors.d.ts.map +1 -0
  12. package/dist/import/safetensors.js +64 -0
  13. package/dist/import/safetensors.js.map +1 -0
  14. package/dist/index.d.ts +6 -3
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +4 -2
  17. package/dist/index.js.map +1 -1
  18. package/dist/limbic/index.d.ts +2 -2
  19. package/dist/limbic/index.d.ts.map +1 -1
  20. package/dist/limbic/index.js +1 -1
  21. package/dist/limbic/index.js.map +1 -1
  22. package/dist/limbic/regions.d.ts +31 -0
  23. package/dist/limbic/regions.d.ts.map +1 -1
  24. package/dist/limbic/regions.js +34 -0
  25. package/dist/limbic/regions.js.map +1 -1
  26. package/dist/tokenizer/bpe.d.ts +37 -0
  27. package/dist/tokenizer/bpe.d.ts.map +1 -1
  28. package/dist/tokenizer/bpe.js +47 -0
  29. package/dist/tokenizer/bpe.js.map +1 -1
  30. package/dist/utils/delta.d.ts +18 -0
  31. package/dist/utils/delta.d.ts.map +1 -1
  32. package/dist/utils/delta.js +40 -0
  33. package/dist/utils/delta.js.map +1 -1
  34. package/dist/utils/quantization.d.ts.map +1 -1
  35. package/dist/utils/quantization.js +4 -1
  36. package/dist/utils/quantization.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/import/evermind.ts +128 -0
  39. package/src/import/index.ts +11 -0
  40. package/src/import/safetensors.ts +67 -0
  41. package/src/index.ts +21 -1
  42. package/src/limbic/index.ts +2 -1
  43. package/src/limbic/regions.ts +60 -0
  44. package/src/tokenizer/bpe.ts +59 -0
  45. package/src/utils/delta.ts +39 -0
  46. package/src/utils/quantization.ts +4 -1
@@ -139,3 +139,63 @@ export function recordToState(rec: Partial<Record<LimbicDimName, number>>): Floa
139
139
  }
140
140
  return s;
141
141
  }
142
+
143
+ /**
144
+ * The static psychometric traits (0..100, 50 = neutral) that pull the resting
145
+ * affective setpoint away from {@link NEUTRAL_STATE}. A subset of the full
146
+ * personality vector — only the dimensions that map to a limbic drive. All
147
+ * optional; an omitted trait is treated as neutral (50).
148
+ *
149
+ * Keep the mapping in {@link personalitySetpoint} in sync with the runtime
150
+ * compiler `deriveLimbicSetpoints` in `@builderforce/agent-tools` so the on-prem
151
+ * runtime, the cloud engine, and this WebGPU trainer settle on the SAME baseline
152
+ * for the same personality. The two are coupled only by this mapping.
153
+ */
154
+ export interface PersonalityTraits {
155
+ openness?: number;
156
+ emotionality?: number;
157
+ conscientiousness?: number;
158
+ extraversion?: number;
159
+ /** Regulatory focus: 0 = prevention, 100 = promotion. */
160
+ regulatoryFocus?: number;
161
+ riskTolerance?: number;
162
+ grit?: number;
163
+ /** Schwartz "stimulation" value. */
164
+ stimulation?: number;
165
+ }
166
+
167
+ /** Signed influence of a 0..100 trait around its neutral midpoint → [-1, 1]. */
168
+ function infl(s: number | undefined): number {
169
+ const v = typeof s === "number" && !Number.isNaN(s) ? Math.max(0, Math.min(100, s)) : 50;
170
+ return (v - 50) / 50;
171
+ }
172
+
173
+ /**
174
+ * Derive the personality-conditioned resting SETPOINT from a trait vector — the
175
+ * homeostatic target the limbic dynamics relax toward and that the trainable
176
+ * affect model rides on top of ("personality = setpoints, limbic = dynamics").
177
+ * Returns a fresh clamped 8-dim state vector. A fully-neutral trait vector yields
178
+ * {@link NEUTRAL_STATE}. Mirrors `deriveLimbicSetpoints` in `@builderforce/agent-tools`.
179
+ */
180
+ export function personalitySetpoint(traits: PersonalityTraits | undefined): Float32Array {
181
+ const s = neutralState();
182
+ if (!traits) return s;
183
+ const open = infl(traits.openness);
184
+ const emo = infl(traits.emotionality);
185
+ const consc = infl(traits.conscientiousness);
186
+ const extra = infl(traits.extraversion);
187
+ const reg = infl(traits.regulatoryFocus);
188
+ const risk = infl(traits.riskTolerance);
189
+ const grit = infl(traits.grit);
190
+ const stim = infl(traits.stimulation);
191
+
192
+ s[LIMBIC_DIM.driveCuriosity] = clampDim(LIMBIC_DIM.driveCuriosity, 0.5 + 0.35 * open + 0.15 * stim);
193
+ s[LIMBIC_DIM.exploration] = clampDim(LIMBIC_DIM.exploration, 0.4 + 0.3 * open + 0.25 * risk + 0.15 * reg);
194
+ s[LIMBIC_DIM.driveCaution] = clampDim(LIMBIC_DIM.driveCaution, 0.5 + 0.3 * consc - 0.3 * risk - 0.2 * reg + 0.15 * emo);
195
+ s[LIMBIC_DIM.arousal] = clampDim(LIMBIC_DIM.arousal, 0.2 + 0.2 * emo + 0.1 * extra);
196
+ s[LIMBIC_DIM.driveSocial] = clampDim(LIMBIC_DIM.driveSocial, 0.5 + 0.35 * extra);
197
+ s[LIMBIC_DIM.driveEffort] = clampDim(LIMBIC_DIM.driveEffort, 0.8 + 0.15 * grit + 0.1 * consc);
198
+ s[LIMBIC_DIM.valence] = clampDim(LIMBIC_DIM.valence, 0.0 + 0.1 * reg - 0.1 * emo);
199
+ s[LIMBIC_DIM.attention] = clampDim(LIMBIC_DIM.attention, 0.7 + 0.1 * consc);
200
+ return s;
201
+ }
@@ -9,6 +9,21 @@ export interface BPEEncodeOptions {
9
9
 
10
10
  export type PadSide = 'right' | 'left';
11
11
 
12
+ /** A Hugging Face `tokenizer.json` (or a bare `{ vocab, merges }`) for import. */
13
+ export interface HuggingFaceTokenizerSpec {
14
+ model?: { vocab?: Record<string, number>; merges?: Array<string | [string, string]> };
15
+ vocab?: Record<string, number>;
16
+ merges?: Array<string | [string, string]>;
17
+ }
18
+
19
+ /** Override the special-token strings to match an imported tokenizer. */
20
+ export interface SpecialTokenOverrides {
21
+ bos?: string;
22
+ eos?: string;
23
+ pad?: string;
24
+ unk?: string;
25
+ }
26
+
12
27
  function buildByteEncoder(): Map<number, string> {
13
28
  const enc = new Map<number, string>();
14
29
  const ranges: [number, number][] = [
@@ -177,6 +192,50 @@ export class BPETokenizer {
177
192
  this.padId = this.vocab.get(this.padToken) ?? null;
178
193
  }
179
194
 
195
+ /**
196
+ * Seed this tokenizer from an existing Hugging Face `tokenizer.json` — the
197
+ * *import-merges* path (vs `train`, which learns a fresh vocab from a corpus).
198
+ *
199
+ * BPE merges + vocab are portable DATA, not architecture: a proven code
200
+ * tokenizer (GPT-2 / Llama / StarCoder family) gives Evermind a battle-tested
201
+ * code vocabulary on day one. Those tokenizers use the same GPT-2 byte→unicode
202
+ * mapping this class already implements ({@link BYTE_ENCODER}), so the imported
203
+ * vocab is directly compatible with `encode`/`decode`/`_bpe`.
204
+ *
205
+ * Accepts the full `tokenizer.json` object (reads `.model.vocab` / `.model.merges`)
206
+ * or a bare `{ vocab, merges }`. `merges` may be the classic `"a b"` strings or
207
+ * the newer `["a", "b"]` pair arrays. Special-token strings can be overridden to
208
+ * match the source tokenizer (e.g. `<|endoftext|>`, `<s>`, `</s>`).
209
+ */
210
+ loadHuggingFace(spec: HuggingFaceTokenizerSpec, specials: SpecialTokenOverrides = {}): void {
211
+ const model = (spec.model ?? spec) as { vocab?: Record<string, number>; merges?: Array<string | [string, string]> };
212
+ const vocabObj = model.vocab ?? (spec as HuggingFaceTokenizerSpec).vocab;
213
+ const rawMerges = model.merges ?? (spec as HuggingFaceTokenizerSpec).merges;
214
+ if (!vocabObj || typeof vocabObj !== 'object') {
215
+ throw new Error('loadHuggingFace: missing model.vocab (expected a Hugging Face tokenizer.json)');
216
+ }
217
+ if (!Array.isArray(rawMerges)) {
218
+ throw new Error('loadHuggingFace: missing model.merges (expected a BPE tokenizer.json)');
219
+ }
220
+ const mergeArr = rawMerges.map((m) => (Array.isArray(m) ? `${m[0]} ${m[1]}` : String(m)));
221
+ if (specials.bos) this.bosToken = specials.bos;
222
+ if (specials.eos) this.eosToken = specials.eos;
223
+ if (specials.pad) this.padToken = specials.pad;
224
+ if (specials.unk) this.unkToken = specials.unk;
225
+ this.loadFromObjects(vocabObj, mergeArr);
226
+ }
227
+
228
+ /**
229
+ * Fetch and import a Hugging Face `tokenizer.json` by URL — the IDE's
230
+ * "import merges from a repo" path. Resolves once the tokenizer is loaded.
231
+ */
232
+ async loadHuggingFaceUrl(url: string, specials: SpecialTokenOverrides = {}): Promise<void> {
233
+ const res = await fetch(url);
234
+ if (!res.ok) throw new Error(`loadHuggingFaceUrl: ${res.status} fetching ${url}`);
235
+ const spec = (await res.json()) as HuggingFaceTokenizerSpec;
236
+ this.loadHuggingFace(spec, specials);
237
+ }
238
+
180
239
  encode(text: string, opts: BPEEncodeOptions = {}): number[] {
181
240
  const words = text.match(PRE_TOKENIZE_RE) ?? [];
182
241
  const ids: number[] = [];
@@ -96,6 +96,45 @@ export function serializeRowDelta(delta: RowDelta): ArrayBuffer {
96
96
  return appendCrcTrailer(buf);
97
97
  }
98
98
 
99
+ /**
100
+ * Diff two whole checkpoint buffers of IDENTICAL structure (same model/config,
101
+ * f32 data) into a sparse delta — the online-save win for `adapt()` (EVM-6b).
102
+ * Both buffers are produced by the same `exportWeights()` so their headers are
103
+ * byte-identical; only the weights the adapt actually changed appear in the
104
+ * delta. Operates on the raw bytes (header rows diff to nothing), so it needs no
105
+ * knowledge of the header layout and is GPU-free / unit-testable.
106
+ *
107
+ * Element-granular (each changed f32 stored as index+value) so a WSLA update
108
+ * that touches a few rows persists kilobytes, not the whole model.
109
+ */
110
+ export function diffCheckpoints(base: ArrayBuffer, current: ArrayBuffer): ArrayBuffer {
111
+ const b = verifyCrcTrailer(base);
112
+ if (b.hasTrailer && !b.ok) throw new Error('diffCheckpoints: base failed CRC integrity check');
113
+ const c = verifyCrcTrailer(current);
114
+ if (c.hasTrailer && !c.ok) throw new Error('diffCheckpoints: current failed CRC integrity check');
115
+ if (b.body.byteLength !== c.body.byteLength) {
116
+ throw new Error(`diffCheckpoints: shape mismatch (base ${b.body.byteLength}B, current ${c.body.byteLength}B) — only same-config checkpoints can be diffed`);
117
+ }
118
+ if (b.body.byteLength % 4 !== 0) {
119
+ throw new Error('diffCheckpoints: checkpoint body is not 4-byte aligned (not an f32 checkpoint)');
120
+ }
121
+ return serializeRowDelta(computeRowDelta(new Float32Array(b.body), new Float32Array(c.body), 1));
122
+ }
123
+
124
+ /**
125
+ * Reconstruct the `current` checkpoint from `base` + a diff produced by
126
+ * {@link diffCheckpoints}. Returns a CRC-trailed buffer byte-identical to the
127
+ * original current checkpoint, ready for `loadWeights`.
128
+ */
129
+ export function applyCheckpointDiff(base: ArrayBuffer, diff: ArrayBuffer): ArrayBuffer {
130
+ const b = verifyCrcTrailer(base);
131
+ if (b.hasTrailer && !b.ok) throw new Error('applyCheckpointDiff: base failed CRC integrity check');
132
+ const patched = applyRowDelta(new Float32Array(b.body), deserializeRowDelta(diff));
133
+ const out = new ArrayBuffer(patched.byteLength);
134
+ new Float32Array(out).set(patched);
135
+ return appendCrcTrailer(out);
136
+ }
137
+
99
138
  /** Parse a binary produced by {@link serializeRowDelta}; verifies CRC + magic. */
100
139
  export function deserializeRowDelta(buffer: ArrayBuffer): RowDelta {
101
140
  const crc = verifyCrcTrailer(buffer);
@@ -54,7 +54,10 @@ export function fp16ToFloat(val: number): number {
54
54
  const mantissa = val & 0x3FF;
55
55
 
56
56
  if (exponent === 0) {
57
- const f = mantissa / 1024.0;
57
+ // Subnormal: value = mantissa/1024 × 2^-14 (no implicit leading 1, min
58
+ // exponent -14). The 2^-14 scale was missing, so subnormals (|x| < ~6.1e-5)
59
+ // decoded ~16384× too large — corrupting every fp16 round-trip of tiny weights.
60
+ const f = (mantissa / 1024.0) * Math.pow(2, -14);
58
61
  return sign ? -f : f;
59
62
  }
60
63