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 +1 -1
- package/dist/core/behavior-playbook.js +211 -0
- package/dist/core/memory/index.js +1 -0
- package/dist/core/memory/pattern-recognizer.js +358 -0
- package/dist/index.js +1 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.3.
|
|
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.
|
|
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.
|
|
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",
|