genesis-ai-cli 10.8.1 → 11.0.0
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/active-inference/actions.d.ts +5 -0
- package/dist/src/active-inference/actions.js +150 -9
- package/dist/src/active-inference/autonomous-loop.d.ts +22 -0
- package/dist/src/active-inference/autonomous-loop.js +84 -1
- package/dist/src/active-inference/core.d.ts +1 -9
- package/dist/src/active-inference/core.js +109 -29
- package/dist/src/active-inference/experience-replay.d.ts +124 -0
- package/dist/src/active-inference/experience-replay.js +227 -0
- package/dist/src/active-inference/index.d.ts +1 -0
- package/dist/src/active-inference/index.js +6 -1
- package/dist/src/active-inference/observations.d.ts +20 -0
- package/dist/src/active-inference/observations.js +61 -1
- package/dist/src/active-inference/types.d.ts +28 -0
- package/dist/src/active-inference/types.js +2 -0
- package/dist/src/active-inference/value-integration.js +1 -0
- package/dist/src/index.js +9 -2
- package/dist/src/services/competitive-intel.d.ts +136 -0
- package/dist/src/services/competitive-intel.js +389 -0
- package/package.json +1 -1
|
@@ -50,6 +50,11 @@ export declare class ActionExecutorManager {
|
|
|
50
50
|
* Execute an action with current context
|
|
51
51
|
*/
|
|
52
52
|
execute(action: ActionType): Promise<ActionResult>;
|
|
53
|
+
/**
|
|
54
|
+
* v10.8.2: Chain successful action outputs as inputs for downstream actions.
|
|
55
|
+
* Implements the opportunity pipeline: scan → evaluate → build → monetize
|
|
56
|
+
*/
|
|
57
|
+
private chainContext;
|
|
53
58
|
/**
|
|
54
59
|
* Get action history
|
|
55
60
|
*/
|
|
@@ -211,15 +211,67 @@ registerAction('recall.memory', async (context) => {
|
|
|
211
211
|
* plan.goals: Decompose goals into steps
|
|
212
212
|
*/
|
|
213
213
|
registerAction('plan.goals', async (context) => {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
214
|
+
const start = Date.now();
|
|
215
|
+
const goal = context.goal || context.parameters?.goal || 'generate revenue autonomously';
|
|
216
|
+
try {
|
|
217
|
+
const mcp = (0, index_js_1.getMCPClient)();
|
|
218
|
+
// Use LLM to decompose goal into actionable steps
|
|
219
|
+
const result = await mcp.call('openai', 'openai_chat', {
|
|
220
|
+
model: 'gpt-4o-mini',
|
|
221
|
+
messages: [{
|
|
222
|
+
role: 'system',
|
|
223
|
+
content: 'You are an AI planner for an autonomous agent. Decompose the goal into 3-5 concrete, actionable steps. Return JSON array of objects: [{step: string, action: string, priority: "high"|"medium"|"low"}]. Actions should be one of: opportunity.scan, opportunity.evaluate, opportunity.build, opportunity.monetize, web.search, market.analyze, econ.optimize, deploy.service, content.generate.'
|
|
224
|
+
}, {
|
|
225
|
+
role: 'user',
|
|
226
|
+
content: `Goal: ${goal}\n\nCurrent context: ${JSON.stringify(context.parameters || {}).slice(0, 500)}`
|
|
227
|
+
}],
|
|
228
|
+
temperature: 0.7,
|
|
229
|
+
max_tokens: 500,
|
|
230
|
+
});
|
|
231
|
+
// Parse LLM response
|
|
232
|
+
let steps = [];
|
|
233
|
+
try {
|
|
234
|
+
const r = result;
|
|
235
|
+
const content = r?.data?.choices?.[0]?.message?.content || r?.choices?.[0]?.message?.content || '[]';
|
|
236
|
+
const jsonMatch = content.match(/\[[\s\S]*\]/);
|
|
237
|
+
if (jsonMatch) {
|
|
238
|
+
steps = JSON.parse(jsonMatch[0]);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
// LLM didn't return valid JSON, use defaults
|
|
243
|
+
steps = [
|
|
244
|
+
{ step: 'Scan for revenue opportunities', action: 'opportunity.scan', priority: 'high' },
|
|
245
|
+
{ step: 'Evaluate best opportunity', action: 'opportunity.evaluate', priority: 'high' },
|
|
246
|
+
{ step: 'Build the service', action: 'opportunity.build', priority: 'medium' },
|
|
247
|
+
{ step: 'Set up monetization', action: 'opportunity.monetize', priority: 'medium' },
|
|
248
|
+
];
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
success: true,
|
|
252
|
+
action: 'plan.goals',
|
|
253
|
+
data: { goal, steps, source: 'llm' },
|
|
254
|
+
duration: Date.now() - start,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// LLM not available, use heuristic defaults
|
|
259
|
+
return {
|
|
260
|
+
success: true,
|
|
261
|
+
action: 'plan.goals',
|
|
262
|
+
data: {
|
|
263
|
+
goal,
|
|
264
|
+
steps: [
|
|
265
|
+
{ step: 'Scan for revenue opportunities', action: 'opportunity.scan', priority: 'high' },
|
|
266
|
+
{ step: 'Evaluate best opportunity', action: 'opportunity.evaluate', priority: 'high' },
|
|
267
|
+
{ step: 'Build the service', action: 'opportunity.build', priority: 'medium' },
|
|
268
|
+
{ step: 'Set up monetization', action: 'opportunity.monetize', priority: 'medium' },
|
|
269
|
+
],
|
|
270
|
+
source: 'heuristic',
|
|
271
|
+
},
|
|
272
|
+
duration: Date.now() - start,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
223
275
|
});
|
|
224
276
|
/**
|
|
225
277
|
* verify.ethics: Check ethical constraints
|
|
@@ -1706,12 +1758,101 @@ class ActionExecutorManager {
|
|
|
1706
1758
|
async execute(action) {
|
|
1707
1759
|
const result = await executeAction(action, this.context);
|
|
1708
1760
|
this.history.push(result);
|
|
1761
|
+
// v10.8.2: Context Chain - pipe action outputs to downstream actions
|
|
1762
|
+
if (result.success && result.data) {
|
|
1763
|
+
this.chainContext(action, result.data);
|
|
1764
|
+
}
|
|
1709
1765
|
// Limit history to last 100 actions
|
|
1710
1766
|
if (this.history.length > 100) {
|
|
1711
1767
|
this.history = this.history.slice(-100);
|
|
1712
1768
|
}
|
|
1713
1769
|
return result;
|
|
1714
1770
|
}
|
|
1771
|
+
/**
|
|
1772
|
+
* v10.8.2: Chain successful action outputs as inputs for downstream actions.
|
|
1773
|
+
* Implements the opportunity pipeline: scan → evaluate → build → monetize
|
|
1774
|
+
*/
|
|
1775
|
+
chainContext(action, data) {
|
|
1776
|
+
if (!this.context.parameters)
|
|
1777
|
+
this.context.parameters = {};
|
|
1778
|
+
const params = this.context.parameters;
|
|
1779
|
+
switch (action) {
|
|
1780
|
+
case 'opportunity.scan':
|
|
1781
|
+
// Scan found opportunities → extract titles → feed to evaluate
|
|
1782
|
+
if (data && typeof data === 'object') {
|
|
1783
|
+
const scanData = data;
|
|
1784
|
+
const allFindings = [];
|
|
1785
|
+
// Extract from Brave search results format
|
|
1786
|
+
const results = scanData.results || [];
|
|
1787
|
+
for (const r of results) {
|
|
1788
|
+
const webResults = r.findings?.web?.results || r.findings?.data?.web?.results || [];
|
|
1789
|
+
for (const item of webResults) {
|
|
1790
|
+
if (item.title)
|
|
1791
|
+
allFindings.push(`${item.title}: ${item.description || ''}`);
|
|
1792
|
+
}
|
|
1793
|
+
// Also handle direct array format
|
|
1794
|
+
if (Array.isArray(r.findings)) {
|
|
1795
|
+
for (const item of r.findings) {
|
|
1796
|
+
if (typeof item === 'string')
|
|
1797
|
+
allFindings.push(item);
|
|
1798
|
+
else if (item?.title)
|
|
1799
|
+
allFindings.push(item.title);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
if (allFindings.length > 0) {
|
|
1804
|
+
params.opportunity = allFindings[0];
|
|
1805
|
+
params.opportunities = allFindings.slice(0, 5);
|
|
1806
|
+
}
|
|
1807
|
+
else {
|
|
1808
|
+
// Fallback: use query context as opportunity seed
|
|
1809
|
+
params.opportunity = scanData.scanType === 'api'
|
|
1810
|
+
? 'AI-powered API service for developers'
|
|
1811
|
+
: 'Micro-SaaS tool for unmet developer need';
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
break;
|
|
1815
|
+
case 'opportunity.evaluate':
|
|
1816
|
+
// Evaluation result → feed to build
|
|
1817
|
+
if (data && typeof data === 'object') {
|
|
1818
|
+
const evalResult = data;
|
|
1819
|
+
if (evalResult.recommendation === 'proceed' || evalResult.feasibility > 0.5) {
|
|
1820
|
+
params.plan = {
|
|
1821
|
+
name: evalResult.opportunity || params.opportunity || 'genesis-service',
|
|
1822
|
+
description: evalResult.description || 'AI-powered service',
|
|
1823
|
+
type: evalResult.type || 'api',
|
|
1824
|
+
features: evalResult.features || ['core functionality'],
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
break;
|
|
1829
|
+
case 'opportunity.build':
|
|
1830
|
+
// Build result → feed to monetize
|
|
1831
|
+
if (data && typeof data === 'object') {
|
|
1832
|
+
const buildResult = data;
|
|
1833
|
+
params.service = {
|
|
1834
|
+
name: buildResult.name || params.plan?.name || 'genesis-service',
|
|
1835
|
+
description: buildResult.description || 'Built by Genesis',
|
|
1836
|
+
pricing: buildResult.pricing || 999, // $9.99/month in cents
|
|
1837
|
+
interval: 'month',
|
|
1838
|
+
deployUrl: buildResult.deployUrl || buildResult.repoUrl,
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
break;
|
|
1842
|
+
case 'market.analyze':
|
|
1843
|
+
// Market analysis → inform opportunity scanning
|
|
1844
|
+
if (data && typeof data === 'object') {
|
|
1845
|
+
params.marketContext = data;
|
|
1846
|
+
}
|
|
1847
|
+
break;
|
|
1848
|
+
case 'plan.goals':
|
|
1849
|
+
// Goal plan → inform task execution
|
|
1850
|
+
if (data && typeof data === 'object' && 'steps' in data) {
|
|
1851
|
+
params.goalSteps = data.steps;
|
|
1852
|
+
}
|
|
1853
|
+
break;
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1715
1856
|
/**
|
|
1716
1857
|
* Get action history
|
|
1717
1858
|
*/
|
|
@@ -18,6 +18,10 @@ export interface AutonomousLoopConfig {
|
|
|
18
18
|
persistModelPath: string;
|
|
19
19
|
persistEveryN: number;
|
|
20
20
|
loadOnStart: boolean;
|
|
21
|
+
replayEveryN: number;
|
|
22
|
+
replayBatchSize: number;
|
|
23
|
+
dreamEveryN: number;
|
|
24
|
+
dreamBatchSize: number;
|
|
21
25
|
verbose: boolean;
|
|
22
26
|
}
|
|
23
27
|
export declare const DEFAULT_LOOP_CONFIG: AutonomousLoopConfig;
|
|
@@ -44,6 +48,8 @@ export declare class AutonomousLoop {
|
|
|
44
48
|
private plateauCycles;
|
|
45
49
|
private deepAIFActive;
|
|
46
50
|
private deepAIF;
|
|
51
|
+
private replayBuffer;
|
|
52
|
+
private previousObservation;
|
|
47
53
|
constructor(config?: Partial<AutonomousLoopConfig>);
|
|
48
54
|
/**
|
|
49
55
|
* Run the autonomous loop
|
|
@@ -67,6 +73,21 @@ export declare class AutonomousLoop {
|
|
|
67
73
|
* Subscribe to stop events
|
|
68
74
|
*/
|
|
69
75
|
onStop(handler: (reason: string, stats: LoopStats) => void): () => void;
|
|
76
|
+
/**
|
|
77
|
+
* Run experience replay: sample a batch from buffer and re-learn.
|
|
78
|
+
* This strengthens A/B matrix updates for important past experiences.
|
|
79
|
+
*/
|
|
80
|
+
private runExperienceReplay;
|
|
81
|
+
/**
|
|
82
|
+
* Run dream consolidation: deep replay of high-surprise experiences.
|
|
83
|
+
* This is the "sleep" phase where the model integrates difficult experiences.
|
|
84
|
+
*
|
|
85
|
+
* Differences from regular replay:
|
|
86
|
+
* - Focuses on highest-surprise experiences
|
|
87
|
+
* - Runs multiple iterations per experience
|
|
88
|
+
* - Prunes consolidated experiences afterward
|
|
89
|
+
*/
|
|
90
|
+
private runDreamConsolidation;
|
|
70
91
|
/**
|
|
71
92
|
* Save learned model to disk.
|
|
72
93
|
* Persists A/B matrices, beliefs, and action counts between sessions.
|
|
@@ -85,6 +106,7 @@ export declare class AutonomousLoop {
|
|
|
85
106
|
worldState: string;
|
|
86
107
|
coupling: string;
|
|
87
108
|
goalProgress: string;
|
|
109
|
+
economic: string;
|
|
88
110
|
};
|
|
89
111
|
getStats(): LoopStats;
|
|
90
112
|
/**
|
|
@@ -48,6 +48,7 @@ const observations_js_1 = require("./observations.js");
|
|
|
48
48
|
const actions_js_1 = require("./actions.js");
|
|
49
49
|
const types_js_1 = require("./types.js");
|
|
50
50
|
const deep_aif_js_1 = require("./deep-aif.js");
|
|
51
|
+
const experience_replay_js_1 = require("./experience-replay.js");
|
|
51
52
|
const fs = __importStar(require("fs"));
|
|
52
53
|
const path = __importStar(require("path"));
|
|
53
54
|
exports.DEFAULT_LOOP_CONFIG = {
|
|
@@ -60,6 +61,10 @@ exports.DEFAULT_LOOP_CONFIG = {
|
|
|
60
61
|
persistModelPath: '.genesis/learned-model.json',
|
|
61
62
|
persistEveryN: 10, // Save every 10 cycles
|
|
62
63
|
loadOnStart: true, // Resume learning from previous session
|
|
64
|
+
replayEveryN: 5, // Replay every 5 cycles
|
|
65
|
+
replayBatchSize: 8, // 8 experiences per replay
|
|
66
|
+
dreamEveryN: 50, // Dream consolidation every 50 cycles
|
|
67
|
+
dreamBatchSize: 16, // 16 high-surprise experiences per dream
|
|
63
68
|
verbose: false,
|
|
64
69
|
};
|
|
65
70
|
// ============================================================================
|
|
@@ -85,11 +90,15 @@ class AutonomousLoop {
|
|
|
85
90
|
plateauCycles = 0; // Consecutive cycles with near-zero learning velocity
|
|
86
91
|
deepAIFActive = false; // Whether Deep-AIF has been activated
|
|
87
92
|
deepAIF = null;
|
|
93
|
+
// v11.0: Experience replay buffer
|
|
94
|
+
replayBuffer;
|
|
95
|
+
previousObservation = null;
|
|
88
96
|
constructor(config = {}) {
|
|
89
97
|
this.config = { ...exports.DEFAULT_LOOP_CONFIG, ...config };
|
|
90
98
|
this.engine = (0, core_js_1.createActiveInferenceEngine)();
|
|
91
99
|
this.observations = (0, observations_js_1.createObservationGatherer)();
|
|
92
100
|
this.actions = (0, actions_js_1.createActionExecutorManager)();
|
|
101
|
+
this.replayBuffer = (0, experience_replay_js_1.createExperienceReplayBuffer)();
|
|
93
102
|
// Subscribe to engine events
|
|
94
103
|
this.engine.on(this.handleEngineEvent.bind(this));
|
|
95
104
|
}
|
|
@@ -198,7 +207,23 @@ class AutonomousLoop {
|
|
|
198
207
|
this.observations.recordToolResult(result.success, result.duration);
|
|
199
208
|
// 5. v10.8: Record learning event
|
|
200
209
|
const surprise = this.engine.getStats().averageSurprise;
|
|
201
|
-
|
|
210
|
+
const outcome = result.success ? 'positive' : 'negative';
|
|
211
|
+
this.engine.recordLearningEvent(action, surprise, outcome);
|
|
212
|
+
// 5a. v11.0: Store experience in replay buffer
|
|
213
|
+
if (this.previousObservation) {
|
|
214
|
+
this.replayBuffer.store({
|
|
215
|
+
timestamp: Date.now(),
|
|
216
|
+
observation: this.previousObservation,
|
|
217
|
+
action,
|
|
218
|
+
actionIdx: types_js_1.ACTIONS.indexOf(action),
|
|
219
|
+
nextObservation: obs,
|
|
220
|
+
surprise,
|
|
221
|
+
outcome,
|
|
222
|
+
beliefs: { ...beliefs },
|
|
223
|
+
nextBeliefs: this.engine.getBeliefs(),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
this.previousObservation = obs;
|
|
202
227
|
// 5b. v10.8.1: Meta-learning triggers (every 20 cycles)
|
|
203
228
|
if (this.cycleCount % 20 === 0 && this.cycleCount >= 40) {
|
|
204
229
|
const patterns = this.engine.analyzeLearningPatterns();
|
|
@@ -223,6 +248,14 @@ class AutonomousLoop {
|
|
|
223
248
|
this.plateauCycles = 0;
|
|
224
249
|
}
|
|
225
250
|
}
|
|
251
|
+
// 5c. v11.0: Experience replay (offline learning from past experiences)
|
|
252
|
+
if (this.config.replayEveryN > 0 && this.cycleCount % this.config.replayEveryN === 0) {
|
|
253
|
+
this.runExperienceReplay();
|
|
254
|
+
}
|
|
255
|
+
// 5d. v11.0: Dream consolidation (deep replay of high-surprise events)
|
|
256
|
+
if (this.config.dreamEveryN > 0 && this.cycleCount % this.config.dreamEveryN === 0 && this.cycleCount > 0) {
|
|
257
|
+
this.runDreamConsolidation();
|
|
258
|
+
}
|
|
226
259
|
// 6. v10.8: Persist model periodically
|
|
227
260
|
if (this.config.persistEveryN > 0 && this.cycleCount % this.config.persistEveryN === 0) {
|
|
228
261
|
this.saveModel();
|
|
@@ -299,6 +332,56 @@ class AutonomousLoop {
|
|
|
299
332
|
};
|
|
300
333
|
}
|
|
301
334
|
// ============================================================================
|
|
335
|
+
// v11.0: Experience Replay & Dream Consolidation
|
|
336
|
+
// ============================================================================
|
|
337
|
+
/**
|
|
338
|
+
* Run experience replay: sample a batch from buffer and re-learn.
|
|
339
|
+
* This strengthens A/B matrix updates for important past experiences.
|
|
340
|
+
*/
|
|
341
|
+
runExperienceReplay() {
|
|
342
|
+
const batch = this.replayBuffer.sampleBatch(this.config.replayBatchSize);
|
|
343
|
+
if (batch.experiences.length === 0)
|
|
344
|
+
return;
|
|
345
|
+
if (this.config.verbose) {
|
|
346
|
+
console.log(`[AI Loop] Replay: ${batch.experiences.length} experiences (avg surprise: ${batch.avgSurprise.toFixed(2)})`);
|
|
347
|
+
}
|
|
348
|
+
// Re-learn from each experience (offline update)
|
|
349
|
+
for (const exp of batch.experiences) {
|
|
350
|
+
// Feed the observation pair back through the engine's learning
|
|
351
|
+
// This is equivalent to "replaying" the experience in the model
|
|
352
|
+
this.engine.learn(exp.observation, exp.nextObservation, exp.beliefs, exp.actionIdx);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Run dream consolidation: deep replay of high-surprise experiences.
|
|
357
|
+
* This is the "sleep" phase where the model integrates difficult experiences.
|
|
358
|
+
*
|
|
359
|
+
* Differences from regular replay:
|
|
360
|
+
* - Focuses on highest-surprise experiences
|
|
361
|
+
* - Runs multiple iterations per experience
|
|
362
|
+
* - Prunes consolidated experiences afterward
|
|
363
|
+
*/
|
|
364
|
+
runDreamConsolidation() {
|
|
365
|
+
const highSurprise = this.replayBuffer.sampleHighSurprise(this.config.dreamBatchSize);
|
|
366
|
+
if (highSurprise.length === 0)
|
|
367
|
+
return;
|
|
368
|
+
if (this.config.verbose) {
|
|
369
|
+
const avgS = highSurprise.reduce((s, e) => s + e.surprise, 0) / highSurprise.length;
|
|
370
|
+
console.log(`[AI Loop] Dream: consolidating ${highSurprise.length} high-surprise experiences (avg: ${avgS.toFixed(2)})`);
|
|
371
|
+
}
|
|
372
|
+
// Deep replay: 3 iterations per experience for stronger consolidation
|
|
373
|
+
for (let iter = 0; iter < 3; iter++) {
|
|
374
|
+
for (const exp of highSurprise) {
|
|
375
|
+
this.engine.learn(exp.observation, exp.nextObservation, exp.beliefs, exp.actionIdx);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// Prune fully consolidated experiences
|
|
379
|
+
const pruned = this.replayBuffer.pruneConsolidated();
|
|
380
|
+
if (pruned > 0 && this.config.verbose) {
|
|
381
|
+
console.log(`[AI Loop] Dream: pruned ${pruned} consolidated experiences`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// ============================================================================
|
|
302
385
|
// v10.8: Model Persistence (save/load learned matrices)
|
|
303
386
|
// ============================================================================
|
|
304
387
|
/**
|
|
@@ -16,15 +16,6 @@
|
|
|
16
16
|
* NO external dependencies - pure TypeScript math.
|
|
17
17
|
*/
|
|
18
18
|
import { Observation, Beliefs, Policy, AMatrix, BMatrix, ActiveInferenceConfig, ActionType, AIEventHandler } from './types.js';
|
|
19
|
-
/**
|
|
20
|
-
* v10.8: Economic preferences for autonomous revenue.
|
|
21
|
-
* Separate from main C matrix to avoid breaking existing EFE computation.
|
|
22
|
-
* Used by the autonomous loop to bias action selection toward revenue actions
|
|
23
|
-
* when economic health is low.
|
|
24
|
-
*/
|
|
25
|
-
export declare const ECONOMIC_PREFERENCES: {
|
|
26
|
-
readonly economic: readonly [-8, -3, 1, 6];
|
|
27
|
-
};
|
|
28
19
|
export declare class ActiveInferenceEngine {
|
|
29
20
|
private config;
|
|
30
21
|
private A;
|
|
@@ -140,6 +131,7 @@ export declare class ActiveInferenceEngine {
|
|
|
140
131
|
worldState: string;
|
|
141
132
|
coupling: string;
|
|
142
133
|
goalProgress: string;
|
|
134
|
+
economic: string;
|
|
143
135
|
};
|
|
144
136
|
/**
|
|
145
137
|
* Export learned matrices for persistence between sessions.
|