@seanhogg/builderforce-memory 2026.6.35 → 2026.7.11
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 +4 -3
- package/dist/cognition/EvermindCognition.d.ts.map +1 -1
- package/dist/cognition/EvermindCognition.js +17 -7
- package/dist/cognition/EvermindCognition.js.map +1 -1
- package/dist/diagnostics/stack-diagnostic.d.ts +9 -0
- package/dist/diagnostics/stack-diagnostic.d.ts.map +1 -1
- package/dist/diagnostics/stack-diagnostic.js +27 -1
- package/dist/diagnostics/stack-diagnostic.js.map +1 -1
- package/dist/distillation/DistillationEngine.d.ts +6 -0
- package/dist/distillation/DistillationEngine.d.ts.map +1 -1
- package/dist/distillation/DistillationEngine.js +10 -4
- package/dist/distillation/DistillationEngine.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/limbic/LimbicSession.d.ts +31 -1
- package/dist/limbic/LimbicSession.d.ts.map +1 -1
- package/dist/limbic/LimbicSession.js +47 -3
- package/dist/limbic/LimbicSession.js.map +1 -1
- package/dist/memory/MemoryStore.d.ts +10 -0
- package/dist/memory/MemoryStore.d.ts.map +1 -1
- package/dist/memory/MemoryStore.js +13 -2
- package/dist/memory/MemoryStore.js.map +1 -1
- package/dist/session/session.d.ts +22 -0
- package/dist/session/session.d.ts.map +1 -1
- package/dist/session/session.js +45 -2
- package/dist/session/session.js.map +1 -1
- package/dist/workflow/code-eval.d.ts +54 -0
- package/dist/workflow/code-eval.d.ts.map +1 -0
- package/dist/workflow/code-eval.js +135 -0
- package/dist/workflow/code-eval.js.map +1 -0
- package/dist/workflow/index.d.ts +3 -2
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +3 -2
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/steps.d.ts +20 -0
- package/dist/workflow/steps.d.ts.map +1 -1
- package/dist/workflow/steps.js +522 -7
- package/dist/workflow/steps.js.map +1 -1
- package/dist/workflow/templates.d.ts +15 -0
- package/dist/workflow/templates.d.ts.map +1 -1
- package/dist/workflow/templates.js +64 -1
- package/dist/workflow/templates.js.map +1 -1
- package/package.json +8 -4
- package/src/cognition/EvermindCognition.ts +24 -7
- package/src/diagnostics/stack-diagnostic.ts +37 -1
- package/src/distillation/DistillationEngine.ts +17 -4
- package/src/index.ts +6 -0
- package/src/limbic/LimbicSession.ts +61 -1
- package/src/memory/MemoryStore.ts +14 -2
- package/src/session/session.ts +72 -2
- package/src/workflow/code-eval.ts +143 -0
- package/src/workflow/index.ts +9 -1
- package/src/workflow/steps.ts +605 -6
- package/src/workflow/templates.ts +67 -1
|
@@ -328,6 +328,16 @@ export class MemoryStore {
|
|
|
328
328
|
* reason, it transparently falls back to Jaccard word-overlap similarity.
|
|
329
329
|
*/
|
|
330
330
|
async recallSimilar(query: string, topK: number, runtime?: SSMRuntimeRef): Promise<MemoryEntry[]> {
|
|
331
|
+
return (await this.recallSimilarScored(query, topK, runtime)).map(s => s.entry);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Like {@link recallSimilar} but returns each hit's real 0..1 relevance score
|
|
336
|
+
* (SSM-embedding cosine, or Jaccard on the fallback path) alongside the entry —
|
|
337
|
+
* so callers can rank/threshold by TRUE similarity instead of list position.
|
|
338
|
+
* {@link recallSimilar} is the score-dropping convenience wrapper over this.
|
|
339
|
+
*/
|
|
340
|
+
async recallSimilarScored(query: string, topK: number, runtime?: SSMRuntimeRef): Promise<Array<{ entry: MemoryEntry; score: number }>> {
|
|
331
341
|
const all = await this.recallAll();
|
|
332
342
|
if (all.length === 0) return [];
|
|
333
343
|
|
|
@@ -344,7 +354,9 @@ export class MemoryStore {
|
|
|
344
354
|
? synced.index.search(queryVec, topK)
|
|
345
355
|
// Below it, an exact scan over the (already-embedded) vectors.
|
|
346
356
|
: denseSearch(queryVec, synced.items, topK, this._annThreshold);
|
|
347
|
-
return hits
|
|
357
|
+
return hits
|
|
358
|
+
.map(h => { const entry = synced.byKey.get(h.id); return entry ? { entry, score: h.score } : null; })
|
|
359
|
+
.filter((e): e is { entry: MemoryEntry; score: number } => !!e);
|
|
348
360
|
}
|
|
349
361
|
}
|
|
350
362
|
// Any failure falls through to the Jaccard path below.
|
|
@@ -359,7 +371,7 @@ export class MemoryStore {
|
|
|
359
371
|
});
|
|
360
372
|
|
|
361
373
|
scored.sort((a, b) => b.score - a.score);
|
|
362
|
-
return scored.slice(0, topK)
|
|
374
|
+
return scored.slice(0, topK);
|
|
363
375
|
}
|
|
364
376
|
|
|
365
377
|
/**
|
package/src/session/session.ts
CHANGED
|
@@ -160,12 +160,34 @@ export interface AdaptOptions {
|
|
|
160
160
|
wsla? : boolean; // Default: true (WSLA fast-adapt mode)
|
|
161
161
|
fullTrain? : boolean; // Convenience alias: sets wsla=false and epochs=5
|
|
162
162
|
onProgress? : (epoch: number, loss: number) => void;
|
|
163
|
+
/**
|
|
164
|
+
* Snapshot the weights before adapting and RESTORE them if the adapt produced
|
|
165
|
+
* a non-finite loss or clearly regressed the model's perplexity on the same
|
|
166
|
+
* text. Default true — this is the last line of defence behind WSLA + the
|
|
167
|
+
* per-step trust region that keeps write-through learning from "dying after
|
|
168
|
+
* several executions". Set false to skip the snapshot cost (e.g. bulk full
|
|
169
|
+
* training where you validate externally).
|
|
170
|
+
*/
|
|
171
|
+
rollbackOnRegression? : boolean; // Default: true
|
|
172
|
+
/** Regression tolerance: roll back if post-perplexity > pre × this. Default 1.25. */
|
|
173
|
+
regressionTolerance? : number;
|
|
163
174
|
}
|
|
164
175
|
|
|
165
176
|
export interface AdaptResult {
|
|
166
177
|
losses : number[];
|
|
167
178
|
epochCount : number;
|
|
168
179
|
durationMs : number;
|
|
180
|
+
/** True when the adapt was reverted to the pre-adapt snapshot (see below). */
|
|
181
|
+
rolledBack? : boolean;
|
|
182
|
+
/** Why it rolled back, when it did (`non-finite loss` | `perplexity regression`). */
|
|
183
|
+
rollbackReason?: string;
|
|
184
|
+
/** Perplexity on the adapt text before / after (when measured). */
|
|
185
|
+
perplexityBefore?: number;
|
|
186
|
+
perplexityAfter? : number;
|
|
187
|
+
/** Mean pre-clip gradient L2 norm across the adapt's epochs — the standard
|
|
188
|
+
* early-warning for an unstable update (it spikes before a loss goes non-finite).
|
|
189
|
+
* Undefined when the trainer didn't measure it. */
|
|
190
|
+
gradNorm?: number;
|
|
169
191
|
}
|
|
170
192
|
|
|
171
193
|
export type StorageTarget = 'indexedDB' | 'download' | 'fileSystem';
|
|
@@ -531,20 +553,68 @@ export class MambaSession {
|
|
|
531
553
|
);
|
|
532
554
|
}
|
|
533
555
|
|
|
556
|
+
const rollbackOnRegression = options.rollbackOnRegression ?? true;
|
|
557
|
+
const regressionTolerance = options.regressionTolerance ?? 1.25;
|
|
558
|
+
|
|
559
|
+
// Snapshot the weights and measure the pre-adapt perplexity so a bad adapt
|
|
560
|
+
// can be reverted. This is what makes repeated write-through learning safe:
|
|
561
|
+
// an update that produces NaN/Inf or clearly worsens the model is undone
|
|
562
|
+
// instead of persisting and compounding across executions.
|
|
563
|
+
let snapshot: ArrayBuffer | null = null;
|
|
564
|
+
let perplexityBefore: number | undefined;
|
|
565
|
+
if (rollbackOnRegression) {
|
|
566
|
+
snapshot = await this._model.exportWeights();
|
|
567
|
+
perplexityBefore = await this._trainer.evaluate(text).catch(() => undefined);
|
|
568
|
+
}
|
|
569
|
+
|
|
534
570
|
const startTime = Date.now();
|
|
571
|
+
// Capture the per-epoch grad norm (instability signal) without disturbing the
|
|
572
|
+
// caller's own progress callback.
|
|
573
|
+
const gradNorms: number[] = [];
|
|
535
574
|
const losses = await this._trainer.train(text, {
|
|
536
575
|
epochs,
|
|
537
576
|
learningRate,
|
|
538
577
|
seqLen,
|
|
539
578
|
wsla,
|
|
540
|
-
|
|
579
|
+
trackGradNorm: true,
|
|
580
|
+
onEpochEnd: (epoch, loss, gradNorm) => {
|
|
581
|
+
if (typeof gradNorm === 'number' && Number.isFinite(gradNorm)) gradNorms.push(gradNorm);
|
|
582
|
+
onProgress?.(epoch, loss);
|
|
583
|
+
},
|
|
541
584
|
});
|
|
585
|
+
const gradNorm = gradNorms.length
|
|
586
|
+
? gradNorms.reduce((a, b) => a + b, 0) / gradNorms.length
|
|
587
|
+
: undefined;
|
|
542
588
|
|
|
543
|
-
|
|
589
|
+
const base: AdaptResult = {
|
|
544
590
|
losses,
|
|
545
591
|
epochCount : losses.length,
|
|
546
592
|
durationMs : Date.now() - startTime,
|
|
593
|
+
gradNorm,
|
|
547
594
|
};
|
|
595
|
+
|
|
596
|
+
if (!rollbackOnRegression || !snapshot) return base;
|
|
597
|
+
|
|
598
|
+
// Validate the adapt. Roll back on a non-finite loss (the update poisoned
|
|
599
|
+
// the weights) or a perplexity regression beyond tolerance.
|
|
600
|
+
const nonFiniteLoss = losses.some((l) => !Number.isFinite(l));
|
|
601
|
+
const perplexityAfter = await this._trainer.evaluate(text).catch(() => Number.POSITIVE_INFINITY);
|
|
602
|
+
const regressed =
|
|
603
|
+
Number.isFinite(perplexityBefore ?? NaN) && Number.isFinite(perplexityAfter) &&
|
|
604
|
+
perplexityAfter > (perplexityBefore as number) * regressionTolerance;
|
|
605
|
+
|
|
606
|
+
if (nonFiniteLoss || !Number.isFinite(perplexityAfter) || regressed) {
|
|
607
|
+
await this._model.loadWeights(snapshot);
|
|
608
|
+
return {
|
|
609
|
+
...base,
|
|
610
|
+
rolledBack : true,
|
|
611
|
+
rollbackReason: nonFiniteLoss || !Number.isFinite(perplexityAfter) ? 'non-finite loss' : 'perplexity regression',
|
|
612
|
+
perplexityBefore,
|
|
613
|
+
perplexityAfter,
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return { ...base, rolledBack: false, perplexityBefore, perplexityAfter };
|
|
548
618
|
}
|
|
549
619
|
|
|
550
620
|
// ── Evaluation ─────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workflow/code-eval.ts — dependency-free code-validity + execution-reward checks.
|
|
3
|
+
*
|
|
4
|
+
* These power the code-specific BUILD diagnostics that grade what Evermind's
|
|
5
|
+
* generator produces when we teach it to program:
|
|
6
|
+
* • {@link analyzeCode} — structural validity (balanced delimiters/strings) +
|
|
7
|
+
* an optional JS parse. The portable "does this look
|
|
8
|
+
* like valid code" gate.
|
|
9
|
+
* • {@link runJsCases} — execution-grounded reward: run generated JS against
|
|
10
|
+
* test cases and score the pass-rate. Code's signal is
|
|
11
|
+
* that it RUNS — this turns that into a number.
|
|
12
|
+
*
|
|
13
|
+
* Both are zero-dependency (no AST library) so they run in the same WebGPU/TS
|
|
14
|
+
* runtime as the rest of the engine. Execution uses the `Function` constructor —
|
|
15
|
+
* a heuristic reward sandbox, NOT a security boundary; only feed it code from a
|
|
16
|
+
* trusted teacher / the model under training.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const OPEN: Record<string, string> = { ")": "(", "]": "[", "}": "{" };
|
|
20
|
+
|
|
21
|
+
export interface CodeAnalysis {
|
|
22
|
+
/** 0..1 structural-validity score (1 = delimiters and strings all balanced). */
|
|
23
|
+
score: number;
|
|
24
|
+
/** True when every bracket/brace/paren and string literal closes cleanly. */
|
|
25
|
+
balanced: boolean;
|
|
26
|
+
/** For language="js": whether the source parses (Function constructor). undefined otherwise. */
|
|
27
|
+
jsParse?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Score how structurally valid `code` is: walk the source tracking string state
|
|
32
|
+
* (so brackets inside strings are ignored) and a delimiter stack. A clean parse
|
|
33
|
+
* scores 1; each unmatched/mismatched delimiter or unterminated string lowers it.
|
|
34
|
+
*/
|
|
35
|
+
export function analyzeCode(code: string, language = "js"): CodeAnalysis {
|
|
36
|
+
const stack: string[] = [];
|
|
37
|
+
let mismatches = 0;
|
|
38
|
+
let delims = 0;
|
|
39
|
+
let inString: string | null = null; // the opening quote char, or null
|
|
40
|
+
let escaped = false;
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < code.length; i++) {
|
|
43
|
+
const ch = code[i]!;
|
|
44
|
+
if (inString) {
|
|
45
|
+
if (escaped) { escaped = false; continue; }
|
|
46
|
+
if (ch === "\\") { escaped = true; continue; }
|
|
47
|
+
if (ch === inString) inString = null;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (ch === '"' || ch === "'" || ch === "`") { inString = ch; continue; }
|
|
51
|
+
if (ch === "(" || ch === "[" || ch === "{") { stack.push(ch); delims++; continue; }
|
|
52
|
+
if (ch === ")" || ch === "]" || ch === "}") {
|
|
53
|
+
delims++;
|
|
54
|
+
if (stack.pop() !== OPEN[ch]) mismatches++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Anything left open is an unmatched delimiter; an open string is one more fault.
|
|
58
|
+
mismatches += stack.length;
|
|
59
|
+
const faults = mismatches + (inString ? 1 : 0);
|
|
60
|
+
const denom = Math.max(1, delims + (inString ? 1 : 0));
|
|
61
|
+
const balanced = faults === 0;
|
|
62
|
+
const score = balanced ? 1 : Math.max(0, 1 - faults / denom);
|
|
63
|
+
|
|
64
|
+
const analysis: CodeAnalysis = { score, balanced };
|
|
65
|
+
if (language === "js" || language === "ts" || language === "javascript" || language === "typescript") {
|
|
66
|
+
analysis.jsParse = jsParses(code);
|
|
67
|
+
}
|
|
68
|
+
return analysis;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** True when `code` parses as JS (Function constructor parses but does not run). */
|
|
72
|
+
function jsParses(code: string): boolean {
|
|
73
|
+
try {
|
|
74
|
+
// eslint-disable-next-line no-new-func
|
|
75
|
+
new Function(code);
|
|
76
|
+
return true;
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface CodeCase {
|
|
83
|
+
/** An expression to evaluate after the code is loaded, e.g. "add(2, 3)". */
|
|
84
|
+
call: string;
|
|
85
|
+
/** The expected result (deep-equal compared). */
|
|
86
|
+
expect: unknown;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface CodeEvalResult {
|
|
90
|
+
passed: number;
|
|
91
|
+
total: number;
|
|
92
|
+
/** passed / total (0 when there are no cases). */
|
|
93
|
+
passRate: number;
|
|
94
|
+
/** A whole-program error (e.g. syntax error) that failed every case, if any. */
|
|
95
|
+
error?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Load `code` (which defines functions) and evaluate each case's `call`
|
|
100
|
+
* expression, comparing the result to `expect`. A syntax error in `code` fails
|
|
101
|
+
* every case. Per-case runtime errors fail only that case.
|
|
102
|
+
*
|
|
103
|
+
* NOTE: heuristic reward sandbox, not a security boundary — see file header.
|
|
104
|
+
*/
|
|
105
|
+
export function runJsCases(code: string, cases: CodeCase[]): CodeEvalResult {
|
|
106
|
+
const total = cases.length;
|
|
107
|
+
if (total === 0) return { passed: 0, total: 0, passRate: 0 };
|
|
108
|
+
|
|
109
|
+
let passed = 0;
|
|
110
|
+
for (const c of cases) {
|
|
111
|
+
try {
|
|
112
|
+
// Code defines functions (hoisted); the trailing return evaluates the call.
|
|
113
|
+
// eslint-disable-next-line no-new-func
|
|
114
|
+
const fn = new Function(`${code}\n;return (${c.call});`);
|
|
115
|
+
const actual = fn();
|
|
116
|
+
if (deepEqual(actual, c.expect)) passed++;
|
|
117
|
+
} catch (err) {
|
|
118
|
+
// A syntax error throws at construction for ALL cases — report it once.
|
|
119
|
+
if (err instanceof SyntaxError) {
|
|
120
|
+
return { passed: 0, total, passRate: 0, error: err.message };
|
|
121
|
+
}
|
|
122
|
+
// Runtime error → this case simply fails.
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { passed, total, passRate: passed / total };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Structural deep equality good enough for JSON-shaped test expectations. */
|
|
129
|
+
function deepEqual(a: unknown, b: unknown): boolean {
|
|
130
|
+
if (a === b) return true;
|
|
131
|
+
if (typeof a !== typeof b || a === null || b === null) return false;
|
|
132
|
+
if (typeof a !== "object") return Object.is(a, b);
|
|
133
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
134
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
135
|
+
return a.every((v, i) => deepEqual(v, b[i]));
|
|
136
|
+
}
|
|
137
|
+
const ao = a as Record<string, unknown>;
|
|
138
|
+
const bo = b as Record<string, unknown>;
|
|
139
|
+
const ak = Object.keys(ao);
|
|
140
|
+
const bk = Object.keys(bo);
|
|
141
|
+
if (ak.length !== bk.length) return false;
|
|
142
|
+
return ak.every((k) => deepEqual(ao[k], bo[k]));
|
|
143
|
+
}
|
package/src/workflow/index.ts
CHANGED
|
@@ -6,14 +6,22 @@
|
|
|
6
6
|
|
|
7
7
|
export type { WorkflowConfig, WorkflowStepConfig, StepTypeInfo, StepFactory } from "./types.js";
|
|
8
8
|
export { StepTypeRegistry, createDefaultRegistry, defaultStepRegistry } from "./registry.js";
|
|
9
|
-
export { BUILTIN_STEPS } from "./steps.js";
|
|
9
|
+
export { BUILTIN_STEPS, selectBestCandidate, stripCodeFences } from "./steps.js";
|
|
10
10
|
export {
|
|
11
11
|
AGENTIC_SEVEN_LAYER,
|
|
12
12
|
TRAIN_LLM,
|
|
13
|
+
TEACH_CODE,
|
|
13
14
|
WORKFLOW_TEMPLATES,
|
|
14
15
|
getTemplate,
|
|
15
16
|
cloneTemplate,
|
|
16
17
|
} from "./templates.js";
|
|
18
|
+
export {
|
|
19
|
+
analyzeCode,
|
|
20
|
+
runJsCases,
|
|
21
|
+
type CodeAnalysis,
|
|
22
|
+
type CodeCase,
|
|
23
|
+
type CodeEvalResult,
|
|
24
|
+
} from "./code-eval.js";
|
|
17
25
|
export {
|
|
18
26
|
compileWorkflow,
|
|
19
27
|
validateWorkflow,
|