mark-improving-agent 2.3.0 → 2.3.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/VERSION CHANGED
@@ -1 +1 @@
1
- 2.3.0
1
+ 2.3.2
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Behavior Playbook - Pattern-Based Playbook Extraction System
3
+ *
4
+ * Inspired by ReflexioAI's self-improvement framework:
5
+ * - Extracts actionable playbooks from user correction patterns
6
+ * - Clusters similar behaviors and aggregates into reusable playbooks
7
+ * - Tracks playbook effectiveness through success/failure metrics
8
+ * - Enables cross-session learning (what one user teaches, all sessions benefit)
9
+ *
10
+ * @module core/behavior-playbook
11
+ * @fileoverview Playbook extraction from interaction patterns
12
+ */
13
+ import { createLogger } from '../utils/logger.js';
14
+ import { atomicWriteJSON, readJSON, ensureDir } from '../storage/archive.js';
15
+ import { randomUUID } from 'crypto';
16
+ const logger = createLogger('[BehaviorPlaybook]');
17
+ const DEFAULT_CONFIG = {
18
+ minConfidence: 0.6,
19
+ maxPlaybooks: 100,
20
+ clusterThreshold: 0.8,
21
+ autoExtract: true,
22
+ };
23
+ const PLAYBOOKS_FILE = 'behavior-playbooks.json';
24
+ const CORRECTIONS_FILE = 'correction-events.json';
25
+ function extractContext(text) {
26
+ if (text.length <= 50)
27
+ return text;
28
+ return text.slice(0, 47) + '...';
29
+ }
30
+ function extractPlaybookFromCorrection(event) {
31
+ const { userMessage, correction } = event;
32
+ if (correction.length < 5 || userMessage.length < 5)
33
+ return null;
34
+ let trigger = '';
35
+ let action = '';
36
+ const tags = [];
37
+ const lowerCorrection = correction.toLowerCase();
38
+ if (lowerCorrection.startsWith("don't") || lowerCorrection.startsWith("do not") || lowerCorrection.startsWith("never")) {
39
+ const match = correction.match(/(?:don't|do not|never)\s+(.+)/i);
40
+ if (match) {
41
+ const avoided = match[1].trim();
42
+ trigger = `When about to: ${avoided}`;
43
+ action = `Avoid: ${avoided}`;
44
+ tags.push('avoid', 'user-warning');
45
+ }
46
+ }
47
+ else if (lowerCorrection.startsWith("you should") || lowerCorrection.startsWith("you need to") || lowerCorrection.startsWith("always")) {
48
+ const match = correction.match(/(?:you should|you need to|always)\s+(.+)/i);
49
+ if (match) {
50
+ const required = match[1].trim();
51
+ trigger = `When encountering relevant situation`;
52
+ action = `Always: ${required}`;
53
+ tags.push('positive', 'user-guidance');
54
+ }
55
+ }
56
+ else {
57
+ // Generic extraction
58
+ trigger = extractContext(userMessage);
59
+ action = correction;
60
+ tags.push('general', 'user-correction');
61
+ }
62
+ if (!trigger || !action)
63
+ return null;
64
+ return { trigger, action, tags, source: 'user-correction' };
65
+ }
66
+ function cosineSimilarity(a, b) {
67
+ const wordsA = new Set(a.toLowerCase().split(/\s+/));
68
+ const wordsB = new Set(b.toLowerCase().split(/\s+/));
69
+ const intersection = new Set([...wordsA].filter(x => wordsB.has(x)));
70
+ const union = new Set([...wordsA, ...wordsB]);
71
+ if (union.size === 0)
72
+ return 0;
73
+ return intersection.size / union.size;
74
+ }
75
+ export function createBehaviorPlaybook(dataDir, config = {}) {
76
+ const cfg = { ...DEFAULT_CONFIG, ...config };
77
+ const playbooks = [];
78
+ const corrections = [];
79
+ let dirty = false;
80
+ async function persist() {
81
+ if (!dirty)
82
+ return;
83
+ await Promise.all([
84
+ atomicWriteJSON(`${dataDir}/${PLAYBOOKS_FILE}`, playbooks),
85
+ atomicWriteJSON(`${dataDir}/${CORRECTIONS_FILE}`, corrections),
86
+ ]);
87
+ dirty = false;
88
+ }
89
+ async function boot() {
90
+ await ensureDir(dataDir);
91
+ const [loadedPlaybooks, loadedCorrections] = await Promise.all([
92
+ readJSON(`${dataDir}/${PLAYBOOKS_FILE}`, []),
93
+ readJSON(`${dataDir}/${CORRECTIONS_FILE}`, []),
94
+ ]);
95
+ if (loadedPlaybooks?.length)
96
+ playbooks.push(...loadedPlaybooks);
97
+ if (loadedCorrections?.length)
98
+ corrections.push(...loadedCorrections);
99
+ logger.info(`Booted with ${playbooks.length} playbooks, ${corrections.length} corrections`);
100
+ }
101
+ function addCorrection(event) {
102
+ const full = { ...event, id: randomUUID(), timestamp: Date.now() };
103
+ corrections.push(full);
104
+ if (corrections.length > 500)
105
+ corrections.splice(0, corrections.length - 500);
106
+ if (cfg.autoExtract)
107
+ extractPlaybook(full);
108
+ return full;
109
+ }
110
+ function extractPlaybook(event) {
111
+ const partial = extractPlaybookFromCorrection(event);
112
+ if (!partial)
113
+ return null;
114
+ // Check for similar existing playbook
115
+ for (const existing of playbooks) {
116
+ if (cosineSimilarity(existing.action, partial.action) > cfg.clusterThreshold) {
117
+ // Update existing playbook's confidence
118
+ existing.successCount++;
119
+ existing.confidence = Math.min(0.95, existing.confidence + 0.05);
120
+ existing.lastUsed = Date.now();
121
+ dirty = true;
122
+ return existing;
123
+ }
124
+ }
125
+ const playbook = {
126
+ id: randomUUID(),
127
+ trigger: partial.trigger,
128
+ action: partial.action,
129
+ confidence: 0.5,
130
+ successCount: 0,
131
+ failureCount: 0,
132
+ source: partial.source,
133
+ createdAt: Date.now(),
134
+ lastUsed: Date.now(),
135
+ tags: partial.tags,
136
+ version: 1,
137
+ };
138
+ playbooks.push(playbook);
139
+ if (playbooks.length > cfg.maxPlaybooks) {
140
+ playbooks.splice(0, playbooks.length - cfg.maxPlaybooks);
141
+ }
142
+ dirty = true;
143
+ logger.info(`Extracted playbook: ${playbook.action.slice(0, 50)}`);
144
+ return playbook;
145
+ }
146
+ function getPlaybook(id) {
147
+ return playbooks.find(p => p.id === id) ?? null;
148
+ }
149
+ function searchPlaybooks(query, limit = 5) {
150
+ const results = [];
151
+ for (const p of playbooks) {
152
+ if (p.confidence < cfg.minConfidence)
153
+ continue;
154
+ const queryLower = query.toLowerCase();
155
+ const triggerMatch = p.trigger.toLowerCase().includes(queryLower);
156
+ const actionMatch = p.action.toLowerCase().includes(queryLower);
157
+ const tagMatch = p.tags.some(t => t.toLowerCase().includes(queryLower));
158
+ if (!triggerMatch && !actionMatch && !tagMatch)
159
+ continue;
160
+ let score = 0;
161
+ let matchType = 'fuzzy';
162
+ if (actionMatch) {
163
+ score += 0.6;
164
+ matchType = 'exact';
165
+ }
166
+ if (triggerMatch)
167
+ score += 0.3;
168
+ if (tagMatch) {
169
+ score += 0.2;
170
+ if (matchType !== 'exact')
171
+ matchType = 'tag';
172
+ }
173
+ score *= p.confidence;
174
+ results.push({ playbook: p, relevanceScore: score, matchType });
175
+ }
176
+ return results.sort((a, b) => b.relevanceScore - a.relevanceScore).slice(0, limit);
177
+ }
178
+ function getPlaybooksByTag(tag) {
179
+ return playbooks.filter(p => p.tags.some(t => t.toLowerCase().includes(tag.toLowerCase())));
180
+ }
181
+ function markPlaybookHit(id, success) {
182
+ const p = playbooks.find(p => p.id === id);
183
+ if (!p)
184
+ return;
185
+ if (success) {
186
+ p.successCount++;
187
+ p.confidence = Math.min(0.95, p.confidence + (0.95 - p.confidence) * 0.1);
188
+ }
189
+ else {
190
+ p.failureCount++;
191
+ p.confidence = Math.max(0.3, p.confidence - 0.05);
192
+ }
193
+ p.lastUsed = Date.now();
194
+ dirty = true;
195
+ }
196
+ function getActivePlaybooks() {
197
+ return playbooks.filter(p => p.confidence >= cfg.minConfidence);
198
+ }
199
+ function getStats() {
200
+ const avgConfidence = playbooks.length ? playbooks.reduce((s, p) => s + p.confidence, 0) / playbooks.length : 0;
201
+ const tagCounts = new Map();
202
+ for (const p of playbooks) {
203
+ for (const t of p.tags) {
204
+ tagCounts.set(t, (tagCounts.get(t) ?? 0) + 1);
205
+ }
206
+ }
207
+ const topTags = [...tagCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([t]) => t);
208
+ return { totalPlaybooks: playbooks.length, avgConfidence, topTags };
209
+ }
210
+ return { addCorrection, extractPlaybook, getPlaybook, searchPlaybooks, getPlaybooksByTag, markPlaybookHit, getActivePlaybooks, getStats, boot, persist };
211
+ }
@@ -9,3 +9,4 @@ export * from './hopfield-network.js';
9
9
  export * from './adaptive-rag.js';
10
10
  export { createContextFragmentationEngine } from './context-fragmentation.js';
11
11
  export { createHybridSearchEngine, createBM25Index, bm25Score, normalizeBM25Scores, DEFAULT_HYBRID_CONFIG } from './hybrid-search.js';
12
+ export { createPatternRecognizer } from './pattern-recognizer.js';
@@ -0,0 +1,358 @@
1
+ /**
2
+ * Memory Pattern Recognizer
3
+ *
4
+ * Analyzes recurring patterns in memory storage and retrieval to enable
5
+ * personalized memory prioritization. Detects temporal patterns, interaction
6
+ * styles, and topic frequencies to optimize memory consolidation.
7
+ *
8
+ * Based on: Adaptive Memory Systems research - patterns in when/how users store memories
9
+ *
10
+ * @module core/memory
11
+ * @fileoverview Pattern recognition for memory optimization
12
+ */
13
+ import { createLogger } from '../../utils/logger.js';
14
+ const logger = createLogger('[PatternRecognizer]');
15
+ const DEFAULT_CONFIG = {
16
+ minMemories: 10,
17
+ temporalWindowDays: 30,
18
+ enableAnomalyDetection: true,
19
+ };
20
+ /**
21
+ * Creates a Memory Pattern Recognizer
22
+ *
23
+ * Analyzes memory entries to detect patterns in:
24
+ * - Temporal distribution (when memories are stored)
25
+ * - Topic frequency and coherence
26
+ * - Tag co-occurrence
27
+ * - User interaction styles
28
+ *
29
+ * @param entries - Memory entries to analyze
30
+ * @param config - Configuration options
31
+ * @returns Pattern analysis results
32
+ */
33
+ export function createPatternRecognizer(entries, config = {}) {
34
+ const cfg = { ...DEFAULT_CONFIG, ...config };
35
+ return new PatternRecognizerImpl(entries, cfg);
36
+ }
37
+ class PatternRecognizerImpl {
38
+ entries;
39
+ config;
40
+ tagCooccurrence = new Map();
41
+ analysisCache = null;
42
+ constructor(entries, config) {
43
+ this.entries = entries;
44
+ this.config = config;
45
+ this.buildTagCooccurrence();
46
+ }
47
+ /**
48
+ * Build tag co-occurrence matrix for pattern detection
49
+ */
50
+ buildTagCooccurrence() {
51
+ this.tagCooccurrence.clear();
52
+ for (const entry of this.entries) {
53
+ for (const tag of entry.tags) {
54
+ if (!this.tagCooccurrence.has(tag)) {
55
+ this.tagCooccurrence.set(tag, {
56
+ tag,
57
+ cooccursWith: new Map(),
58
+ totalCount: 0,
59
+ });
60
+ }
61
+ const tagData = this.tagCooccurrence.get(tag);
62
+ tagData.totalCount++;
63
+ // Count co-occurrences with other tags
64
+ for (const otherTag of entry.tags) {
65
+ if (otherTag !== tag) {
66
+ const current = tagData.cooccursWith.get(otherTag) || 0;
67
+ tagData.cooccursWith.set(otherTag, current + 1);
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+ /**
74
+ * Perform full pattern analysis
75
+ */
76
+ analyze() {
77
+ if (this.entries.length < this.config.minMemories) {
78
+ logger.info(`Not enough memories for pattern analysis (${this.entries.length} < ${this.config.minMemories})`);
79
+ return this.createEmptyAnalysis();
80
+ }
81
+ logger.info(`Analyzing patterns across ${this.entries.length} memories`);
82
+ const temporalPatterns = this.getTemporalPatterns();
83
+ const topicPatterns = this.getTopicPatterns();
84
+ const interactionStyle = this.getInteractionStyle();
85
+ const optimalRecallTimes = this.getOptimalRecallTimes();
86
+ const predictedHighValueTags = this.getPredictedHighValueTags();
87
+ const anomalyScore = this.getAnomalyScore();
88
+ const analysis = {
89
+ temporalPatterns,
90
+ topicPatterns,
91
+ interactionStyle,
92
+ optimalRecallTimes,
93
+ predictedHighValueTags,
94
+ anomalyScore,
95
+ analyzedAt: Date.now(),
96
+ };
97
+ this.analysisCache = analysis;
98
+ return analysis;
99
+ }
100
+ /**
101
+ * Create empty analysis when insufficient data
102
+ */
103
+ createEmptyAnalysis() {
104
+ return {
105
+ temporalPatterns: [],
106
+ topicPatterns: [],
107
+ interactionStyle: {
108
+ style: 'mixed',
109
+ confidence: 0,
110
+ indicators: [],
111
+ dominantTags: [],
112
+ },
113
+ optimalRecallTimes: [],
114
+ predictedHighValueTags: [],
115
+ anomalyScore: 0,
116
+ analyzedAt: Date.now(),
117
+ };
118
+ }
119
+ /**
120
+ * Get temporal patterns (when user stores memories)
121
+ */
122
+ getTemporalPatterns() {
123
+ const patterns = [];
124
+ const now = Date.now();
125
+ const cutoff = now - (this.config.temporalWindowDays * 24 * 60 * 60 * 1000);
126
+ // Filter to time window
127
+ const recentEntries = this.entries.filter(e => e.timestamp >= cutoff);
128
+ // Hour patterns
129
+ const hourMap = new Map();
130
+ for (const entry of recentEntries) {
131
+ const d = new Date(entry.timestamp);
132
+ const hour = d.getHours();
133
+ const existing = hourMap.get(hour) || { count: 0, importance: 0, tags: [] };
134
+ existing.count++;
135
+ existing.importance += entry.importance;
136
+ existing.tags.push(...entry.tags.slice(0, 2));
137
+ hourMap.set(hour, existing);
138
+ }
139
+ for (const [hour, data] of hourMap) {
140
+ patterns.push({
141
+ window: 'hour',
142
+ value: hour,
143
+ count: data.count,
144
+ avgImportance: data.importance / data.count,
145
+ topTags: this.getTopTags(data.tags, 3),
146
+ });
147
+ }
148
+ // Day patterns (0=Sun, 6=Sat)
149
+ const dayMap = new Map();
150
+ for (const entry of recentEntries) {
151
+ const d = new Date(entry.timestamp);
152
+ const day = d.getDay();
153
+ const existing = dayMap.get(day) || { count: 0, importance: 0, tags: [] };
154
+ existing.count++;
155
+ existing.importance += entry.importance;
156
+ existing.tags.push(...entry.tags.slice(0, 2));
157
+ dayMap.set(day, existing);
158
+ }
159
+ for (const [day, data] of dayMap) {
160
+ patterns.push({
161
+ window: 'day',
162
+ value: day,
163
+ count: data.count,
164
+ avgImportance: data.importance / data.count,
165
+ topTags: this.getTopTags(data.tags, 3),
166
+ });
167
+ }
168
+ return patterns;
169
+ }
170
+ /**
171
+ * Get top tags from a list
172
+ */
173
+ getTopTags(tags, limit) {
174
+ const counts = new Map();
175
+ for (const tag of tags) {
176
+ counts.set(tag, (counts.get(tag) || 0) + 1);
177
+ }
178
+ return Array.from(counts.entries())
179
+ .sort((a, b) => b[1] - a[1])
180
+ .slice(0, limit)
181
+ .map(([tag]) => tag);
182
+ }
183
+ /**
184
+ * Get topic patterns based on tag frequency and recency
185
+ */
186
+ getTopicPatterns() {
187
+ const patterns = [];
188
+ for (const [tag, data] of this.tagCooccurrence) {
189
+ const recency = Math.max(...this.entries
190
+ .filter(e => e.tags.includes(tag))
191
+ .map(e => e.timestamp));
192
+ // Calculate coherence: how consistently this tag appears
193
+ const entriesWithTag = this.entries.filter(e => e.tags.includes(tag));
194
+ const coherence = entriesWithTag.length / this.entries.length;
195
+ patterns.push({
196
+ topic: tag,
197
+ frequency: data.totalCount,
198
+ avgImportance: data.totalCount / this.entries.length, // normalized
199
+ recency,
200
+ coherence: Math.min(coherence * 10, 1), // scale to 0-1
201
+ });
202
+ }
203
+ return patterns.sort((a, b) => b.frequency - a.frequency);
204
+ }
205
+ /**
206
+ * Detect user's interaction style based on tag patterns
207
+ */
208
+ getInteractionStyle() {
209
+ const tagData = Array.from(this.tagCooccurrence.entries())
210
+ .map(([tagName, data]) => ({
211
+ tag: tagName,
212
+ cooccursWith: data.cooccursWith,
213
+ totalCount: data.totalCount
214
+ }))
215
+ .sort((a, b) => b.totalCount - a.totalCount);
216
+ const topTags = tagData.slice(0, 10).map(d => d.tag);
217
+ // Style indicators
218
+ const indicators = [];
219
+ // Analytical: code, technical, system, data, logic
220
+ const analyticalTags = ['code', 'technical', 'system', 'data', 'logic', 'debug', 'architecture'];
221
+ const analyticalCount = topTags.filter(t => analyticalTags.includes(t.toLowerCase())).length;
222
+ if (analyticalCount >= 2)
223
+ indicators.push('anytical patterns detected');
224
+ // Creative: idea, design, creative, story, write, art
225
+ const creativeTags = ['idea', 'design', 'creative', 'story', 'write', 'art', 'content'];
226
+ const creativeCount = topTags.filter(t => creativeTags.some(c => t.toLowerCase().includes(c))).length;
227
+ if (creativeCount >= 2)
228
+ indicators.push('creative patterns detected');
229
+ // Operational: task, project, plan, schedule, workflow
230
+ const operationalTags = ['task', 'project', 'plan', 'schedule', 'workflow', 'routine'];
231
+ const operationalCount = topTags.filter(t => operationalTags.some(o => t.toLowerCase().includes(o))).length;
232
+ if (operationalCount >= 2)
233
+ indicators.push('operational patterns detected');
234
+ // Social: user, people, team, communication, feedback
235
+ const socialTags = ['user', 'people', 'team', 'communication', 'feedback', 'review', 'collaboration'];
236
+ const socialCount = topTags.filter(t => socialTags.some(s => t.toLowerCase().includes(s))).length;
237
+ if (socialCount >= 2)
238
+ indicators.push('social patterns detected');
239
+ // Determine dominant style
240
+ const counts = { analytical: analyticalCount, creative: creativeCount, operational: operationalCount, social: socialCount };
241
+ const maxCount = Math.max(...Object.values(counts));
242
+ const dominantStyles = Object.entries(counts).filter(([, c]) => c === maxCount).map(([s]) => s);
243
+ let style = 'mixed';
244
+ let confidence = 0.5;
245
+ if (dominantStyles.length === 1 && maxCount >= 2) {
246
+ style = dominantStyles[0];
247
+ confidence = Math.min(maxCount / 5, 0.95); // higher confidence with more matches
248
+ }
249
+ return {
250
+ style,
251
+ confidence,
252
+ indicators,
253
+ dominantTags: topTags.slice(0, 5),
254
+ };
255
+ }
256
+ /**
257
+ * Calculate optimal times for memory recall based on temporal patterns
258
+ */
259
+ getOptimalRecallTimes() {
260
+ const hourCounts = new Map();
261
+ for (const entry of this.entries) {
262
+ const d = new Date(entry.timestamp);
263
+ const hour = d.getHours();
264
+ hourCounts.set(hour, (hourCounts.get(hour) || 0) + 1);
265
+ }
266
+ const maxCount = Math.max(...hourCounts.values());
267
+ const results = [];
268
+ for (let h = 0; h < 24; h++) {
269
+ const count = hourCounts.get(h) || 0;
270
+ results.push({
271
+ hour: h,
272
+ score: count / maxCount,
273
+ });
274
+ }
275
+ return results.sort((a, b) => b.score - a.score);
276
+ }
277
+ /**
278
+ * Predict which tags will be high-value based on patterns
279
+ */
280
+ getPredictedHighValueTags() {
281
+ // Tags that appear frequently with high importance
282
+ const tagScores = new Map();
283
+ for (const entry of this.entries) {
284
+ for (const tag of entry.tags) {
285
+ const currentScore = tagScores.get(tag) || 0;
286
+ // Score = importance * recency boost * frequency
287
+ const recencyBoost = this.getRecencyBoost(entry.timestamp);
288
+ tagScores.set(tag, currentScore + (entry.importance * recencyBoost));
289
+ }
290
+ }
291
+ return Array.from(tagScores.entries())
292
+ .sort((a, b) => b[1] - a[1])
293
+ .slice(0, 10)
294
+ .map(([tag]) => tag);
295
+ }
296
+ /**
297
+ * Get recency boost factor (higher for recent entries)
298
+ */
299
+ getRecencyBoost(timestamp) {
300
+ const now = Date.now();
301
+ const age = now - timestamp;
302
+ const dayMs = 24 * 60 * 60 * 1000;
303
+ const daysOld = age / dayMs;
304
+ // Exponential decay: more recent = higher boost
305
+ return Math.exp(-daysOld / 7); // 7-day half-life
306
+ }
307
+ /**
308
+ * Calculate anomaly score for recent memories
309
+ * Compares recent patterns to historical baseline
310
+ */
311
+ getAnomalyScore() {
312
+ if (!this.config.enableAnomalyDetection || this.entries.length < 20) {
313
+ return 0;
314
+ }
315
+ const now = Date.now();
316
+ const dayMs = 24 * 60 * 60 * 1000;
317
+ const weekMs = 7 * dayMs;
318
+ // Split into recent (last 3 days) and baseline (before that)
319
+ const recentCutoff = now - (3 * dayMs);
320
+ const baselineCutoff = now - weekMs;
321
+ const recentEntries = this.entries.filter(e => e.timestamp >= recentCutoff);
322
+ const baselineEntries = this.entries.filter(e => e.timestamp >= baselineCutoff && e.timestamp < recentCutoff);
323
+ if (recentEntries.length === 0 || baselineEntries.length === 0) {
324
+ return 0;
325
+ }
326
+ // Compare tag distribution
327
+ const recentTags = this.getTagDistribution(recentEntries);
328
+ const baselineTags = this.getTagDistribution(baselineEntries);
329
+ // Calculate KL-like divergence (simplified)
330
+ let divergence = 0;
331
+ const allTags = new Set([...recentTags.keys(), ...baselineTags.keys()]);
332
+ for (const tag of allTags) {
333
+ const p = recentTags.get(tag) || 0.001;
334
+ const q = baselineTags.get(tag) || 0.001;
335
+ divergence += p * Math.log(p / q);
336
+ }
337
+ // Normalize to 0-1 range (typical divergence is 0-2)
338
+ return Math.min(divergence / 2, 1);
339
+ }
340
+ /**
341
+ * Get tag distribution as proportions
342
+ */
343
+ getTagDistribution(entries) {
344
+ const counts = new Map();
345
+ let total = 0;
346
+ for (const entry of entries) {
347
+ for (const tag of entry.tags) {
348
+ counts.set(tag, (counts.get(tag) || 0) + 1);
349
+ total++;
350
+ }
351
+ }
352
+ const dist = new Map();
353
+ for (const [tag, count] of counts) {
354
+ dist.set(tag, count / total);
355
+ }
356
+ return dist;
357
+ }
358
+ }
package/dist/index.js CHANGED
@@ -10,3 +10,4 @@ export { createMCPProtocol } from './core/collaboration/mcp-protocol.js';
10
10
  export { createTruthTeller, formatTruthStatement } from './core/truth-teller.js';
11
11
  export { createActiveInferenceEngine, formatFreeEnergyMetrics, formatBeliefState } from './core/cognition/active-inference.js';
12
12
  export { createExpertModelsEngine } from './core/expert-models/index.js';
13
+ export { createBehaviorPlaybook } from './core/behavior-playbook.js';
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '2.3.0';
1
+ export const VERSION = '2.3.2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mark-improving-agent",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "description": "Self-evolving AI agent with permanent memory, identity continuity, and self-evolution — for AI agents that need to remember, learn, and evolve across sessions",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",