@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.
- package/README.md +30 -2
- package/dist/import/evermind.d.ts +48 -0
- package/dist/import/evermind.d.ts.map +1 -0
- package/dist/import/evermind.js +111 -0
- package/dist/import/evermind.js.map +1 -0
- package/dist/import/index.d.ts +11 -0
- package/dist/import/index.d.ts.map +1 -0
- package/dist/import/index.js +10 -0
- package/dist/import/index.js.map +1 -0
- package/dist/import/safetensors.d.ts +13 -0
- package/dist/import/safetensors.d.ts.map +1 -0
- package/dist/import/safetensors.js +64 -0
- package/dist/import/safetensors.js.map +1 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/limbic/index.d.ts +2 -2
- package/dist/limbic/index.d.ts.map +1 -1
- package/dist/limbic/index.js +1 -1
- package/dist/limbic/index.js.map +1 -1
- package/dist/limbic/regions.d.ts +31 -0
- package/dist/limbic/regions.d.ts.map +1 -1
- package/dist/limbic/regions.js +34 -0
- package/dist/limbic/regions.js.map +1 -1
- package/dist/tokenizer/bpe.d.ts +37 -0
- package/dist/tokenizer/bpe.d.ts.map +1 -1
- package/dist/tokenizer/bpe.js +47 -0
- package/dist/tokenizer/bpe.js.map +1 -1
- package/dist/utils/delta.d.ts +18 -0
- package/dist/utils/delta.d.ts.map +1 -1
- package/dist/utils/delta.js +40 -0
- package/dist/utils/delta.js.map +1 -1
- package/dist/utils/quantization.d.ts.map +1 -1
- package/dist/utils/quantization.js +4 -1
- package/dist/utils/quantization.js.map +1 -1
- package/package.json +1 -1
- package/src/import/evermind.ts +128 -0
- package/src/import/index.ts +11 -0
- package/src/import/safetensors.ts +67 -0
- package/src/index.ts +21 -1
- package/src/limbic/index.ts +2 -1
- package/src/limbic/regions.ts +60 -0
- package/src/tokenizer/bpe.ts +59 -0
- package/src/utils/delta.ts +39 -0
- package/src/utils/quantization.ts +4 -1
package/src/limbic/regions.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/tokenizer/bpe.ts
CHANGED
|
@@ -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[] = [];
|
package/src/utils/delta.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|